feat(integrations): add WSMS (WP SMS) integration#183
Conversation
- 5 wp_hook triggers: SMS Sent, OTP Generated, Subscriber Added, Subscriber Verified, Number Unsubscribed (custom_form_submission, logic in Pro) - 7 actions (all Pro): Send SMS; Add/Update/Delete Subscriber; Add/Update/Delete Group. Update Subscriber is identified by mobile number and Update/Delete Group by group_id via the field map; subscriber actions expose group + status dropdowns - Free fires bit_integrations_wsms_* filters and serves group/status dropdown data; registered in AllTriggersName, the New/Edit/Info integration switches, SelectAction, plus the integration icon
…ntegrations Add B2BKing, FormyChat, GiveWp, SenseiLMS, SureDash, WpDataTables, WpErp and Wsms to the customFormIntegrations list. These Pro triggers declare type custom_form_submission but were absent from the frontend list, so they were not handled as custom-form integrations in the UI.
- WsmsFieldMap: compare i < requiredFlds.length (was missing .length, so the locked required-field branch never ran) and bind the select value to the field key, not its label - WsmsIntegLayout: clear groupId/status when the action changes so a previous action's dropdown selection no longer leaks into the next one - statuses are now a static frontend list (wsmsStatuses) instead of a backend AJAX round-trip for two constants — removes the refreshStatuses route/method and the loading-state race with refreshGroups
There was a problem hiding this comment.
Code Review
This pull request introduces a new integration for WSMS (WP SMS), adding backend controllers, routes, and helpers to handle actions like sending SMS and managing subscribers/groups, alongside frontend React components for authorization and field mapping. The review feedback suggests declaring helper methods as static where appropriate, correcting a typo in a translation string, adding error handling to the authorization fetch request to prevent stuck loading states, and safely rendering multi-select options using a ternary operator instead of logical AND.
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.
| return $response; | ||
| } | ||
|
|
||
| private function generateReqDataFromFieldMap($fieldMap, $fieldValues) |
There was a problem hiding this comment.
The method generateReqDataFromFieldMap is called statically on line 47 (static::generateReqDataFromFieldMap) but is declared as an instance method. Since it does not access any instance properties ($this), it should be declared as static to adhere to PHP best practices and avoid potential warnings.
private static function generateReqDataFromFieldMap($fieldMap, $fieldValues)References
- 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.
Done — declared generateReqDataFromFieldMap as static (it uses no instance state and is already called via static::).
| wp_send_json_success(true); | ||
| } | ||
|
|
||
| public function refreshGroups() |
There was a problem hiding this comment.
The method refreshGroups does not access any instance properties ($this) and is registered as a static callback in Routes.php. It should be declared as static to adhere to the project's coding standards.
public static function refreshGroups()References
- 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.
Done — declared refreshGroups as static (no $this usage; registered as a static route callback, consistent with wsmsAuthorize).
| $defaultResponse = [ | ||
| 'success' => false, | ||
| // translators: %s: Plugin name | ||
| 'message' => wp_sprintf(__('%s plugin is not installed or activate', 'bit-integrations'), 'Bit Integrations Pro') |
There was a problem hiding this comment.
Done — fixed the typo to "activated".
| const authorizeHandler = () => { | ||
| setIsLoading('auth') | ||
| bitsFetch({}, 'wsms_authorize').then(result => { | ||
| if (result?.success) { | ||
| setIsAuthorized(true) | ||
| setSnackbar({ | ||
| show: true, | ||
| msg: __('Connected with WSMS (WP SMS) Successfully', 'bit-integrations') | ||
| }) | ||
| } | ||
| setIsLoading(false) | ||
| setShowAuthMsg(true) | ||
| }) | ||
| } |
There was a problem hiding this comment.
If the bitsFetch request fails or encounters a network error, the promise will reject and the .then block will not execute. This leaves the loading state (isLoading) stuck as 'auth'. Adding a .catch block ensures that the loading state is cleared and the error message is shown.
const authorizeHandler = () => {
setIsLoading('auth')
bitsFetch({}, 'wsms_authorize')
.then(result => {
if (result?.success) {
setIsAuthorized(true)
setSnackbar({
show: true,
msg: __('Connected with WSMS (WP SMS) Successfully', 'bit-integrations')
})
}
setIsLoading(false)
setShowAuthMsg(true)
})
.catch(() => {
setIsLoading(false)
setShowAuthMsg(true)
})
}There was a problem hiding this comment.
Done — added a .catch so the loading state clears and the auth message shows on a failed/rejected request.
.catch(() => {
setIsLoading(false)
setShowAuthMsg(true)
})| options={ | ||
| wsmsConf?.allGroups && | ||
| Array.isArray(wsmsConf.allGroups) && | ||
| wsmsConf.allGroups.map(group => ({ | ||
| label: group.label, | ||
| value: group.value?.toString() | ||
| })) | ||
| } |
There was a problem hiding this comment.
Using logical AND (&&) for conditional rendering or prop assignment can result in false or undefined being passed to the options prop of MultiSelect if allGroups is not set or is not an array. It is safer and more robust to use a ternary operator and default to an empty array [] to prevent potential component rendering issues.
| options={ | |
| wsmsConf?.allGroups && | |
| Array.isArray(wsmsConf.allGroups) && | |
| wsmsConf.allGroups.map(group => ({ | |
| label: group.label, | |
| value: group.value?.toString() | |
| })) | |
| } | |
| options={ | |
| Array.isArray(wsmsConf?.allGroups) | |
| ? wsmsConf.allGroups.map(group => ({ | |
| label: group.label, | |
| value: group.value?.toString() | |
| })) | |
| : [] | |
| } |
There was a problem hiding this comment.
Done — switched the options to a ternary that defaults to [] when allGroups is unset/not an array.
🔍 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
- RecordApiHelper: declare generateReqDataFromFieldMap static (no instance state, already called via static::); fix "activate" -> "activated" typo - WsmsController: declare refreshGroups static (no $this, static route callback) - WsmsAuthorization: add a .catch so loading clears and the auth message shows on a failed/rejected request - WsmsIntegLayout: default the group options to [] via a ternary instead of &&
Description
Adds the WSMS (formerly WP SMS) integration to Bit Integrations — 5
wp_hooktriggers and 7 write actions for sending SMS/MMS and managing subscribers and groups. The Free plugin ships the action UI and fires thebit_integrations_wsms_*hook bridge; the actual WP SMS work lives in the Pro add-on.Motivation & Context
WSMS is a widely-used WordPress SMS/MMS plugin but had no Bit Integrations support. This lets flows send SMS/MMS and react to WSMS subscriber/group events.
Type of Change
Key Changes
Integrations (Backend)
Actions/Wsms/—WsmsController(authorize + group dropdown data),RecordApiHelper(dispatches the 7 actions throughbit_integrations_wsms_*filters),RoutesWsmsentry inAllTriggersNameFrontend
SelectAction, thecustom_form_submissiontrigger list, and the integration iconFixes (code review)
requiredFlds.lengthand bind to the field key (the locked required-field branch never ran before)groupId/statusleaking across actions by clearing them when the action changesChecklist
Changelog