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
60 changes: 60 additions & 0 deletions backend/Actions/MainWP/MainWPController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace BitApps\Integrations\Actions\MainWP;

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

class MainWPController
{
public static function isExists(): void
{
if (!class_exists('\MainWP\Dashboard\MainWP_DB')) {
wp_send_json_error(
__('MainWP Dashboard is not activated or not installed', 'bit-integrations'),
400
);
}
}

public static function mainWPAuthorize(): void
{
self::isExists();
wp_send_json_success(true);
}

public static function refreshSites(): void
{
self::isExists();

$websites = \MainWP\Dashboard\MainWP_DB::instance()->get_sites();
$sites = [];

if (!empty($websites)) {
foreach ($websites as $website) {
$website = (array) $website;
if (empty($website['id'])) {
continue;
}
$sites[] = [
'value' => (string) $website['id'],
'label' => ($website['name'] ?? 'Site') . ' (' . ($website['url'] ?? '') . ')',
];
}
Comment on lines +35 to +44

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The MainWP_DB::instance()->get_sites() method returns an array of stdClass objects. Accessing $website directly as an array (e.g., $website['id']) will throw a fatal error: Cannot use object of type stdClass as array. Casting $website to an array before processing prevents this fatal error and ensures compatibility.

            foreach ($websites as $website) {
                $websiteArray = (array) $website;
                if (empty($websiteArray['id'])) {
                    continue;
                }
                $sites[] = [
                    'value' => (string) $websiteArray['id'],
                    'label' => ($websiteArray['name'] ?? 'Site') . ' (' . ($websiteArray['url'] ?? '') . ')',
                ];
            }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed β€” get_sites() returns stdClass objects, so the loop now casts each entry before key access:

foreach ($websites as $website) {
    $website = (array) $website;
    if (empty($website['id'])) {
        continue;
    }
    ...
}

}

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

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

$RecordApiHelper = new RecordApiHelper($integrationDetails, $integId);

return $RecordApiHelper->execute($fieldValues, $fieldMap);
}
}
151 changes: 151 additions & 0 deletions backend/Actions/MainWP/RecordApiHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php

namespace BitApps\Integrations\Actions\MainWP;

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

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

