Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@mostafasoufi
Copy link
Member

@mostafasoufi mostafasoufi commented Jan 22, 2026

Summary

This PR includes hardening improvements, bug fixes, and new features.

Changes

Hardening

  • Escape output and validate input in Outbox list table

Bug Fixes

  • Fix CF7 "Send to form" field not extracting phone number
  • Fix SMS.to gateway GetCredit() - handle decoded JSON response
  • Fix: Separate license conditions for header and notice display
  • fix: resolve license status image size issue in header
  • fix: fix accessibility issues
  • fix(privacy): resolve subscriber deletion not working

Features

  • Add workflow to notify gateway registry on changes
  • feat: add %product_name% variable to WooCommerce product notifications

Dependencies

  • build(deps): bump qs and express

Documentation

  • doc: tweak update on readme.txt
  • doc: update readme

Refactor

  • fix: revert getTitle() to use get_title() method
  • refactor: replace get_title() with get_name()

Test plan

  • Test Outbox page displays correctly with various sender values
  • Test CF7 form phone number extraction
  • Test SMS.to gateway credit check
  • Test WooCommerce product notifications with %product_name%
  • Verify license notice displays correctly

Summary by CodeRabbit

  • New Features

    • Added new upgrade notice functionality for users without all-in-one licenses.
    • Added support for product name token in WooCommerce notifications.
  • Bug Fixes

    • Fixed privacy data deletion to correctly handle phone numbers and database queries.
    • Improved security by preventing SQL injection in admin outbox.
    • Enhanced output escaping to prevent potential XSS vulnerabilities.
    • Fixed Contact Form 7 integration phone number resolution.
  • Accessibility

    • Added label associations for form inputs in privacy page.
  • Style

    • Updated privacy page styling with improved button colors and input field appearance.

✏️ Tip: You can customize this high-level summary in your review settings.

mohammadyari-dev and others added 21 commits December 2, 2025 16:56
- Remove integer cast from mobile number that truncated phone numbers
- Fix malformed format specifier in wpdb->delete (' % d' -> '%d')
- Use prepareMobileNumberQuery with IN clause for flexible phone matching

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
fix(privacy): resolve subscriber deletion not working
…image

fix: resolve license status image size issue in header
Replaces 'wp_sms_enable_upgrade_to_bundle' filter with 'wp_sms_enable_upgrade_notice' in settings and send-sms templates. Adds new filter and logic in LicenseManagementManager to show upgrade notices only when user lacks both All-in-One and Pro licenses, while keeping the old filter for header notices when user lacks All-in-One license.
…ce-conditions

Fix: Separate license conditions for header and notice display
…ailability-notification

refactor: replace get_title() with get_name()
Bumps [qs](https://github.com/ljharb/qs) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `qs` from 6.13.0 to 6.14.1
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](ljharb/qs@v6.13.0...v6.14.1)

Updates `express` from 4.21.0 to 4.22.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/v4.22.1/History.md)
- [Commits](expressjs/express@4.21.0...v4.22.1)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.1
  dependency-type: indirect
- dependency-name: express
  dependency-version: 4.22.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
The request() method now returns decoded JSON directly (stdClass/array)
instead of raw WordPress HTTP response. Updated GetCredit() to work
with this new return format.

Fixes: PHP Fatal error: Cannot use object of type stdClass as array
This workflow triggers a sync with the gateway registry on push events to specified branches, on release publication, and via manual dispatch.
…fcbbcd8

build(deps): bump qs and express
The phone field placeholder (e.g., %your-phone%) was being passed directly
to the SMS send function without being replaced with the actual form value.

Now properly extracts the phone number from the notification variables.
Bumps [lodash-es](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](lodash/lodash@4.17.21...4.17.23)

---
updated-dependencies:
- dependency-name: lodash-es
  dependency-version: 4.17.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](lodash/lodash@4.17.21...4.17.23)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2026

Walkthrough

