Skip to content

Upd. Integrations. Improve ajax prefilter. - #889

Merged
svedge merged 19 commits into
devfrom
upd-ajax-prefilter
Sep 15, 2026
Merged

svedge merged 19 commits into
devfrom
upd-ajax-prefilter

Conversation

@svedge

@svedge svedge commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

@codecov

codecov Bot commented Sep 13, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 30.56%. Comparing base (f37f12e) to head (c77bb40).
⚠️ Report is 1 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff            @@
##                dev     #889   +/-   ##
=========================================
  Coverage     30.56%   30.56%           
  Complexity     6637     6637           
=========================================
  Files           292      292           
  Lines         26489    26489           
=========================================
  Hits           8097     8097           
  Misses        18392    18392           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Unresolved artifact coverage and payload-handling issues block approval.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Improves CleanTalk’s jQuery AJAX interception with ajaxPrefilter and broader payload handling.

Changes:

  • Replaces ajaxSetup.beforeSend interception.
  • Adds payload normalization and CleanTalk data injection.
  • Updates source, prebuild, and default minified bundle artifacts.
File summaries
File Summary
js/src/public-1-main.js Implements the interceptor. Critical (2 votes): protection and gathering minified variants still lack the new dispatcher. Moderate (3 votes): object serialization ignores traditional. Moderate (1 vote): URLSearchParams receives no CleanTalk fields.
js/prebuild/apbct-public-bundle.js Updated generated default bundle.
js/prebuild/apbct-public-bundle_int-protection.js Updated generated internal-protection bundle.
js/prebuild/apbct-public-bundle_int-protection_gathering.js Updated generated internal gathering bundle.
js/prebuild/apbct-public-bundle_gathering.js Updated generated gathering bundle.
js/prebuild/apbct-public-bundle_full-protection.js Updated generated full-protection bundle.
js/prebuild/apbct-public-bundle_full-protection_gathering.js Updated generated full gathering bundle.
js/prebuild/apbct-public-bundle_ext-protection.js Updated generated external-protection bundle.
js/prebuild/apbct-public-bundle_ext-protection_gathering.js Updated generated external gathering bundle.
js/apbct-public-bundle.min.js Updated default minified bundle.
Review details

Suppressed comments (1)