class RecordApiHelper
{
private $_integrationID;

private $_integrationDetails;

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

public function execute($fieldValues, $fieldMap)
{
if (!class_exists('\MainWP\Dashboard\MainWP_DB')) {
return [
'success' => false,
'message' => __('MainWP Dashboard is not installed or activated', 'bit-integrations'),
];
}
Comment on lines +28 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure defensive programming and prevent potential warnings or fatal errors in PHP 8.0+, we should verify that $this->_integrationDetails is not empty before accessing its properties (like mainAction, selectedSite, or utilities).

        if (!class_exists('\\MainWP\\Dashboard\\MainWP_DB')) {
            return [
                'success' => false,
                'message' => __('MainWP Dashboard is not installed or activated', 'bit-integrations'),
            ];
        }

        if (empty($this->_integrationDetails)) {
            return [
                'success' => false,
                'message' => __('Integration details are missing', 'bit-integrations'),
            ];
        }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed β€” added a guard right after the class_exists check:

if (empty($this->_integrationDetails)) {
    return [
        'success' => false,
        'message' => __('Integration details are missing', 'bit-integrations'),
    ];
}


if (empty($this->_integrationDetails)) {
return [
'success' => false,
'message' => __('Integration details are missing', 'bit-integrations'),
];
}

$fieldData = static::generateReqDataFromFieldMap($fieldMap, $fieldValues);
$mainAction = $this->_integrationDetails->mainAction ?? 'sync_site';

if (!empty($this->_integrationDetails->selectedSite)) {
$fieldData['site_id'] = (int) $this->_integrationDetails->selectedSite;
}

if (in_array($mainAction, ['create_post', 'update_post'], true)) {
$utils = (array) ($this->_integrationDetails->utilities ?? []);
if (!empty($utils['post_type'])) {
$fieldData['post_type'] = sanitize_text_field($utils['post_type']);
}
if (!empty($utils['post_status'])) {
$fieldData['post_status'] = sanitize_text_field($utils['post_status']);
}
}

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

switch ($mainAction) {
case 'sync_site':
$response = Hooks::apply(Config::withPrefix('main_wp_sync_site'), $defaultResponse, $fieldData);
$actionType = 'sync_site';

break;

case 'sync_all_sites':
$response = Hooks::apply(Config::withPrefix('main_wp_sync_all_sites'), $defaultResponse);
$actionType = 'sync_all_sites';

break;

case 'create_post':
$response = Hooks::apply(Config::withPrefix('main_wp_create_post'), $defaultResponse, $fieldData);
$actionType = 'create_post';

break;

case 'update_post':
$response = Hooks::apply(Config::withPrefix('main_wp_update_post'), $defaultResponse, $fieldData);
$actionType = 'update_post';

break;

case 'delete_post':
$response = Hooks::apply(Config::withPrefix('main_wp_delete_post'), $defaultResponse, $fieldData);
$actionType = 'delete_post';

break;

case 'activate_plugin':
$response = Hooks::apply(Config::withPrefix('main_wp_activate_plugin'), $defaultResponse, $fieldData);
$actionType = 'activate_plugin';

break;

case 'deactivate_plugin':
$response = Hooks::apply(Config::withPrefix('main_wp_deactivate_plugin'), $defaultResponse, $fieldData);
$actionType = 'deactivate_plugin';

break;

case 'create_user':
$response = Hooks::apply(Config::withPrefix('main_wp_create_user'), $defaultResponse, $fieldData);
$actionType = 'create_user';

break;

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

break;
}

if (is_wp_error($response)) {
$response = [
'success' => false,
'message' => $response->get_error_message(),
];
}

$responseType = isset($response['success']) && $response['success'] ? 'success' : 'error';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the response from Hooks::apply is a WP_Error object, accessing it as an array (e.g., $response['success']) will trigger a fatal error in PHP 8.0+ because WP_Error does not implement ArrayAccess. Always validate the response using is_wp_error() before accessing it as an array.

        if (is_wp_error($response)) {
            $response = [
                'success' => false,
                'message' => $response->get_error_message(),
            ];
        }

        $responseType = isset($response['success']) && $response['success'] ? 'success' : 'error';
References
  1. In PHP, when handling responses that may return a WP_Error object, always use the is_wp_error() function to validate the response before accessing it as an array or object. This prevents fatal errors in PHP 8.0+.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed β€” added an is_wp_error() guard before the array access so a WP_Error from Hooks::apply is normalized first:

if (is_wp_error($response)) {
    $response = ['success' => false, 'message' => $response->get_error_message()];
}
$responseType = isset($response['success']) && $response['success'] ? 'success' : 'error';

LogHandler::save($this->_integrationID, ['type' => 'MainWP', 'type_name' => $actionType], $responseType, $response);

return $response;
}

private static function generateReqDataFromFieldMap($fieldMap, $fieldValues): array
{
$data = [];
foreach ($fieldMap as $item) {
$triggerValue = $item->formField;
$actionValue = $item->mainWPField;

if (empty($actionValue) || (empty($triggerValue) && $triggerValue !== 'custom')) {
continue;
}

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

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

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

use BitApps\Integrations\Actions\MainWP\MainWPController;
use BitApps\Integrations\Core\Util\Route;

Route::post('main_wp_authorize', [MainWPController::class, 'mainWPAuthorize']);
Route::post('refresh_main_wp_sites', [MainWPController::class, 'refreshSites']);
1 change: 1 addition & 0 deletions backend/Core/Util/AllTriggersName.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public static function allTriggersName()
'MailPoet' => ['name' => 'MailPoet', 'isPro' => true, 'is_active' => false],
'MailMint' => ['name' => 'Mail Mint', 'isPro' => true, 'is_active' => false],
'Mailster' => ['name' => 'Mailster', 'isPro' => true, 'is_active' => false],
'MainWP' => ['name' => 'MainWP', 'isPro' => true, 'is_active' => false],
'MasterStudyLms' => ['name' => 'MasterStudyLms', 'isPro' => true, 'is_active' => false],
'MasteriyoLMS' => ['name' => 'Masteriyo LMS', 'isPro' => true, 'is_active' => false],
'MyCred' => ['name' => 'myCred', 'isPro' => true, 'is_active' => false],
Expand Down
12 changes: 9 additions & 3 deletions frontend/src/Utils/StaticData/webhookIntegrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export const customFormIntegrations = [
'PopupMaker',
'DiviFormBuilder',
'Bricks',
'Bricksforge',
'Brizy',
'GutenaForms',
'PieForms',
Expand Down Expand Up @@ -100,9 +99,16 @@ export const customFormIntegrations = [
'Registration',
'Post',
'WordPress',
'FluentPdfGenerator',
'BookingPress',
'FluentPdfGenerator'
"FluentPdfGenerator",
"B2BKing",
"FormyChat",
"MainWP",
"SureDash",
"WpErp",
"WpDataTables",
"GiveWp",
"SenseiLMS"
]

export const actionHookIntegrations = ['ActionHook']
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/AllIntegrations/EditInteg.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ const EditNewsletter = lazy(() => import('./Newsletter/EditNewsletter'))
const EditSureDash = lazy(() => import('./SureDash/EditSureDash'))
const EditSureMembers = lazy(() => import('./SureMembers/EditSureMembers'))
const EditMailster = lazy(() => import('./Mailster/EditMailster'))
const EditMainWP = lazy(() => import('./MainWP/EditMainWP'))
const EditWPForo = lazy(() => import('./WPForo/EditWPForo'))
const EditDokan = lazy(() => import('./Dokan/EditDokan'))
const EditJetEngine = lazy(() => import('./JetEngine/EditJetEngine'))
Expand Down Expand Up @@ -587,6 +588,8 @@ const IntegType = memo(({ allIntegURL, flow }) => {
return <EditSureMembers allIntegURL={allIntegURL} />
case 'Mailster':
return <EditMailster allIntegURL={allIntegURL} />
case 'MainWP':
return <EditMainWP allIntegURL={allIntegURL} />
case 'WPForo':
return <EditWPForo allIntegURL={allIntegURL} />
case 'Dokan':
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 @@ -159,6 +159,7 @@ const NewsletterAuthorization = lazy(() => import('./Newsletter/NewsletterAuthor
const SureDashAuthorization = lazy(() => import('./SureDash/SureDashAuthorization'))
const SureMembersAuthorization = lazy(() => import('./SureMembers/SureMembersAuthorization'))
const MailsterAuthentication = lazy(() => import('./Mailster/MailsterAuthorization'))
const MainWPAuthorization = lazy(() => import('./MainWP/MainWPAuthorization'))
const WPForoAuthorization = lazy(() => import('./WPForo/WPForoAuthorization'))
const DokanAuthorization = lazy(() => import('./Dokan/DokanAuthorization'))
const JetEngineAuthorization = lazy(() => import('./JetEngine/JetEngineAuthorization'))
Expand Down Expand Up @@ -626,6 +627,8 @@ export default function IntegInfo() {
return <SureMembersAuthorization sureMembersConf={integrationConf} step={1} isInfo />
case 'Mailster':
return <MailsterAuthentication mailsterConf={integrationConf} step={1} isInfo />
case 'MainWP':
return <MainWPAuthorization mainWPConf={integrationConf} step={1} isInfo />
case 'WPForo':
return <WPForoAuthorization wpforoConf={integrationConf} step={1} isInfo />
case 'Dokan':
Expand Down
76 changes: 76 additions & 0 deletions frontend/src/components/AllIntegrations/MainWP/EditMainWP.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { useState } from 'react'
import { useNavigate, useParams } from 'react-router'
import { useRecoilState, useRecoilValue } from 'recoil'
import { $actionConf, $formFields, $newFlow } from '../../../GlobalStates'
import { __ } from '../../../Utils/i18nwrap'
import SnackMsg from '../../Utilities/SnackMsg'
import { saveActionConf } from '../IntegrationHelpers/IntegrationHelpers'
import IntegrationStepThree from '../IntegrationHelpers/IntegrationStepThree'
import SetEditIntegComponents from '../IntegrationHelpers/SetEditIntegComponents'
import { checkMappedFields, handleInput } from './MainWPCommonFunc'
import MainWPIntegLayout from './MainWPIntegLayout'

export default function EditMainWP({ allIntegURL }) {
const navigate = useNavigate()
const { id, formID } = useParams()

const [mainWPConf, setMainWPConf] = useRecoilState($actionConf)
const [flow, setFlow] = useRecoilState($newFlow)
const formFields = useRecoilValue($formFields)
const [isLoading, setIsLoading] = useState(false)
const [snack, setSnackbar] = useState({ show: false })

return (
<div style={{ width: 900 }}>
<SnackMsg snack={snack} setSnackbar={setSnackbar} />

<div className="flx mt-3">
<b className="wdt-200 d-in-b">{__('Integration Name:', 'bit-integrations')}</b>
<input
className="btcd-paper-inp w-5"
onChange={e => handleInput(e, mainWPConf, setMainWPConf)}
name="name"
value={mainWPConf.name}
type="text"
placeholder={__('Integration Name...', 'bit-integrations')}
/>
</div>
<br />

<SetEditIntegComponents entity={flow.triggered_entity} setSnackbar={setSnackbar} />

<MainWPIntegLayout
formID={formID}
formFields={formFields}
mainWPConf={mainWPConf}
setMainWPConf={setMainWPConf}
setSnackbar={setSnackbar}
isLoading={isLoading}
setIsLoading={setIsLoading}
/>

<IntegrationStepThree
edit
saveConfig={() =>
saveActionConf({
flow,
setFlow,
allIntegURL,
conf: mainWPConf,
navigate,
id,
edit: 1,
setIsLoading,
setSnackbar
})
}
disabled={!checkMappedFields(mainWPConf)}
isLoading={isLoading}
dataConf={mainWPConf}
setDataConf={setMainWPConf}
formFields={formFields}
/>
<br />
</div>
)
}
Loading
Loading