This PR encompasses multiple improvements: adds a GitHub Actions workflow for gateway registry notifications, fixes privacy data deletion by refining mobile number queries and SQL placeholders, enhances security in admin outbox with SQL sanitization and output escaping, updates license management with a new upgrade notice filter, normalizes CSS formatting, and adds WooCommerce product name token support.

Changes

Cohort / File(s) Summary
CI/CD & Automation
.github/workflows/notify-gateway-registry.yml
New GitHub Actions workflow to dispatch repository events to gateway-registry on push to includes/gateways/**, releases, or manual trigger.
Configuration & Documentation
CHANGELOG.md, readme.txt
Added privacy data deletion fix entry; updated feature list and removed outdated changelog versions.
CSS Styling
assets/css/front-styles.css, assets/css/mail.css, assets/src/admin/modules/_postbox.scss, assets/src/admin/modules/_privacy-page.scss
Font and color formatting normalization; added icon sizing constraints; updated privacy page button colors, border-radius, and focus styles.
Admin Templates
includes/templates/admin/privacy-page.php, includes/templates/admin/send-sms.php
Added label-input associations for accessibility; updated upgrade notice hook reference.
Admin Outbox & Settings
includes/admin/outbox/class-wpsms-outbox.php, includes/admin/settings/class-wpsms-settings.php
Added SQL column validation and safe ordering normalization to prevent injection; added output escaping; changed upgrade notice hook name.
Gateway & Integration Logic
includes/gateways/class-wpsms-gateway-smsto.php, includes/class-wpsms-integrations.php
Simplified SMS.to API response handling with added error fallback; resolved CF7 phone field using notification variables map.
License Management
src/Admin/LicenseManagement/LicenseManagementManager.php
Added new showUpgradeNotice() method; registered new wp_sms_enable_upgrade_notice filter; refined license visibility logic.
Privacy & Data Handling
src/Controller/PrivacyDataAjax.php
Refactored mobile number queries using prepared IN clauses; fixed placeholder syntax for delete operations.
WooCommerce Integration
src/Notification/Handler/WooCommerceProductNotification.php
Added %product_name% token support via new getName() method.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Release: Hardening, bug fixes and improvements' directly aligns with the main changes across the PR, which include security hardening (output escaping, input validation), multiple bug fixes (CF7 field extraction, SMS.to gateway response handling, privacy deletion, accessibility), and feature additions (gateway registry workflow, WooCommerce variables).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/Controller/PrivacyDataAjax.php (1)

65-68: Potential inconsistency: WordPress user query may not match phone variations.

The get_users() call passes an array from prepareMobileNumberQuery() to meta_value, but without meta_compare => 'IN', WordPress defaults to = comparison which won't correctly match any of the phone variations in the array.

🔧 Proposed fix to use IN comparison for user meta query
 $get_user = get_users([
     'meta_key'   => Helper::getUserMobileFieldName(), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
-    'meta_value' => Helper::prepareMobileNumberQuery($mobile) // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
+    'meta_value' => Helper::prepareMobileNumberQuery($mobile), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
+    'meta_compare' => 'IN' // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
 ]);
includes/admin/settings/class-wpsms-settings.php (1)

1761-1782: Preserve backward compatibility for existing filters.

Line 1761 switches to wp_sms_enable_upgrade_notice, which can unintentionally re-enable notices for sites that previously disabled them via wp_sms_enable_upgrade_to_bundle. Consider chaining the legacy filter as a fallback to avoid regressions.

🔧 Suggested backward‑compat change
-                            if (apply_filters('wp_sms_enable_upgrade_notice', true)) :
+                            $showUpgradeNotice = apply_filters('wp_sms_enable_upgrade_notice', true);
+                            $showUpgradeNotice = apply_filters('wp_sms_enable_upgrade_to_bundle', $showUpgradeNotice);
+                            if ($showUpgradeNotice) :
🤖 Fix all issues with AI agents
In `@includes/admin/outbox/class-wpsms-outbox.php`:
- Around line 348-356: Normalize and validate incoming sort params before use:
run wp_unslash() and sanitize_key() on $_REQUEST['orderby'] and
sanitize_text_field/strtoupper on $_REQUEST['order'], then check the sanitized
orderby against the existing $allowed_columns array and only use that sanitized
variable in the SQL ORDER BY string (replace direct $_REQUEST['orderby'] usage)
and in the usort_reorder comparator; also reuse the same $allowed_columns
allowlist inside usort_reorder (or pass the validated key into it) so sorting
never operates on an invalid key and you have a consistent, notice-free ordering
behavior.

In `@includes/gateways/class-wpsms-gateway-smsto.php`:
- Around line 178-195: The balance handling assumes $responseObject is an
object, but request() may return an array; update the logic in the method in
class WPSMS_Gateway_SMSTO (the block that currently sets $responseObject =
$response and then checks $responseObject->balance / $responseObject->message)
to handle both arrays and objects: detect is_array($responseObject) and check
['balance'] and ['message'] keys (or normalize $responseObject to an object via
(object)$response), convert the balance to a float and round it, and return
WP_Error('account-credit', ...) when message exists for either type so array
responses no longer cause a false "Invalid response" error.
♻️ Duplicate comments (1)
includes/templates/admin/send-sms.php (1)

287-296: Same filter compatibility concern as settings page.

Please see the note in includes/admin/settings/class-wpsms-settings.php (Line 1761) about chaining the legacy wp_sms_enable_upgrade_to_bundle filter.

🧹 Nitpick comments (2)
.github/workflows/notify-gateway-registry.yml (2)

5-16: Set explicit minimal permissions for the workflow.
This job uses a PAT and doesn’t need the default GITHUB_TOKEN scopes. Explicitly setting permissions reduces risk.

♻️ Suggested change
 on:
   push:
     branches: [master, development]
     paths:
       - 'includes/gateways/**'
   release:
     types: [published]
   workflow_dispatch:
+
+permissions: {}

19-25: Pin third-party action to a commit SHA.
Using a floating tag (@v3) increases supply-chain risk. Pin to a specific commit SHA from the upstream release.

♻️ Example update
-        uses: peter-evans/repository-dispatch@v3
+        uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0

Comment on lines +348 to +356
// Allowed columns for ordering to prevent SQL injection
$allowed_columns = array('ID', 'sender', 'date', 'message', 'recipient', 'media', 'status');
$allowed_orders = array('asc', 'desc');

if (isset($_REQUEST['orderby']) && in_array($_REQUEST['orderby'], $allowed_columns, true)) {
$order_direction = isset($_REQUEST['order']) && in_array(strtolower($_REQUEST['order']), $allowed_orders, true)
? strtoupper($_REQUEST['order'])
: 'ASC';
$orderby .= "ORDER BY {$this->tb_prefix}sms_send.{$_REQUEST['orderby']} {$order_direction}";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Normalize request params and keep sorting consistent with usort_reorder.
Good allowlisting, but raw $_REQUEST values are still used and usort_reorder can still sort on an invalid key, causing notices or inconsistent ordering when orderby is invalid. Consider normalizing with wp_unslash() + sanitize_key() and reusing the same allowlist in usort_reorder.

🧩 Proposed tweak
-        if (isset($_REQUEST['orderby']) && in_array($_REQUEST['orderby'], $allowed_columns, true)) {
-            $order_direction = isset($_REQUEST['order']) && in_array(strtolower($_REQUEST['order']), $allowed_orders, true)
-                ? strtoupper($_REQUEST['order'])
+        $orderby_key = isset($_REQUEST['orderby']) ? sanitize_key(wp_unslash($_REQUEST['orderby'])) : '';
+        $order_key   = isset($_REQUEST['order']) ? strtolower(sanitize_key(wp_unslash($_REQUEST['order']))) : '';
+
+        if ($orderby_key && in_array($orderby_key, $allowed_columns, true)) {
+            $order_direction = in_array($order_key, $allowed_orders, true)
+                ? strtoupper($order_key)
                 : 'ASC';
-            $orderby .= "ORDER BY {$this->tb_prefix}sms_send.{$_REQUEST['orderby']} {$order_direction}";
+            $orderby .= "ORDER BY {$this->tb_prefix}sms_send.{$orderby_key} {$order_direction}";
         } else {
             $orderby .= "ORDER BY date DESC";
         }
-        $orderby = (!empty($_REQUEST['orderby'])) ? sanitize_text_field($_REQUEST['orderby']) : 'date';
-        $order   = (!empty($_REQUEST['order'])) ? sanitize_text_field($_REQUEST['order']) : 'desc';
+        $allowed_columns = array('ID', 'sender', 'date', 'message', 'recipient', 'media', 'status');
+        $allowed_orders  = array('asc', 'desc');
+        $orderby = isset($_REQUEST['orderby']) ? sanitize_key(wp_unslash($_REQUEST['orderby'])) : 'date';
+        if (!in_array($orderby, $allowed_columns, true)) {
+            $orderby = 'date';
+        }
+        $order = isset($_REQUEST['order']) ? strtolower(sanitize_key(wp_unslash($_REQUEST['order']))) : 'desc';
+        if (!in_array($order, $allowed_orders, true)) {
+            $order = 'desc';
+        }
🤖 Prompt for AI Agents
In `@includes/admin/outbox/class-wpsms-outbox.php` around lines 348 - 356,
Normalize and validate incoming sort params before use: run wp_unslash() and
sanitize_key() on $_REQUEST['orderby'] and sanitize_text_field/strtoupper on
$_REQUEST['order'], then check the sanitized orderby against the existing
$allowed_columns array and only use that sanitized variable in the SQL ORDER BY
string (replace direct $_REQUEST['orderby'] usage) and in the usort_reorder
comparator; also reuse the same $allowed_columns allowlist inside usort_reorder
(or pass the validated key into it) so sorting never operates on an invalid key
and you have a consistent, notice-free ordering behavior.

Comment on lines +178 to +195
/**
* The request() method returns decoded JSON directly (stdClass or array),
* not the raw WordPress HTTP response.
*/
$responseObject = $response;

