Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b2b9723
fix(security): close custom-action gate bypass and harden auth bounda…
RishadAlam Jul 26, 2026
670339e
feat(webhook): dynamic url path variables in outgoing webhook
RishadAlam Jul 26, 2026
a760955
chore: banner updated
RishadAlam Jul 26, 2026
f5e1d8b
feat(webhook): offer smart codes in params, headers and path variables
RishadAlam Jul 26, 2026
8093b36
fix(webhook): sync path variables even when their tab is never opened
RishadAlam Jul 26, 2026
c8deeb4
fix(webhook): judge webhook runs by their http status code
RishadAlam Jul 26, 2026
42d23ef
feat(connections): switch an action's connection from its info page
RishadAlam Jul 26, 2026
3e99857
fix(info): stop legacy actions rendering a blank info page
RishadAlam Jul 26, 2026
8616798
style(app): put every page and table on the shared canvas
RishadAlam Jul 26, 2026
f6bed35
refactor(styles): drop dead rules from app.scss
RishadAlam Jul 26, 2026
f0f574c
fix(styles): give every multiselect a white field and matching border
RishadAlam Jul 26, 2026
e7a0e8d
Merge branch 'main' into redesign/settings-doc-support
RishadAlam Jul 29, 2026
e32cf3d
refactor(core): use WordPress APIs over raw PHP functions
RishadAlam Jul 29, 2026
c4776a6
fix(admin): register submenu only when capability check passes
RishadAlam Jul 29, 2026
a107f31
chore: declare license with an SPDX identifier
RishadAlam Jul 29, 2026
9ba71a9
fix(helper): guard ACF meta read against a missing value
RishadAlam Jul 29, 2026
1a72553
feat(connections): explain missing credentials on legacy integration …
RishadAlam Jul 29, 2026
9e4c5d0
feat(connections): show loading state while connections fetch
RishadAlam Jul 30, 2026
4f3f5f0
style(connections): render the manage-connections link as a button
RishadAlam Jul 30, 2026
984969c
docs(actions): update action doc links and wire the missing ones
RishadAlam Jul 30, 2026
69210e8
fix(connections): resolve credentials for renamed integrations
RishadAlam Jul 30, 2026
51b7300
feat(menus): open every popover menu the way the row-action menu does
RishadAlam Jul 30, 2026
a4bf61a
Merge branch 'main' into redesign/settings-doc-support
RishadAlam Jul 30, 2026
a0ce089
Merge branch 'main' into redesign/settings-doc-support
RishadAlam Jul 30, 2026
65f20d9
chore: bump version v2.10.1
RishadAlam Jul 30, 2026
1e5d110
docs(bitCrm): add translator comment and align param annotation
RishadAlam Jul 30, 2026
045d787
chore(phpcs): silence PrefixAllGlobals false positives and third-part…
RishadAlam Jul 30, 2026
4462c5c
refactor(fileSystem): simplify instance() resolution
RishadAlam Jul 30, 2026
5a661ec
refactor(fileSystem): collapse instance() to a single return
RishadAlam Jul 30, 2026
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
Binary file removed .wordpress-org/banner-1544x500.gif
Binary file not shown.
Binary file added .wordpress-org/banner-1544x500.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed .wordpress-org/banner-772x250.gif
Binary file not shown.
Binary file added .wordpress-org/banner-772x250.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions backend/Actions/BitCrm/BitCrmActionHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
exit;
}