js/src/public-1-main.js:1341

  • URLSearchParams has forEach, get, and append, so it is recognized by getJQAjaxDataAsString and routed into this branch. However, injectCleantalkDataToJQAjaxFormData only accepts instanceof FormData, returns the URLSearchParams unchanged, and the CleanTalk fields are never added. Handle URLSearchParams separately (for example, inject into its toString() result) or broaden the helper's accepted payload contract.
        if (
            typeof ajaxData === 'object' &&
            ajaxData !== null &&
            typeof ajaxData.append === 'function'
        ) {
            return this.injectCleantalkDataToJQAjaxFormData(sourceSign, ajaxData);
  • Files reviewed: 9/17 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread js/src/public-1-main.js
Comment thread js/src/public-1-main.js Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Critical payload handling and production-bundle/IE11 compatibility issues remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (2)

js/src/public-1-main.js:1177

  • The bundles selected by ApbctJsBundleResolver are the eight js/apbct-public-bundle*.min.js artifacts, but none of those served files contains ajaxPrefilter; this implementation exists only in the source/prebuild copies. As a result, production requests still execute the old jQuery interception and do not receive this change. Rebuild and commit all minified variants from this source (including the _gathering variants).
        if ( typeof jQuery !== 'undefined' && typeof jQuery.ajaxPrefilter === 'function' ) {
            jQuery.ajaxPrefilter(function(options, originalOptions, jqXHR) {
                const handler = new ApbctHandler();
                const sourceSign = handler.searchSignsForJQAjaxInjection(options);
                if (sourceSign.found !== false) {

js/src/public-1-main.js:1207

  • FormData.forEach is not available in IE11, while the bundles are explicitly transpiled for IE11 (gulpfile.js:224). In that browser this call throws and the catch returns an empty string, so every FormData request misses its integration sign and receives no CleanTalk data. Use a compatibility-safe way to inspect the action or retain a FormData-specific fallback.
                ajaxObject.data.forEach(function(value, key) {
  • Files reviewed: 9/17 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread js/src/public-1-main.js Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Address the three moderate payload-handling and nonce-detection issues in the AJAX prefilter.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (1)

js/src/public-1-main.js:1228

  • Returning as soon as an action field exists means a FormData request containing action=user_registration never includes ur_frontend_form_nonce in dataString, so the compound signature at lines 1287-1290 is not detected and no CleanTalk data is injected. Only use the get('action') shortcut when enumeration is unavailable; otherwise serialize all entries (or explicitly include the nonce).
        if ( typeof formData.get === 'function' ) {
            const action = formData.get('action');
            if ( action ) {
                return 'action=' + action;
  • Files reviewed: 9/17 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread js/src/public-1-main.js Outdated
Comment thread js/src/public-1-main.js

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Four moderate issues affect FormData handling, array injection, and IE11 compatibility.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (3)

js/src/public-1-main.js:1481

  • The bundles are transpiled for IE11, but Babel does not polyfill Object.assign; IE11 reaches this branch for a recognized processData: false plain-object request and throws before the AJAX call is sent. Use an IE11-compatible shallow-copy implementation or provide a guaranteed polyfill.
        const result = Object.prototype.toString.call(ajaxData) === '[object Array]' ?
            ajaxData.slice() :
            Object.assign({}, ajaxData);

js/src/public-1-main.js:1237

  • IE11's FormData implementation has neither forEach nor get, so this fallback returns an empty string for every FormData payload. The new prefilter therefore never recognizes any supported action or injects fields for FormData requests in the IE11 target; use an IE11-compatible signal (for example, request metadata/URL) or remove the unsupported fallback claim.
        if ( typeof formData.get !== 'function' ) {
            return '';

js/src/public-1-main.js:1484

  • When ajaxData is an array, these assignments add named properties such as data[ct_no_cookie_hidden_field], not array elements. Standard serialization (JSON.stringify or jQuery.param) ignores those properties, so a processData: false array request is sent without the injected fields despite this branch claiming to preserve/inject it. Handle the integration's expected array-entry format explicitly or exclude arrays from this path.
        const pairs = this.getCleantalkJQAjaxFieldPairs(sourceSign);
        for ( let i = 0; i < pairs.length; i++ ) {
            result[pairs[i][0]] = pairs[i][1];
  • Files reviewed: 9/17 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread js/src/public-1-main.js Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Four moderate issues remain in AJAX payload handling and should be addressed before approval.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (3)

js/src/public-1-main.js:1395

  • With processData: false, this passes the caller-owned URLSearchParams instance directly to a helper that appends CleanTalk fields. Reusing the same params object for a later AJAX call permanently retains the previous token/browser-state values, so requests accumulate duplicate or stale fields. Clone the params before injecting, as the plain-object path already does.
            if ( keepOriginalType ) {
                return this.injectCleantalkDataToJQAjaxKeyValue(sourceSign, ajaxData);

js/src/public-1-main.js:1391

  • The URL-only WooCommerce path is recognized in searchSignsForJQAjaxInjection, but when such a request has no data, this dispatcher falls through and returns undefined unchanged. The prefilter therefore injects nothing into a valid wc-ajax=add_to_cart request with an omitted body; treat null/undefined as an empty string before calling the string injector, then regenerate the tracked bundles.
        if ( typeof ajaxData === 'string' ) {
            return this.injectCleantalkDataToJQAjaxString(sourceSign, ajaxData);

js/src/public-1-main.js:1207

  • Because this prefilter runs for every jQuery AJAX call, these branches now fully traverse/serialize every plain object, array, and (where supported) FormData just to search for a sign, including unrelated requests; recognized object payloads are then serialized again in injectCleantalkDataToJQAjax. On pages with frequent or large AJAX payloads this adds avoidable O(payload) work to the global hook. Reuse the serialized representation for injection or add a cheaper candidate check before doing full serialization.
        if ( typeof ajaxObject.data === 'string' ) {
            return ajaxObject.data;
        }
        if ( typeof URLSearchParams !== 'undefined' && ajaxObject.data instanceof URLSearchParams ) {
            return ajaxObject.data.toString();
        }
        if ( typeof FormData !== 'undefined' && ajaxObject.data instanceof FormData ) {
            return this.getFormDataAsString(ajaxObject.data);
        }
        if ( this.isJQAjaxPlainObjectOrArray(ajaxObject.data) ) {
  • Files reviewed: 9/17 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread js/src/public-1-main.js Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Three moderate issues remain in FormData and array payload detection and injection.

Review details

Suppressed comments (3)

js/src/public-1-main.js:1252

  • When FormData implements both get() and forEach() (the normal modern-browser case), this early branch returns after probing only action, ur_frontend_form_nonce, and twt_cc_signup. Supported submissions such as action=mailpoet or action=nf_ajax_submit are therefore not recognized, so their CleanTalk and visible-field data is never injected. Use the enumeration path whenever it is available and keep the get() probes only as a fallback.
        if ( typeof formData.get === 'function' ) {
            const keysToProbe = [
                'action',
                'ur_frontend_form_nonce',
                'twt_cc_signup',

js/src/public-1-main.js:1238

  • Arrays are explicitly supported by isJQAjaxPlainObjectOrArray, but this candidate check only inspects data.action and an own twt_cc_signup property. A normal $(form).serializeArray() payload is an array of {name: 'action', value: '...'} entries, so it is rejected before jQuery.param() and no supported integration is detected; the previous prefilter saw the serialized action. Scan array entries for named fields before returning false.
        if ( typeof data.action === 'string' && data.action !== '' ) {
            return true;
        }
        return Object.prototype.hasOwnProperty.call(data, 'twt_cc_signup');

js/src/public-1-main.js:1437

  • The URL-only WooCommerce sign is also accepted for array payloads, but this branch returns every array unchanged when processData: false. A wc-ajax=add_to_cart request using an array body therefore passes through the new prefilter without any CleanTalk fields, even though arrays are explicitly treated as supported above. Handle this array case explicitly (or reject it before marking the request as injectable) instead of silently skipping the injection.
        if ( this.isJQAjaxPlainObjectOrArray(ajaxData) ) {
            if ( keepOriginalType ) {
                if ( Object.prototype.toString.call(ajaxData) === '[object Array]' ) {
                    return ajaxData;
  • Files reviewed: 9/17 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Five moderate review findings remain in the AJAX prefilter and payload-handling logic.

Review details

Suppressed comments (5)

js/src/public-1-main.js:1213

  • For every plain-object request whose payload has any action, this call runs jQuery.param() once for sign detection, then the dispatcher serializes the same payload again before returning it. Unsupported WordPress AJAX actions therefore pay for two full serializations, and large/nested payloads can incur a noticeable prefilter cost. Detect the supported signs from the structured payload or carry the serialized result into injection so the payload is serialized only once.
            try {
                if ( typeof jQuery !== 'undefined' && typeof jQuery.param === 'function' ) {
                    return jQuery.param(ajaxObject.data, ajaxObject.traditional);

js/src/public-1-main.js:1271

  • An empty $(form).serializeArray() is a valid payload. For a URL-only wc-ajax=add_to_cart request with processData: false, URL sign detection succeeds, but this predicate rejects [] and the dispatcher returns it unchanged, so no CleanTalk fields are sent. Treat an empty array as a serializeArray payload.
        if ( Object.prototype.toString.call(data) !== '[object Array]' || data.length === 0 ) {

js/src/public-1-main.js:1475

  • This rejects valid FormData instances created by another window (and common FormData polyfills), because instanceof FormData is realm-specific. The prefilter then returns the payload unchanged, whereas the previous append duck check handled it; use a cross-realm-safe check here and in the existing FormData injector.
        if ( typeof FormData !== 'undefined' && ajaxData instanceof FormData ) {
            return this.injectCleantalkDataToJQAjaxFormData(
                sourceSign,
                this.cloneFormData(ajaxData),
            );

js/src/public-1-main.js:1259

  • For serializeArray data, this gate checks twt_cc_signup only in entry.name. A serialized form commonly carries the form identifier as a value (for example {name: 'form_id', value: 'twt_cc_signup'}), which the old substring search recognized; this new gate returns false and prevents sign detection and injection. Check the entry value as well as its name.
            if ( entry.name === 'twt_cc_signup' ) {
                return true;

js/src/public-1-main.js:1241

  • This candidate gate no longer searches the serialized object for twt_cc_signup; it only accepts that marker when it is an own property name. A valid form payload such as {form_id: 'twt_cc_signup'} is therefore rejected before jQuery.param() and searchSignsForJQAjaxInjection() runs, so the request receives no CleanTalk fields. The previous string-based path matched this marker anywhere (and the form handling identifies it as an id), so include string-valued properties when deciding whether to serialize the object.
        if ( typeof data.action === 'string' && data.action !== '' ) {
            return true;
        }
        return Object.prototype.hasOwnProperty.call(data, 'twt_cc_signup');
  • Files reviewed: 9/17 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The moderate URLSearchParams handling issue must be corrected before approval.

Review details

Suppressed comments (8)

js/prebuild/apbct-public-bundle.js:4338

  • jQuery skips its data serialization when contentType: false, even if processData remains true. A recognized URLSearchParams request using that option is converted to a plain string below, so XHR no longer applies the URLSearchParams form encoding/content type and PHP-style integrations can receive an empty $_POST; treat contentType: false as another reason to retain and augment the original payload type.
        const keepOriginalType = ajaxOptions && ajaxOptions.processData === false;

js/prebuild/apbct-public-bundle_ext-protection.js:4338

  • jQuery skips its data serialization when contentType: false, even if processData remains true. A recognized URLSearchParams request using that option is converted to a plain string below, so XHR no longer applies the URLSearchParams form encoding/content type and PHP-style integrations can receive an empty $_POST; treat contentType: false as another reason to retain and augment the original payload type.
        const keepOriginalType = ajaxOptions && ajaxOptions.processData === false;

js/prebuild/apbct-public-bundle_full-protection.js:4338

  • jQuery skips its data serialization when contentType: false, even if processData remains true. A recognized URLSearchParams request using that option is converted to a plain string below, so XHR no longer applies the URLSearchParams form encoding/content type and PHP-style integrations can receive an empty $_POST; treat contentType: false as another reason to retain and augment the original payload type.
        const keepOriginalType = ajaxOptions && ajaxOptions.processData === false;

js/prebuild/apbct-public-bundle_full-protection_gathering.js:4338

  • jQuery skips its data serialization when contentType: false, even if processData remains true. A recognized URLSearchParams request using that option is converted to a plain string below, so XHR no longer applies the URLSearchParams form encoding/content type and PHP-style integrations can receive an empty $_POST; treat contentType: false as another reason to retain and augment the original payload type.
        const keepOriginalType = ajaxOptions && ajaxOptions.processData === false;

js/prebuild/apbct-public-bundle_gathering.js:4338

  • jQuery skips its data serialization when contentType: false, even if processData remains true. A recognized URLSearchParams request using that option is converted to a plain string below, so XHR no longer applies the URLSearchParams form encoding/content type and PHP-style integrations can receive an empty $_POST; treat contentType: false as another reason to retain and augment the original payload type.
        const keepOriginalType = ajaxOptions && ajaxOptions.processData === false;

js/prebuild/apbct-public-bundle_int-protection.js:4338

  • jQuery skips its data serialization when contentType: false, even if processData remains true. A recognized URLSearchParams request using that option is converted to a plain string below, so XHR no longer applies the URLSearchParams form encoding/content type and PHP-style integrations can receive an empty $_POST; treat contentType: false as another reason to retain and augment the original payload type.
        const keepOriginalType = ajaxOptions && ajaxOptions.processData === false;

js/prebuild/apbct-public-bundle_int-protection_gathering.js:4338

  • jQuery skips its data serialization when contentType: false, even if processData remains true. A recognized URLSearchParams request using that option is converted to a plain string below, so XHR no longer applies the URLSearchParams form encoding/content type and PHP-style integrations can receive an empty $_POST; treat contentType: false as another reason to retain and augment the original payload type.
        const keepOriginalType = ajaxOptions && ajaxOptions.processData === false;

js/src/public-1-main.js:1591

  • jQuery skips its data serialization when contentType: false, even if processData remains true. A recognized URLSearchParams request using that option is converted to a plain string below, so XHR no longer applies the URLSearchParams form encoding/content type and PHP-style integrations can receive an empty $_POST; treat contentType: false as another reason to retain and augment the original payload type.
        const keepOriginalType = ajaxOptions && ajaxOptions.processData === false;
  • Files reviewed: 9/17 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

URLSearchParams payloads with contentType: false can be emptied by jQuery while processData remains enabled.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 9/17 changed files
  • Comments generated: 9
  • Review effort level: Lite

Comment thread js/prebuild/apbct-public-bundle.js Outdated
Comment thread js/prebuild/apbct-public-bundle_ext-protection.js Outdated
Comment thread js/prebuild/apbct-public-bundle_ext-protection_gathering.js Outdated
Comment thread js/prebuild/apbct-public-bundle_full-protection.js Outdated
Comment thread js/prebuild/apbct-public-bundle_full-protection_gathering.js Outdated
Comment thread js/prebuild/apbct-public-bundle_gathering.js Outdated
Comment thread js/prebuild/apbct-public-bundle_int-protection.js Outdated
Comment thread js/prebuild/apbct-public-bundle_int-protection_gathering.js Outdated
Comment thread js/src/public-1-main.js

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Normalize nested and bracketed payload fields so valid AJAX requests receive CleanTalk data.

Review details

Suppressed comments (11)

js/prebuild/apbct-public-bundle.js:3992

  • These branches only recognize top-level action/nonce fields. A valid WordPress-style payload using data[action]=pafe_ajax_form_builder is either ignored by getStructuredJQAjaxSignString or encoded by URLSearchParams.toString() as data%5Baction%5D=..., so the later action=... checks never match and no CleanTalk fields are injected. FormData already explicitly unwraps this same data[...] shape; normalize equivalent object/URLSearchParams keys before sign matching.
        if ( this.isJQAjaxPlainObjectOrArray(ajaxObject.data) ) {
            return this.getStructuredJQAjaxSignString(ajaxObject.data);
        }
        if ( this.isJQAjaxURLSearchParams(ajaxObject.data) ) {
            return ajaxObject.data.toString();

js/prebuild/apbct-public-bundle_ext-protection.js:3992

  • These branches only recognize top-level action/nonce fields. A valid WordPress-style payload using data[action]=pafe_ajax_form_builder is either ignored by getStructuredJQAjaxSignString or encoded by URLSearchParams.toString() as data%5Baction%5D=..., so the later action=... checks never match and no CleanTalk fields are injected. FormData already explicitly unwraps this same data[...] shape; normalize equivalent object/URLSearchParams keys before sign matching.
        if ( this.isJQAjaxPlainObjectOrArray(ajaxObject.data) ) {
            return this.getStructuredJQAjaxSignString(ajaxObject.data);
        }
        if ( this.isJQAjaxURLSearchParams(ajaxObject.data) ) {
            return ajaxObject.data.toString();

js/prebuild/apbct-public-bundle_ext-protection_gathering.js:3992

  • These branches only recognize top-level action/nonce fields. A valid WordPress-style payload using data[action]=pafe_ajax_form_builder is either ignored by getStructuredJQAjaxSignString or encoded by URLSearchParams.toString() as data%5Baction%5D=..., so the later action=... checks never match and no CleanTalk fields are injected. FormData already explicitly unwraps this same data[...] shape; normalize equivalent object/URLSearchParams keys before sign matching.
        if ( this.isJQAjaxPlainObjectOrArray(ajaxObject.data) ) {
            return this.getStructuredJQAjaxSignString(ajaxObject.data);
        }
        if ( this.isJQAjaxURLSearchParams(ajaxObject.data) ) {
            return ajaxObject.data.toString();

js/prebuild/apbct-public-bundle_full-protection.js:3992

  • These branches only recognize top-level action/nonce fields. A valid WordPress-style payload using data[action]=pafe_ajax_form_builder is either ignored by getStructuredJQAjaxSignString or encoded by URLSearchParams.toString() as data%5Baction%5D=..., so the later action=... checks never match and no CleanTalk fields are injected. FormData already explicitly unwraps this same data[...] shape; normalize equivalent object/URLSearchParams keys before sign matching.
        if ( this.isJQAjaxPlainObjectOrArray(ajaxObject.data) ) {
            return this.getStructuredJQAjaxSignString(ajaxObject.data);
        }
        if ( this.isJQAjaxURLSearchParams(ajaxObject.data) ) {
            return ajaxObject.data.toString();

js/prebuild/apbct-public-bundle_full-protection_gathering.js:3992

  • These branches only recognize top-level action/nonce fields. A valid WordPress-style payload using data[action]=pafe_ajax_form_builder is either ignored by getStructuredJQAjaxSignString or encoded by URLSearchParams.toString() as data%5Baction%5D=..., so the later action=... checks never match and no CleanTalk fields are injected. FormData already explicitly unwraps this same data[...] shape; normalize equivalent object/URLSearchParams keys before sign matching.
        if ( this.isJQAjaxPlainObjectOrArray(ajaxObject.data) ) {
            return this.getStructuredJQAjaxSignString(ajaxObject.data);
        }
        if ( this.isJQAjaxURLSearchParams(ajaxObject.data) ) {
            return ajaxObject.data.toString();

js/prebuild/apbct-public-bundle_gathering.js:3992

  • These branches only recognize top-level action/nonce fields. A valid WordPress-style payload using data[action]=pafe_ajax_form_builder is either ignored by getStructuredJQAjaxSignString or encoded by URLSearchParams.toString() as data%5Baction%5D=..., so the later action=... checks never match and no CleanTalk fields are injected. FormData already explicitly unwraps this same data[...] shape; normalize equivalent object/URLSearchParams keys before sign matching.
        if ( this.isJQAjaxPlainObjectOrArray(ajaxObject.data) ) {
            return this.getStructuredJQAjaxSignString(ajaxObject.data);
        }
        if ( this.isJQAjaxURLSearchParams(ajaxObject.data) ) {
            return ajaxObject.data.toString();

js/prebuild/apbct-public-bundle_int-protection.js:3992

  • These branches only recognize top-level action/nonce fields. A valid WordPress-style payload using data[action]=pafe_ajax_form_builder is either ignored by getStructuredJQAjaxSignString or encoded by URLSearchParams.toString() as data%5Baction%5D=..., so the later action=... checks never match and no CleanTalk fields are injected. FormData already explicitly unwraps this same data[...] shape; normalize equivalent object/URLSearchParams keys before sign matching.
        if ( this.isJQAjaxPlainObjectOrArray(ajaxObject.data) ) {
            return this.getStructuredJQAjaxSignString(ajaxObject.data);
        }
        if ( this.isJQAjaxURLSearchParams(ajaxObject.data) ) {
            return ajaxObject.data.toString();

js/prebuild/apbct-public-bundle_int-protection_gathering.js:3992

  • These branches only recognize top-level action/nonce fields. A valid WordPress-style payload using data[action]=pafe_ajax_form_builder is either ignored by getStructuredJQAjaxSignString or encoded by URLSearchParams.toString() as data%5Baction%5D=..., so the later action=... checks never match and no CleanTalk fields are injected. FormData already explicitly unwraps this same data[...] shape; normalize equivalent object/URLSearchParams keys before sign matching.
        if ( this.isJQAjaxPlainObjectOrArray(ajaxObject.data) ) {
            return this.getStructuredJQAjaxSignString(ajaxObject.data);
        }
        if ( this.isJQAjaxURLSearchParams(ajaxObject.data) ) {
            return ajaxObject.data.toString();

js/src/public-1-main.js:1245

  • These branches only recognize top-level action/nonce fields. A valid WordPress-style payload using data[action]=pafe_ajax_form_builder is either ignored by getStructuredJQAjaxSignString or encoded by URLSearchParams.toString() as data%5Baction%5D=..., so the later action=... checks never match and no CleanTalk fields are injected. FormData already explicitly unwraps this same data[...] shape; normalize equivalent object/URLSearchParams keys before sign matching.
        if ( this.isJQAjaxPlainObjectOrArray(ajaxObject.data) ) {
            return this.getStructuredJQAjaxSignString(ajaxObject.data);
        }
        if ( this.isJQAjaxURLSearchParams(ajaxObject.data) ) {
            return ajaxObject.data.toString();

js/src/public-1-main.js:1310

  • A serializeArray entry can use a form-style name such as data[twt_cc_signup]. The old serialized-string path matched that name, but this condition only accepts the exact twt_cc_signup name (or a matching value), so the prefilter skips the request and does not inject CleanTalk data. Normalize bracketed names or match twt_cc_signup within entry.name too.
                entry.name === 'action' ||
                entry.name === 'ur_frontend_form_nonce' ||
                entry.name === 'twt_cc_signup' ||
                (typeof value === 'string' && value.indexOf('twt_cc_signup') !== -1)

js/src/public-1-main.js:1288

  • This structural scan misses bracketed/nested field names such as {'data[twt_cc_signup]': 1}. jQuery serializes that object to a payload containing twt_cc_signup, which the previous string-based check detected, but this method only recognizes an exact top-level key or a string value. Such requests are therefore not recognized and receive no CleanTalk fields; match bracketed keys and/or recurse through nested objects as part of the structural scan.
        for ( const key in data ) {
            if ( !Object.prototype.hasOwnProperty.call(data, key) ) {
                continue;
            }
            if ( typeof data[key] === 'string' && data[key].indexOf('twt_cc_signup') !== -1 ) {
                return true;
  • Files reviewed: 9/17 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Moderate issues remain with base64 token encoding and entries() fallback handling.

Review details

Suppressed comments (16)

js/prebuild/apbct-public-bundle.js:4433

  • These new object/URLSearchParams paths route payloads through injectCleantalkDataToJQAjaxString, which prepends getNoCookieData() verbatim. That value is base64, so a + is decoded as a space in an application/x-www-form-urlencoded request and the no-cookie token becomes invalid. URL-encode the value (or append it through a form encoder) before using this string path.
        if ( typeof ajaxData === 'string' ) {
            return this.injectCleantalkDataToJQAjaxString(sourceSign, ajaxData);

js/prebuild/apbct-public-bundle.js:4215

  • This fallback handles only get() after forEach(). FormData polyfills can expose entries() without either method (the previous implementation used entries() for the visible-field path); for those payloads data[action]/action is never discovered, so the prefilter skips CleanTalk injection entirely. Add an entries() enumeration fallback before returning an empty sign string.
        if ( typeof bag.get !== 'function' ) {
            return '';
        }

js/prebuild/apbct-public-bundle_ext-protection.js:4433

  • These new object/URLSearchParams paths route payloads through injectCleantalkDataToJQAjaxString, which prepends getNoCookieData() verbatim. That value is base64, so a + is decoded as a space in an application/x-www-form-urlencoded request and the no-cookie token becomes invalid. URL-encode the value (or append it through a form encoder) before using this string path.
        if ( typeof ajaxData === 'string' ) {
            return this.injectCleantalkDataToJQAjaxString(sourceSign, ajaxData);

js/prebuild/apbct-public-bundle_ext-protection.js:4215

  • This fallback handles only get() after forEach(). FormData polyfills can expose entries() without either method (the previous implementation used entries() for the visible-field path); for those payloads data[action]/action is never discovered, so the prefilter skips CleanTalk injection entirely. Add an entries() enumeration fallback before returning an empty sign string.
        if ( typeof bag.get !== 'function' ) {
            return '';
        }

js/prebuild/apbct-public-bundle_ext-protection_gathering.js:4215

  • This fallback handles only get() after forEach(). FormData polyfills can expose entries() without either method (the previous implementation used entries() for the visible-field path); for those payloads data[action]/action is never discovered, so the prefilter skips CleanTalk injection entirely. Add an entries() enumeration fallback before returning an empty sign string.
        if ( typeof bag.get !== 'function' ) {
            return '';
        }

js/prebuild/apbct-public-bundle_full-protection.js:4433

  • These new object/URLSearchParams paths route payloads through injectCleantalkDataToJQAjaxString, which prepends getNoCookieData() verbatim. That value is base64, so a + is decoded as a space in an application/x-www-form-urlencoded request and the no-cookie token becomes invalid. URL-encode the value (or append it through a form encoder) before using this string path.
        if ( typeof ajaxData === 'string' ) {
            return this.injectCleantalkDataToJQAjaxString(sourceSign, ajaxData);

js/prebuild/apbct-public-bundle_full-protection.js:4215

  • This fallback handles only get() after forEach(). FormData polyfills can expose entries() without either method (the previous implementation used entries() for the visible-field path); for those payloads data[action]/action is never discovered, so the prefilter skips CleanTalk injection entirely. Add an entries() enumeration fallback before returning an empty sign string.
        if ( typeof bag.get !== 'function' ) {
            return '';
        }

js/prebuild/apbct-public-bundle_full-protection_gathering.js:4215

  • This fallback handles only get() after forEach(). FormData polyfills can expose entries() without either method (the previous implementation used entries() for the visible-field path); for those payloads data[action]/action is never discovered, so the prefilter skips CleanTalk injection entirely. Add an entries() enumeration fallback before returning an empty sign string.
        if ( typeof bag.get !== 'function' ) {
            return '';
        }

js/prebuild/apbct-public-bundle_gathering.js:4433

  • These new object/URLSearchParams paths route payloads through injectCleantalkDataToJQAjaxString, which prepends getNoCookieData() verbatim. That value is base64, so a + is decoded as a space in an application/x-www-form-urlencoded request and the no-cookie token becomes invalid. URL-encode the value (or append it through a form encoder) before using this string path.
        if ( typeof ajaxData === 'string' ) {
            return this.injectCleantalkDataToJQAjaxString(sourceSign, ajaxData);

js/prebuild/apbct-public-bundle_gathering.js:4215

  • This fallback handles only get() after forEach(). FormData polyfills can expose entries() without either method (the previous implementation used entries() for the visible-field path); for those payloads data[action]/action is never discovered, so the prefilter skips CleanTalk injection entirely. Add an entries() enumeration fallback before returning an empty sign string.
        if ( typeof bag.get !== 'function' ) {
            return '';
        }

js/prebuild/apbct-public-bundle_int-protection.js:4433

  • These new object/URLSearchParams paths route payloads through injectCleantalkDataToJQAjaxString, which prepends getNoCookieData() verbatim. That value is base64, so a + is decoded as a space in an application/x-www-form-urlencoded request and the no-cookie token becomes invalid. URL-encode the value (or append it through a form encoder) before using this string path.
        if ( typeof ajaxData === 'string' ) {
            return this.injectCleantalkDataToJQAjaxString(sourceSign, ajaxData);

js/prebuild/apbct-public-bundle_int-protection.js:4215

  • This fallback handles only get() after forEach(). FormData polyfills can expose entries() without either method (the previous implementation used entries() for the visible-field path); for those payloads data[action]/action is never discovered, so the prefilter skips CleanTalk injection entirely. Add an entries() enumeration fallback before returning an empty sign string.
        if ( typeof bag.get !== 'function' ) {
            return '';
        }

js/prebuild/apbct-public-bundle_int-protection_gathering.js:4433

  • These new object/URLSearchParams paths route payloads through injectCleantalkDataToJQAjaxString, which prepends getNoCookieData() verbatim. That value is base64, so a + is decoded as a space in an application/x-www-form-urlencoded request and the no-cookie token becomes invalid. URL-encode the value (or append it through a form encoder) before using this string path.
        if ( typeof ajaxData === 'string' ) {
            return this.injectCleantalkDataToJQAjaxString(sourceSign, ajaxData);

js/prebuild/apbct-public-bundle_int-protection_gathering.js:4215

  • This fallback handles only get() after forEach(). FormData polyfills can expose entries() without either method (the previous implementation used entries() for the visible-field path); for those payloads data[action]/action is never discovered, so the prefilter skips CleanTalk injection entirely. Add an entries() enumeration fallback before returning an empty sign string.
        if ( typeof bag.get !== 'function' ) {
            return '';
        }

js/src/public-1-main.js:1686

  • These new object/URLSearchParams paths route payloads through injectCleantalkDataToJQAjaxString, which prepends getNoCookieData() verbatim. That value is base64, so a + is decoded as a space in an application/x-www-form-urlencoded request and the no-cookie token becomes invalid. URL-encode the value (or append it through a form encoder) before using this string path.
        if ( typeof ajaxData === 'string' ) {
            return this.injectCleantalkDataToJQAjaxString(sourceSign, ajaxData);

js/src/public-1-main.js:1468

  • This fallback handles only get() after forEach(). FormData polyfills can expose entries() without either method (the previous implementation used entries() for the visible-field path); for those payloads data[action]/action is never discovered, so the prefilter skips CleanTalk injection entirely. Add an entries() enumeration fallback before returning an empty sign string.
        if ( typeof bag.get !== 'function' ) {
            return '';
        }
  • Files reviewed: 9/17 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Four moderate AJAX payload-handling issues remain unresolved.

Review details

Suppressed comments (4)

js/src/public-1-main.js:1713

  • This preserves object-like payloads whenever processData or contentType is false, but the prefilter also handles GET/HEAD requests. jQuery only moves no-content data into the URL when it is a string or processData is true; with this flag a recognized plain object, serializeArray, or URLSearchParams remains an object and the transport sends neither its original fields nor the injected CleanTalk fields. Exclude GET/HEAD (the default method when no type is supplied) from keepOriginalType so those payloads are serialized to the query string.
        const keepOriginalType = !!(ajaxOptions && (
            ajaxOptions.processData === false ||
            ajaxOptions.contentType === false
        ));

js/src/public-1-main.js:1755

  • This branch always forces processData = false for FormData, including GET/HEAD requests. jQuery does not append a non-string data value to the URL for no-content methods, and its transport sends no body, so a recognized FormData request loses both the caller's fields and the injected CleanTalk fields. No-content requests need a query-string representation (or an explicit unsupported-case path) instead of preserving FormData.
        if ( this.isJQAjaxFormData(ajaxData) ) {
            this.preserveJQAjaxFormDataOptions(ajaxOptions);
            return this.injectCleantalkDataToJQAjaxFormData(

js/src/public-1-main.js:1913

  • forEachJQAjaxKeyValueBag() explicitly supports FormData implementations that expose only entries(), but this clone path returns the original object whenever forEach() is absent. For an entries-only FormData, the subsequent append() calls therefore mutate the caller's payload; reusing that FormData for another AJAX request accumulates CleanTalk fields (and can send duplicates). Clone via the same entries() fallback before appending, or otherwise avoid mutating the original when it is enumerable.
    cloneFormData(formData) {
        if ( typeof formData.forEach !== 'function' ) {
            return formData;
        }

js/src/public-1-main.js:1432

  • The implementation below supports entries()-only key/value bags, but this type guard rejects an entries-only FormData polyfill unless it happens to spoof [object FormData]. Such payloads fall through as unknown objects, so their action is not detected and no CleanTalk fields are injected. Include entries() in the fallback capability check (while retaining the existing exclusions for URLSearchParams/Headers).
        return typeof data.forEach === 'function' || typeof data.get === 'function';
  • Files reviewed: 9/17 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Moderate issues remain with WooCommerce token handling and IE11 FormData support.

Review details

Suppressed comments (12)

js/prebuild/apbct-public-bundle.js:4443

  • For a GET/HEAD request to wc-ajax=add_to_cart, keepUnwrapped remains false, so the dispatcher adds data[ct_bot_detector_event_token] to the URL. WooCommerce's classic handler reads this token from top-level Get::get('ct_bot_detector_event_token') (lib/Cleantalk/Antispam/IntegrationsByClass/Woocommerce.php:430-434), so it ignores the injected token; use unwrapped keys for no-body methods while retaining wrapped keys for POST payloads.
        // woocommerce add to cart is based on URL
        if ( typeof ajaxObject.url === 'string' && ajaxObject.url.indexOf('wc-ajax=add_to_cart') !== -1 ) {
            sourceSign.found = 'wc-ajax=add_to_cart';
        }

js/prebuild/apbct-public-bundle.js:4212

  • Native IE11 FormData has no forEach, entries, or get, so this branch returns an empty sign string for every such payload. As a result, action- and twt_cc_signup-based requests are never recognized and receive no CleanTalk fields; getFormDataField cannot help because it runs only after a sign is found. Use a request/plugin signal that IE11 can inspect or remove the IE11 compatibility claim instead of relying on unavailable FormData readers.
        if ( typeof bag.get !== 'function' ) {
            return '';

js/prebuild/apbct-public-bundle_ext-protection.js:4443

  • For a GET/HEAD request to wc-ajax=add_to_cart, keepUnwrapped remains false, so the dispatcher adds data[ct_bot_detector_event_token] to the URL. WooCommerce's classic handler reads this token from top-level Get::get('ct_bot_detector_event_token') (lib/Cleantalk/Antispam/IntegrationsByClass/Woocommerce.php:430-434), so it ignores the injected token; use unwrapped keys for no-body methods while retaining wrapped keys for POST payloads.
        // woocommerce add to cart is based on URL
        if ( typeof ajaxObject.url === 'string' && ajaxObject.url.indexOf('wc-ajax=add_to_cart') !== -1 ) {
            sourceSign.found = 'wc-ajax=add_to_cart';
        }

js/prebuild/apbct-public-bundle_ext-protection.js:4212

  • Native IE11 FormData has no forEach, entries, or get, so this branch returns an empty sign string for every such payload. As a result, action- and twt_cc_signup-based requests are never recognized and receive no CleanTalk fields; getFormDataField cannot help because it runs only after a sign is found. Use a request/plugin signal that IE11 can inspect or remove the IE11 compatibility claim instead of relying on unavailable FormData readers.
        if ( typeof bag.get !== 'function' ) {
            return '';

js/prebuild/apbct-public-bundle_full-protection.js:4443

  • For a GET/HEAD request to wc-ajax=add_to_cart, keepUnwrapped remains false, so the dispatcher adds data[ct_bot_detector_event_token] to the URL. WooCommerce's classic handler reads this token from top-level Get::get('ct_bot_detector_event_token') (lib/Cleantalk/Antispam/IntegrationsByClass/Woocommerce.php:430-434), so it ignores the injected token; use unwrapped keys for no-body methods while retaining wrapped keys for POST payloads.
        // woocommerce add to cart is based on URL
        if ( typeof ajaxObject.url === 'string' && ajaxObject.url.indexOf('wc-ajax=add_to_cart') !== -1 ) {
            sourceSign.found = 'wc-ajax=add_to_cart';
        }

js/prebuild/apbct-public-bundle_full-protection.js:4212

  • Native IE11 FormData has no forEach, entries, or get, so this branch returns an empty sign string for every such payload. As a result, action- and twt_cc_signup-based requests are never recognized and receive no CleanTalk fields; getFormDataField cannot help because it runs only after a sign is found. Use a request/plugin signal that IE11 can inspect or remove the IE11 compatibility claim instead of relying on unavailable FormData readers.
        if ( typeof bag.get !== 'function' ) {
            return '';

js/prebuild/apbct-public-bundle_gathering.js:4443

  • For a GET/HEAD request to wc-ajax=add_to_cart, keepUnwrapped remains false, so the dispatcher adds data[ct_bot_detector_event_token] to the URL. WooCommerce's classic handler reads this token from top-level Get::get('ct_bot_detector_event_token') (lib/Cleantalk/Antispam/IntegrationsByClass/Woocommerce.php:430-434), so it ignores the injected token; use unwrapped keys for no-body methods while retaining wrapped keys for POST payloads.
        // woocommerce add to cart is based on URL
        if ( typeof ajaxObject.url === 'string' && ajaxObject.url.indexOf('wc-ajax=add_to_cart') !== -1 ) {
            sourceSign.found = 'wc-ajax=add_to_cart';
        }

js/prebuild/apbct-public-bundle_gathering.js:4212

  • Native IE11 FormData has no forEach, entries, or get, so this branch returns an empty sign string for every such payload. As a result, action- and twt_cc_signup-based requests are never recognized and receive no CleanTalk fields; getFormDataField cannot help because it runs only after a sign is found. Use a request/plugin signal that IE11 can inspect or remove the IE11 compatibility claim instead of relying on unavailable FormData readers.
        if ( typeof bag.get !== 'function' ) {
            return '';

js/prebuild/apbct-public-bundle_int-protection.js:4443

  • For a GET/HEAD request to wc-ajax=add_to_cart, keepUnwrapped remains false, so the dispatcher adds data[ct_bot_detector_event_token] to the URL. WooCommerce's classic handler reads this token from top-level Get::get('ct_bot_detector_event_token') (lib/Cleantalk/Antispam/IntegrationsByClass/Woocommerce.php:430-434), so it ignores the injected token; use unwrapped keys for no-body methods while retaining wrapped keys for POST payloads.
        // woocommerce add to cart is based on URL
        if ( typeof ajaxObject.url === 'string' && ajaxObject.url.indexOf('wc-ajax=add_to_cart') !== -1 ) {
            sourceSign.found = 'wc-ajax=add_to_cart';
        }

js/prebuild/apbct-public-bundle_int-protection.js:4212

  • Native IE11 FormData has no forEach, entries, or get, so this branch returns an empty sign string for every such payload. As a result, action- and twt_cc_signup-based requests are never recognized and receive no CleanTalk fields; getFormDataField cannot help because it runs only after a sign is found. Use a request/plugin signal that IE11 can inspect or remove the IE11 compatibility claim instead of relying on unavailable FormData readers.
        if ( typeof bag.get !== 'function' ) {
            return '';

js/src/public-1-main.js:1696

  • For a GET/HEAD request to wc-ajax=add_to_cart, keepUnwrapped remains false, so the dispatcher adds data[ct_bot_detector_event_token] to the URL. WooCommerce's classic handler reads this token from top-level Get::get('ct_bot_detector_event_token') (lib/Cleantalk/Antispam/IntegrationsByClass/Woocommerce.php:430-434), so it ignores the injected token; use unwrapped keys for no-body methods while retaining wrapped keys for POST payloads.
        // woocommerce add to cart is based on URL
        if ( typeof ajaxObject.url === 'string' && ajaxObject.url.indexOf('wc-ajax=add_to_cart') !== -1 ) {
            sourceSign.found = 'wc-ajax=add_to_cart';
        }

js/src/public-1-main.js:1465

  • Native IE11 FormData has no forEach, entries, or get, so this branch returns an empty sign string for every such payload. As a result, action- and twt_cc_signup-based requests are never recognized and receive no CleanTalk fields; getFormDataField cannot help because it runs only after a sign is found. Use a request/plugin signal that IE11 can inspect or remove the IE11 compatibility claim instead of relying on unavailable FormData readers.
        if ( typeof bag.get !== 'function' ) {
            return '';
  • Files reviewed: 9/17 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The AJAX interception and regenerated bundle changes span multiple payload formats and integrations, warranting human review.

Review details
  • Files reviewed: 9/17 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@svedge
svedge requested a review from Glomberg September 14, 2026 11:01
@svedge
svedge merged commit 3fdb754 into dev Sep 15, 2026
10 checks passed
@svedge
svedge deleted the upd-ajax-prefilter branch September 29, 2026 03:42
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.

3 participants