/*
* Response validity
* Response validity - check if we got a valid response object
*/
if (wp_remote_retrieve_response_code($response) == '200') {

if (isset($responseObject->balance)) {
return round($responseObject->balance, 2);
}
if (isset($responseObject->balance)) {
return round($responseObject->balance, 2);
}

if (isset($responseObject->message)) {
return new \WP_Error('account-credit', $responseObject->message);

} else {
$errorResponse = isset($responseObject->message) ? $responseObject->message : $responseObject;
return new \WP_Error('account-credit', $errorResponse);
}

return new \WP_Error('account-credit', esc_html__('Invalid response from SMS.to API', 'wp-sms'));
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle array responses from request() before dereferencing
The comment says request() can return an array; accessing $responseObject->balance will fail in that case and incorrectly return “Invalid response.” Please handle both arrays and objects.

🛠️ Proposed fix
-        $responseObject = $response;
+        $responseObject = $response;
+
+        $balance = is_array($responseObject)
+            ? ($responseObject['balance'] ?? null)
+            : ($responseObject->balance ?? null);
+
+        if ($balance !== null) {
+            return round((float) $balance, 2);
+        }
-
-        if (isset($responseObject->balance)) {
-            return round($responseObject->balance, 2);
-        }
-
-        if (isset($responseObject->message)) {
-            return new \WP_Error('account-credit', $responseObject->message);
-        }
+        $message = is_array($responseObject)
+            ? ($responseObject['message'] ?? null)
+            : ($responseObject->message ?? null);
+
+        if ($message !== null) {
+            return new \WP_Error('account-credit', $message);
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/**
* The request() method returns decoded JSON directly (stdClass or array),
* not the raw WordPress HTTP response.
*/
$responseObject = $response;
/*
* Response validity
* Response validity - check if we got a valid response object
*/
if (wp_remote_retrieve_response_code($response) == '200') {
if (isset($responseObject->balance)) {
return round($responseObject->balance, 2);
}
if (isset($responseObject->balance)) {
return round($responseObject->balance, 2);
}
if (isset($responseObject->message)) {
return new \WP_Error('account-credit', $responseObject->message);
} else {
$errorResponse = isset($responseObject->message) ? $responseObject->message : $responseObject;
return new \WP_Error('account-credit', $errorResponse);
}
return new \WP_Error('account-credit', esc_html__('Invalid response from SMS.to API', 'wp-sms'));
/**
* The request() method returns decoded JSON directly (stdClass or array),
* not the raw WordPress HTTP response.
*/
$responseObject = $response;
/*
* Response validity - check if we got a valid response object
*/
$balance = is_array($responseObject)
? ($responseObject['balance'] ?? null)
: ($responseObject->balance ?? null);
if ($balance !== null) {
return round((float) $balance, 2);
}
$message = is_array($responseObject)
? ($responseObject['message'] ?? null)
: ($responseObject->message ?? null);
if ($message !== null) {
return new \WP_Error('account-credit', $message);
}
return new \WP_Error('account-credit', esc_html__('Invalid response from SMS.to API', 'wp-sms'));
🤖 Prompt for AI Agents
In `@includes/gateways/class-wpsms-gateway-smsto.php` around lines 178 - 195, The
balance handling assumes $responseObject is an object, but request() may return
an array; update the logic in the method in class WPSMS_Gateway_SMSTO (the block
that currently sets $responseObject = $response and then checks
$responseObject->balance / $responseObject->message) to handle both arrays and
objects: detect is_array($responseObject) and check ['balance'] and ['message']
keys (or normalize $responseObject to an object via (object)$response), convert
the balance to a float and round it, and return WP_Error('account-credit', ...)
when message exists for either type so array responses no longer cause a false
"Invalid response" error.

…ites

- Use site-independent cache key that doesn't vary by home_url()
- Add negative caching for failed requests (1 hour) to prevent API hammering
- Fixes issue where multilingual sites (e.g., /en, /fr) generate different
  cache keys, causing each language variant to make separate API calls
- This was causing 16,000+ daily requests from single sites and filling
  server disk with 88GB of fail2ban logs
- Add multisite support using get_current_blog_id() in cache key
- Reduce negative cache duration from 1 hour to 5 minutes for faster retry
- Add clearProductInfoCache() method for cache invalidation
- Clear cache on successful license validation
- Use PluginHelper::$plugins for add-on list consistency
- Add $customCacheKey parameter to RemoteRequest::execute()
- Simplify ApiCommunicator to delegate caching to RemoteRequest
- Keep negative cache handling for failed requests (5 min)
- Reduces code duplication and improves maintainability
The TransientCacheTrait transforms cache keys via getCacheKey(),
causing mismatch between negative cache (direct transient) and
positive cache (trait method). Fix by using direct get_transient/
set_transient when custom cache key is provided.
Test that custom cache key uses direct transient calls instead of trait
methods to avoid key transformation issues.
…17.23

build(deps): bump lodash from 4.17.21 to 4.17.23
…-4.17.23

build(deps-dev): bump lodash-es from 4.17.21 to 4.17.23
mostafasoufi and others added 3 commits January 27, 2026 12:32
Store generated cache key in variable for reuse instead of calling
generateCacheKey() twice (once for read, once for write).
Fix license API cache to prevent excessive requests on multilingual sites
- Add v7.1.1 changelog entries to CHANGELOG.md and readme.txt
- Update plugin version to 7.1.1
@mostafasoufi mostafasoufi merged commit 01ac792 into master Jan 28, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants