feat(integrations): add MainWP action integration#172
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a new integration for MainWP, allowing users to sync sites, manage posts, control plugins, and create users. The feedback highlights several critical and high-severity issues, including a potential fatal error when accessing stdClass objects as arrays in MainWPController.php, a potential fatal error if a WP_Error object is returned and accessed as an array in RecordApiHelper.php, and a non-static method being called statically. Additionally, suggestions were made to add defensive checks for integration details and a .catch() block to prevent the UI from getting stuck in a loading state during authorization.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| foreach ($websites as $website) { | ||
| if (empty($website['id'])) { | ||
| continue; | ||
| } | ||
| $sites[] = [ | ||
| 'value' => (string) $website['id'], | ||
| 'label' => ($website['name'] ?? 'Site') . ' (' . ($website['url'] ?? '') . ')', | ||
| ]; | ||
| } |
There was a problem hiding this comment.
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'] ?? '') . ')',
];
}There was a problem hiding this comment.
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;
}
...
}| break; | ||
| } | ||
|
|
||
| $responseType = isset($response['success']) && $response['success'] ? 'success' : 'error'; |
There was a problem hiding this comment.
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
- 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+.
There was a problem hiding this comment.
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';| wp_send_json_success(true); | ||
| } | ||
|
|
||
| public function refreshSites(): void |
There was a problem hiding this comment.
The refreshSites method is registered as a route callback and called statically, but it is declared as a non-static public method. Since it does not access any instance properties ($this), it should be declared as static to adhere to best practices and avoid PHP deprecation warnings or notices.
public static function refreshSites(): voidReferences
- In PHP, declare methods as static if they do not access any instance properties ($this) and are intended to be called statically.
There was a problem hiding this comment.
Fixed — refreshSites() is now declared static (it never touches $this), matching mainWPAuthorize():
public static function refreshSites(): voidNote: Route::action() already resolves static/instance via ReflectionMethod::isStatic(), so this is a consistency/best-practice change with no runtime behavior difference.
| if (!class_exists('\MainWP\Dashboard\MainWP_DB')) { | ||
| return [ | ||
| 'success' => false, | ||
| 'message' => __('MainWP Dashboard is not installed or activated', 'bit-integrations'), | ||
| ]; | ||
| } |
There was a problem hiding this comment.
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'),
];
}There was a problem hiding this comment.
Fixed — added a guard right after the class_exists check:
if (empty($this->_integrationDetails)) {
return [
'success' => false,
'message' => __('Integration details are missing', 'bit-integrations'),
];
}| bitsFetch({}, 'main_wp_authorize').then(result => { | ||
| if (result?.success) { | ||
| setIsAuthorized(true) | ||
| setSnackbar({ | ||
| show: true, | ||
| msg: __('Connected with MainWP Dashboard Successfully', 'bit-integrations') | ||
| }) | ||
| } | ||
| setIsLoading(false) | ||
| setShowAuthMsg(true) | ||
| }) |
There was a problem hiding this comment.
The bitsFetch promise does not have a .catch() block. If the network request fails or the server returns an error, the promise will reject, and setIsLoading(false) will never be called, leaving the UI stuck in a permanent loading state. Adding a .catch() block ensures the loading state is properly reset.
bitsFetch({}, 'main_wp_authorize')
.then(result => {
if (result?.success) {
setIsAuthorized(true)
setSnackbar({
show: true,
msg: __('Connected with MainWP Dashboard Successfully', 'bit-integrations')
})
}
setIsLoading(false)
setShowAuthMsg(true)
})
.catch(() => {
setIsLoading(false)
setShowAuthMsg(true)
})There was a problem hiding this comment.
Fixed — added a .catch() so the loading/auth-message state resets on a rejected request:
bitsFetch({}, 'main_wp_authorize')
.then(result => {
if (result?.success) { ... }
setIsLoading(false)
setShowAuthMsg(true)
})
.catch(() => {
setIsLoading(false)
setShowAuthMsg(true)
})- Cast site entries to array in refreshSites (get_sites returns stdClass) - Make refreshSites static (no $this usage; matches mainWPAuthorize) - Guard empty integrationDetails before accessing properties - Normalize WP_Error responses before array access - Add .catch() to auth bitsFetch so loading state resets on failure
🔍 WordPress Plugin Check Report
📊 Report
|
| 📍 Line | 🔖 Check | 💬 Message |
|---|---|---|
0 |
mismatched_plugin_name | Plugin name "Bit integrations - Form Integration, Webhook, Spreadsheets, CRM, LMS & Email Automation" is different from the name declared in plugin header "Bit Integrations". |
🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check
Description
Adds MainWP as a new action integration. Bit Integrations flows can now drive a MainWP Dashboard — syncing child sites and managing posts, plugins, and users across the network. The free plugin ships the full integration UI and backend scaffolding; the eight operations themselves execute via Pro hooks.
Motivation & Context
MainWP is a popular self-hosted dashboard for managing many WordPress sites from one place. Wiring it in as an action lets users trigger cross-site management (sync, post CRUD, plugin toggling, user creation) from any Bit Integrations trigger, closing a long-standing request for network-level automation.
Type of Change
Key Changes
Backend (Actions/MainWP)
MainWPController—isExists()guards on the MainWP Dashboard plugin,mainWPAuthorize()validates availability,refreshSites()returns the managed-site list for the UI, andexecute()delegates toRecordApiHelper.RecordApiHelper— maps trigger fields to action fields, appliespost_type/post_statusutilities for post actions, and dispatches one of eight operations (sync_site,sync_all_sites,create_post,update_post,delete_post,activate_plugin,deactivate_plugin,create_user) to Pro viaHooks::apply, then logs success/error throughLogHandler.Routes.php—main_wp_authorizeandrefresh_main_wp_sitesAJAX endpoints.Core/Util/AllTriggersName.php— registers MainWP for the integration list.Frontend (AllIntegrations/MainWP)
MainWP.jsx,MainWPAuthorization.jsx,MainWPIntegLayout.jsx,MainWPFieldMap.jsx,EditMainWP.jsx) and shared helpers (MainWPCommonFunc.js,staticData.js) covering auth, site/action selection, post-type & status utilities, and field mapping.mainWP.webp).NewInteg.jsx,EditInteg.jsx,IntegInfo.jsx,SelectAction.jsx, andwebhookIntegrations.jsto surface MainWP in the integration picker and routing.Checklist
Changelog