Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 119 additions & 0 deletions backend/Actions/Sender/RecordApiHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

/**
* Sender Record Api
*/

namespace BitApps\Integrations\Actions\Sender;

use BitApps\Integrations\Config;
use BitApps\Integrations\Core\Util\Common;
use BitApps\Integrations\Core\Util\Hooks;
use BitApps\Integrations\Log\LogHandler;

/**
* Build the request data from the field map and route each action to Bit Integrations Pro.
*/
class RecordApiHelper
{
private $_integrationID;

private $_integrationDetails;

public function __construct($integrationDetails, $integId)
{
$this->_integrationDetails = $integrationDetails;
$this->_integrationID = $integId;
}

public function execute($fieldValues, $fieldMap)
{
$fieldData = static::generateReqDataFromFieldMap($fieldMap, $fieldValues);
$mainAction = $this->_integrationDetails->mainAction ?? '';

$defaultResponse = [
'success' => false,
// translators: %s: Plugin name
'message' => wp_sprintf(__('%s plugin is not installed or activates', 'bit-integrations'), 'Bit Integrations Pro'),
];

switch ($mainAction) {
case 'create_or_update_subscriber':
$response = Hooks::apply(Config::withPrefix('sender_create_or_update_subscriber'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;

case 'update_subscriber':
$response = Hooks::apply(Config::withPrefix('sender_update_subscriber'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;

case 'delete_subscriber':
$response = Hooks::apply(Config::withPrefix('sender_delete_subscriber'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;

case 'remove_phone_from_subscriber':
$response = Hooks::apply(Config::withPrefix('sender_remove_phone_from_subscriber'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;

case 'add_subscriber_to_group':
$response = Hooks::apply(Config::withPrefix('sender_add_subscriber_to_group'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;

case 'remove_subscriber_from_group':
$response = Hooks::apply(Config::withPrefix('sender_remove_subscriber_from_group'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;

case 'create_group':
$response = Hooks::apply(Config::withPrefix('sender_create_group'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;

case 'update_group':
$response = Hooks::apply(Config::withPrefix('sender_update_group'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;

case 'delete_group':
$response = Hooks::apply(Config::withPrefix('sender_delete_group'), $defaultResponse, $fieldData, $this->_integrationDetails);

break;

default:
$response = [
'success' => false,
'message' => __('Invalid action', 'bit-integrations'),
];

break;
}

$responseType = !is_wp_error($response) && isset($response['success']) && $response['success'] ? 'success' : 'error';
LogHandler::save($this->_integrationID, ['type' => 'Sender', 'type_name' => $mainAction], $responseType, $response);

return $response;
}

private static function generateReqDataFromFieldMap($fieldMap, $fieldValues)
{
$dataFinal = [];
foreach ($fieldMap as $item) {
if (empty($item->senderField)) {
continue;
}

$triggerValue = $item->formField;
$actionValue = $item->senderField;

$dataFinal[$actionValue] = $triggerValue === 'custom' && isset($item->customValue)
? Common::replaceFieldWithValue($item->customValue, $fieldValues)
: ($fieldValues[$triggerValue] ?? '');
}

return $dataFinal;
}
}
12 changes: 12 additions & 0 deletions backend/Actions/Sender/Routes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

if (!defined('ABSPATH')) {
exit;
}

use BitApps\Integrations\Actions\Sender\SenderController;
use BitApps\Integrations\Core\Util\Route;

Route::post('sender_authorize', [SenderController::class, 'senderAuthorize']);
Route::post('refresh_sender_groups', [SenderController::class, 'refreshGroups']);
Route::post('refresh_sender_fields', [SenderController::class, 'refreshFields']);
159 changes: 159 additions & 0 deletions backend/Actions/Sender/SenderController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php

/**
* Sender Integration
*/

namespace BitApps\Integrations\Actions\Sender;

use BitApps\Integrations\Core\Util\HttpHelper;
use WP_Error;

/**
* Provide functionality for Sender (sender.net) integration
*/
class SenderController
{
private static $baseUrl = 'https://api.sender.net/v2';

/**
* Verify the supplied API access token by hitting the groups endpoint.
*
* @param object $requestParams
*/
public static function senderAuthorize($requestParams)
{
if (empty($requestParams->api_token)) {
wp_send_json_error(__('API token is required', 'bit-integrations'), 400);
}

$response = HttpHelper::get(self::$baseUrl . '/groups', null, self::authHeader($requestParams->api_token));

if (is_wp_error($response)) {
wp_send_json_error($response->get_error_message(), 400);
}

if (HttpHelper::$responseCode >= 200 && HttpHelper::$responseCode < 300) {
wp_send_json_success(__('Authorized Successfully', 'bit-integrations'), 200);
}

wp_send_json_error(__('Invalid API token', 'bit-integrations'), 400);
}

/**
* Fetch the account groups for the group dropdowns.
*
* @param object $requestParams
*/
public static function refreshGroups($requestParams)
{
if (empty($requestParams->api_token)) {
wp_send_json_error(__('API token is required', 'bit-integrations'), 400);
}

$response = HttpHelper::get(self::$baseUrl . '/groups', null, self::authHeader($requestParams->api_token));

if (is_wp_error($response)) {
wp_send_json_error($response->get_error_message(), 400);
}

if (HttpHelper::$responseCode < 200 || HttpHelper::$responseCode >= 300) {
wp_send_json_error(__('Failed to fetch groups', 'bit-integrations'), 400);
}

$groups = [];
foreach (self::dataRows($response) as $group) {
$groups[] = [
'id' => $group->id ?? '',
'title' => $group->title ?? '',
];
}

wp_send_json_success(['groups' => $groups], 200);
}

/**
* Fetch the account custom fields so they can be mapped on subscriber actions.
*
* @param object $requestParams
*/
public static function refreshFields($requestParams)
{
if (empty($requestParams->api_token)) {
wp_send_json_error(__('API token is required', 'bit-integrations'), 400);
}

$response = HttpHelper::get(self::$baseUrl . '/fields', null, self::authHeader($requestParams->api_token));

if (is_wp_error($response)) {
wp_send_json_error($response->get_error_message(), 400);
}

if (HttpHelper::$responseCode < 200 || HttpHelper::$responseCode >= 300) {
wp_send_json_error(__('Failed to fetch fields', 'bit-integrations'), 400);
}

$defaultFields = ['email', 'firstname', 'lastname', 'phone'];
$fields = [];
foreach (self::dataRows($response) as $field) {
// Sender returns the field token as `name` in {{slug}} form; the subscriber `fields`
// payload must be keyed by the {$slug} personalization token, not the numeric/string id.
$token = isset($field->name) ? str_replace(['{{', '}}'], ['{$', '}'], $field->name) : '';
if ($token === '') {
continue;
}

// Default subscriber fields are mapped top-level (email/firstname/lastname/phone),
// so keep them out of the custom-field options to avoid duplicate mapping.
$slug = strtolower(preg_replace('/[{}$\s]/', '', $token));
if (\in_array($slug, $defaultFields, true)) {
continue;
}

$fields[] = [
'key' => $token,
'label' => $field->title ?? $token,
];
}

wp_send_json_success(['fields' => $fields], 200);
}

public function execute($integrationData, $fieldValues)
{
$integrationDetails = $integrationData->flow_details;
$integId = $integrationData->id;
$fieldMap = $integrationDetails->field_map ?? [];

if (empty($integrationDetails->api_token)) {
return new WP_Error('api_token_empty', __('Sender API token is required', 'bit-integrations'));
}

$recordApiHelper = new RecordApiHelper($integrationDetails, $integId);
$senderResponse = $recordApiHelper->execute($fieldValues, $fieldMap);

if (is_wp_error($senderResponse)) {
return $senderResponse;
}

return $senderResponse;
}

private static function authHeader($token)
{
return [
'Authorization' => 'Bearer ' . $token,
'Accept' => 'application/json',
'Content-Type' => 'application/json',
];
}

private static function dataRows($response)
{
if (isset($response->data) && \is_array($response->data)) {
return $response->data;
}

return \is_array($response) ? $response : [];
}
}
4 changes: 4 additions & 0 deletions frontend/src/Utils/StaticData/tutorialLinks.js
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,10 @@ const tutorialLinks = {
bookingPress: {
youTubeLink: '',
docLink: ''
},
sender: {
youTubeLink: '',
docLink: 'https://bit-integrations.com/wp-docs/actions/sender-integrations/'
}
}
export default tutorialLinks
3 changes: 3 additions & 0 deletions frontend/src/components/AllIntegrations/EditInteg.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const EditPod = lazy(() => import('./Pods/EditPod'))
const EditMailPoet = lazy(() => import('./MailPoet/EditMailPoet'))
const EditMailerPress = lazy(() => import('./MailerPress/EditMailerPress'))
const EditSendinBlue = lazy(() => import('./SendinBlue/EditSendinBlue'))
const EditSender = lazy(() => import('./Sender/EditSender'))
const EditWooCommerce = lazy(() => import('./WooCommerce/EditWooCommerce'))
const EditActiveCampaign = lazy(() => import('./ActiveCampaign/EditActiveCampaign'))
const EditWebHooks = lazy(() => import('./WebHooks/EditWebHooks'))
Expand Down Expand Up @@ -307,6 +308,8 @@ const IntegType = memo(({ allIntegURL, flow }) => {
return <EditMailPoet allIntegURL={allIntegURL} />
case 'MailerPress':
return <EditMailerPress allIntegURL={allIntegURL} />
case 'Sender':
return <EditSender allIntegURL={allIntegURL} />
case 'SendinBlue':
case 'Brevo(Sendinblue)':
return <EditSendinBlue allIntegURL={allIntegURL} />
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/AllIntegrations/IntegInfo.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const MailChimpAuthorization = lazy(() => import('./MailChimp/MailChimpAuthoriza
const MailPoetAuthorization = lazy(() => import('./MailPoet/MailPoetAuthorization'))
const MailerPressAuthorization = lazy(() => import('./MailerPress/MailerPressAuthorization'))
const SendinblueAuthorization = lazy(() => import('./SendinBlue/SendinBlueAuthorization'))
const SenderAuthorization = lazy(() => import('./Sender/SenderAuthorization'))
const WooCommerceAuthorization = lazy(() => import('./WooCommerce/WooCommerceAuthorization'))
const ActiveCampaignAuthorization = lazy(() => import('./ActiveCampaign/ActiveCampaignAuthorization'))
const ZohoFlowAuthorization = lazy(() => import('./IntegrationHelpers/WebHook/WebHooksIntegration'))
Expand Down Expand Up @@ -341,6 +342,8 @@ export default function IntegInfo() {
return <MailPoetAuthorization mailPoetConf={integrationConf} step={1} isInfo />
case 'MailerPress':
return <MailerPressAuthorization mailerPressConf={integrationConf} step={1} isInfo />
case 'Sender':
return <SenderAuthorization senderConf={integrationConf} step={1} isInfo />
case 'SendinBlue':
case 'Brevo(Sendinblue)':
return <SendinblueAuthorization sendinBlueConf={integrationConf} step={1} isInfo />
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/components/AllIntegrations/NewInteg.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const MailChimp = lazy(() => import('./MailChimp/MailChimp'))
const MailPoet = lazy(() => import('./MailPoet/MailPoet'))
const MailerPress = lazy(() => import('./MailerPress/MailerPress'))
const Sendinblue = lazy(() => import('./SendinBlue/SendinBlue'))
const Sender = lazy(() => import('./Sender/Sender'))
const WooCommerce = lazy(() => import('./WooCommerce/WooCommerce'))
const Pods = lazy(() => import('./Pods/Pods'))
const ActiveCampaign = lazy(() => import('./ActiveCampaign/ActiveCampaign'))
Expand Down Expand Up @@ -333,6 +334,15 @@ export default function NewInteg({ allIntegURL }) {
setFlow={setFlow}
/>
)
case 'Sender':
return (
<Sender
allIntegURL={allIntegURL}
formFields={flow?.triggerData?.fields}
flow={flow}
setFlow={setFlow}
/>
)
case 'SendinBlue':
return (
<Sendinblue
Expand Down
Loading
Loading