/**
* Every do_action() below fires one of Bit CRM's own `bit_crm/*` hooks on its
* behalf, because the service methods this helper calls do not fire them
* themselves. The names belong to Bit CRM's namespace and must match it exactly
* for its listeners to react, so they cannot carry this plugin's prefix.
*
* phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
*/
final class BitCrmActionHelper
{
public static function createLead($fieldData)
Expand Down
2 changes: 2 additions & 0 deletions backend/Actions/ConvertKit/ConvertKitController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class ConvertKitController
public static array $authConfig = [
'authType' => AuthorizationType::API_KEY,
'slug' => 'convertkit',
// Connections store the UI's display name, which carries the Kit rebrand.
'aliases' => ['Kit(ConvertKit)'],
'fields' => [
'api_secret' => 'value',
],
Expand Down
10 changes: 8 additions & 2 deletions backend/Actions/CustomAction/CustomActionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,15 @@ public static function functionValidateHandler($data)

public function execute($integrationData, $fieldValues)
{
$funcFileLocation = $integrationData->flow_details->funcFileLocation;
$integId = $integrationData->id;
$isExits = file_exists($funcFileLocation);

// funcFileLocation arrives inside flow_details (caller-supplied JSON), so file_exists()
// alone would include any readable PHP on the box. Only ever run a file the plugin
// itself wrote into the custom-function directory.
$funcFileLocation = CustomFuncValidator::resolveCustomFunctionFile(
$integrationData->flow_details->funcFileLocation ?? ''
);
$isExits = $funcFileLocation !== '';
$isSuccessfullyRun = true;
$additionalData = null;

Expand Down
3 changes: 2 additions & 1 deletion backend/Actions/Dropbox/RecordApiHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace BitApps\Integrations\Actions\Dropbox;

use BitApps\Integrations\Core\Util\Common;
use BitApps\Integrations\Core\Util\FileSystem;
use BitApps\Integrations\Core\Util\HttpHelper;
use BitApps\Integrations\Log\LogHandler;
use WP_Error;
Expand Down Expand Up @@ -33,7 +34,7 @@ public function uploadFile($folder, $filePath)
return new WP_Error(423, __('Can\'t open file!', 'bit-integrations'));
}

$body = file_get_contents($safeFilePath);
$body = FileSystem::read($safeFilePath);

if (!$body) {
return new WP_Error(423, __('Can\'t open file!', 'bit-integrations'));
Expand Down
3 changes: 1 addition & 2 deletions backend/Actions/Encharge/RecordApiHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ public function execute($fieldValues, $fieldMap, $tags)

foreach ($fieldMap as $fieldKey => $fieldPair) {
if (!empty($fieldPair->enChargeFields)) {
// echo $fieldPair->enChargeFields . ' ' . $fieldPair->formField;
if ($fieldPair->formField === 'custom' && isset($fieldPair->customValue)) {
$fieldData[$fieldPair->enChargeFields] = Common::replaceFieldWithValue($fieldPair->customValue, $fieldValues);
} elseif (!\is_null($fieldValues[$fieldPair->formField])) {
Expand Down Expand Up @@ -76,7 +75,7 @@ public function execute($fieldValues, $fieldMap, $tags)

private function combineTagsWithExisting($tags, $email)
{
$endpoint = $this->_endpoint . '?people[0][email]=' . urlencode($email);
$endpoint = $this->_endpoint . '?people[0][email]=' . rawurlencode($email);

$response = HttpHelper::get($endpoint, null, $this->_defaultHeader);

Expand Down
2 changes: 1 addition & 1 deletion backend/Actions/Fabman/FabmanController.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ private static function fetchMemberByEmail($apiKey, $email)
'Content-Type' => 'application/json'
];

$apiEndpoint = 'https://fabman.io/api/v1/members?q=' . urlencode($email);
$apiEndpoint = 'https://fabman.io/api/v1/members?q=' . rawurlencode($email);
$apiResponse = HttpHelper::get($apiEndpoint, null, $header);

if (is_wp_error($apiResponse) || !\is_array($apiResponse) || empty($apiResponse)) {
Expand Down
14 changes: 7 additions & 7 deletions backend/Actions/Fabman/RecordApiHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ private function createMember($data)
{
unset($data['memberId']);
$apiEndpoint = self::API_ENDPOINT . '/members';
$apiResponse = HttpHelper::post($apiEndpoint, json_encode($data), $this->setHeaders());
$apiResponse = HttpHelper::post($apiEndpoint, wp_json_encode($data), $this->setHeaders());

if (\is_wp_error($apiResponse)) {
return $apiResponse;
Expand All @@ -181,13 +181,13 @@ private function updateMember($data)
return new WP_Error('MISSING_MEMBER_ID', __('The email provided did not match any existing Fabman member.', 'bit-integrations'));
}

$response = Hooks::apply(Config::withPrefix('fabman_update_member'), false, json_encode($data), $this->setHeaders(), self::API_ENDPOINT, $this->memberId);
$response = Hooks::apply(Config::withPrefix('fabman_update_member'), false, wp_json_encode($data), $this->setHeaders(), self::API_ENDPOINT, $this->memberId);

/**
* @deprecated 2.7.8 Use `bit_integrations_fabman_update_member` filter instead.
* @since 2.7.8
*/
$response = Hooks::apply('btcbi_fabman_update_member', $response, json_encode($data), $this->setHeaders(), self::API_ENDPOINT, $this->memberId);
$response = Hooks::apply('btcbi_fabman_update_member', $response, wp_json_encode($data), $this->setHeaders(), self::API_ENDPOINT, $this->memberId);

return $this->handleFilterResponse($response);
}
Expand All @@ -212,13 +212,13 @@ private function deleteMember()
private function createSpace($data)
{
unset($data['space']);
$response = Hooks::apply(Config::withPrefix('fabman_create_space'), false, json_encode($data), $this->setHeaders(), self::API_ENDPOINT);
$response = Hooks::apply(Config::withPrefix('fabman_create_space'), false, wp_json_encode($data), $this->setHeaders(), self::API_ENDPOINT);

/**
* @deprecated 2.7.8 Use `bit_integrations_fabman_create_space` filter instead.
* @since 2.7.8
*/
$response = Hooks::apply('btcbi_fabman_create_space', $response, json_encode($data), $this->setHeaders(), self::API_ENDPOINT);
$response = Hooks::apply('btcbi_fabman_create_space', $response, wp_json_encode($data), $this->setHeaders(), self::API_ENDPOINT);

return $this->handleFilterResponse($response);
}
Expand All @@ -236,13 +236,13 @@ private function updateSpace($data)

$data['lockVersion'] = (int) $this->lockVersion;

$response = Hooks::apply(Config::withPrefix('fabman_update_space'), false, json_encode($data), $this->setHeaders(), self::API_ENDPOINT, $this->workspaceId);
$response = Hooks::apply(Config::withPrefix('fabman_update_space'), false, wp_json_encode($data), $this->setHeaders(), self::API_ENDPOINT, $this->workspaceId);

/**
* @deprecated 2.7.8 Use `bit_integrations_fabman_update_space` filter instead.
* @since 2.7.8
*/
$response = Hooks::apply('btcbi_fabman_update_space', $response, json_encode($data), $this->setHeaders(), self::API_ENDPOINT, $this->workspaceId);
$response = Hooks::apply('btcbi_fabman_update_space', $response, wp_json_encode($data), $this->setHeaders(), self::API_ENDPOINT, $this->workspaceId);

return $this->handleFilterResponse($response);
}
Expand Down
3 changes: 2 additions & 1 deletion backend/Actions/GoogleDrive/RecordApiHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace BitApps\Integrations\Actions\GoogleDrive;

use BitApps\Integrations\Core\Util\Common;
use BitApps\Integrations\Core\Util\FileSystem;
use BitApps\Integrations\Core\Util\HttpHelper;
use BitApps\Integrations\Log\LogHandler;

Expand Down Expand Up @@ -95,7 +96,7 @@ protected function getBody($folder, $filePath, $boundary)
$body .= '{"name": "' . basename($filePath) . '", "parents": ["' . $folder . '"]}' . "\r\n";
$body .= '--' . $boundary . "\r\n";
$body .= "Content-Type: application/octet-stream\r\n\r\n";
$body .= file_get_contents($filePath) . "\r\n";
$body .= FileSystem::read($filePath) . "\r\n";
$body .= '--' . $boundary . "--\r\n";

return $body;
Expand Down
2 changes: 2 additions & 0 deletions backend/Actions/LMFWC/LMFWCController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class LMFWCController
public static array $authConfig = [
'authType' => AuthorizationType::API_KEY,
'slug' => 'lmfwc',
// Connections store the UI's display name, which spells the plugin out.
'aliases' => ['License Manager For WooCommerce'],
'fields' => [
'api_key' => 'value',
'api_secret' => 'api_secret',
Expand Down
6 changes: 3 additions & 3 deletions backend/Actions/Line/RecordApiHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,18 @@ public function execute($integrationDetails, $fieldValues)
switch ($type) {
case 'sendReplyMessage':
$data['replyToken'] = $integrationDetails->replyToken ?? '';
$response = $this->sendReplyMessage(json_encode($data));
$response = $this->sendReplyMessage(wp_json_encode($data));

break;

case 'sendBroadcastMessage':
$response = $this->sendBroadcastMessage(json_encode($data));
$response = $this->sendBroadcastMessage(wp_json_encode($data));

break;

default:
$data['to'] = $integrationDetails->recipientId ?? '';
$response = $this->sendPushMessage(json_encode($data));
$response = $this->sendPushMessage(wp_json_encode($data));
$response = HttpHelper::$responseCode === 200 ? 'Push message sent successfully' : 'Failed';
}

Expand Down
51 changes: 37 additions & 14 deletions backend/Actions/Mail/MailController.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,33 +78,56 @@ public static function filterMailContentType()
return 'text/html; charset=UTF-8';
}

/**
* Resolve mapped address values and keep only the ones that are real addresses.
*
* The scalar branch used to return the interpolated value unchecked, so a submitted
* form field became the recipient/header verbatim. Both branches now apply the same
* is_email() filter, and anything that fails it is dropped rather than passed on.
*
* @param array|string $emailAddresses
* @param array $fieldValues
*
* @return array
*/
public function validateAddresses($emailAddresses, $fieldValues)
{
if (!\is_array($emailAddresses)) {
return [Common::replaceFieldWithValue($emailAddresses, $fieldValues)];
}
foreach ($emailAddresses as $key => $email) {
$candidates = \is_array($emailAddresses) ? $emailAddresses : [$emailAddresses];
$valid = [];

foreach ($candidates as $email) {
if (!\is_scalar($email)) {
continue;
}

$email = (string) $email;

if (!is_email($email)) {
$email = Common::replaceFieldWithValue($email, $fieldValues);
}
if (is_email($email)) {
$emailAddresses[$key] = $email;

// A single mapped field may resolve to a comma-separated list.
foreach (explode(',', (string) $email) as $candidate) {
$candidate = sanitize_email(trim($candidate));

if ($candidate !== '' && is_email($candidate)) {
$valid[] = $candidate;
}
}
}

return $emailAddresses;
return array_values(array_unique($valid));
}

public function processHeader($type, $address, $fields)
{
$headers = [];
$addresses = $this->validateAddresses($address, $fields);
if (\is_array($addresses)) {
foreach ($addresses as $address) {
$headers[] = "{$type}: " . explode('@', $address)[0] . '<' . sanitize_email($address) . '>';
}
} else {
$headers[] = "{$type}: " . explode('@', $addresses)[0] . '<' . sanitize_email($addresses) . '>';

foreach ($this->validateAddresses($address, $fields) as $validAddress) {
// The local part becomes the header display name, so it must be sanitized too —
// it is submitter-controlled and lands in a raw header string.
$displayName = sanitize_text_field(explode('@', $validAddress)[0]);
$headers[] = "{$type}: {$displayName}<{$validAddress}>";
}

return $headers;
Expand Down
2 changes: 1 addition & 1 deletion backend/Actions/MailRelay/RecordApiHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public function execute($selectedGroups, $fieldValues, $fieldMap, $status)
public function isExist($baseUrl, $email)
{
$queryEndpoints = $baseUrl . 'subscribers?q%5Bemail_eq%5D=';
$encodedEmail = urlencode($email);
$encodedEmail = rawurlencode($email);
$apiEndpoints = $queryEndpoints . $encodedEmail;
$response = HttpHelper::get($apiEndpoints, null, $this->_defaultHeader);

Expand Down
2 changes: 2 additions & 0 deletions backend/Actions/Mailify/MailifyController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class MailifyController
public static array $authConfig = [
'authType' => AuthorizationType::BASIC_AUTH,
'slug' => 'mailify',
// Connections store the UI's display name, which carries the Sarbacane rebrand.
'aliases' => ['Sarbacane(Mailify)'],
'fields' => [
'account_id' => 'username',
'api_key' => 'password',
Expand Down
2 changes: 1 addition & 1 deletion backend/Actions/Mailjet/RecordApiHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ private function jobMonitoring($response)

private function isExist($email)
{
$encodedEmail = urlencode($email);
$encodedEmail = rawurlencode($email);
$apiEndpoint = 'https://api.mailjet.com/v3/REST/contact/' . $encodedEmail;
$response = HttpHelper::get($apiEndpoint, null, $this->_defaultHeader);

Expand Down
3 changes: 2 additions & 1 deletion backend/Actions/OneDrive/RecordApiHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace BitApps\Integrations\Actions\OneDrive;

use BitApps\Integrations\Core\Util\Common;
use BitApps\Integrations\Core\Util\FileSystem;
use BitApps\Integrations\Core\Util\HttpHelper;
use BitApps\Integrations\Log\LogHandler;

Expand Down Expand Up @@ -46,7 +47,7 @@ public function uploadFile($folder, $file, $folderId, $parentId)

return HttpHelper::post(
$apiEndpoint,
file_get_contents($filePath),
FileSystem::read($filePath),
$headers
);
}
Expand Down
2 changes: 1 addition & 1 deletion backend/Actions/Salesforce/SalesforceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ public static function selesforceUserList($params)
$response = self::refreshTokenDetails($params);
$tokenDetails = $response['tokenDetails'];

$apiEndpoint = "{$tokenDetails->instance_url}/services/data/v37.0/query/?q=" . urlencode('SELECT Id, Name FROM User');
$apiEndpoint = "{$tokenDetails->instance_url}/services/data/v37.0/query/?q=" . rawurlencode('SELECT Id, Name FROM User');

$apiResponse = HttpHelper::get($apiEndpoint, null, self::setHeaders($tokenDetails->access_token));

Expand Down
2 changes: 2 additions & 0 deletions backend/Actions/SendinBlue/SendinBlueController.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class SendinBlueController
public static array $authConfig = [
'authType' => AuthorizationType::API_KEY,
'slug' => 'sendinblue',
// Connections store the UI's display name, which carries the Brevo rebrand.
'aliases' => ['Brevo(Sendinblue)'],
'fields' => [
'api_key' => 'value',
],
Expand Down
Loading