Skip to content

Commit fbfe066

Browse files
authored
Merge pull request #55 from vizuh/agent/release-1.8.8-prep
release: v1.8.8 — security (macro reject) + consent gate fix
2 parents 941fb11 + 52b3ff1 commit fbfe066

7 files changed

Lines changed: 53 additions & 10 deletions

File tree

.distignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,25 @@ RELEASING.md
4444

4545
# Dev README variants (non-English kept out of WP.org trunk)
4646
README.en.md
47+
README.md
4748
README.pt-BR.md
4849
readme_header_update.txt
4950

51+
# Dev assets not used in production
52+
assets/funnelsheet-logo.png
53+
5054
# Internal docs
5155
docs/
5256

5357
# Build tools and dev scripts
5458
tools/
59+
60+
# Release artefacts (zip archives, release notes)
61+
dist/
62+
63+
# GTM reference / dev files
64+
assets/GTM-*.json
65+
assets/gtm-starter-kit.json
66+
assets/shopify-gtm-container-templates-master/
67+
assets/build-starter-kit.py
68+
includes/admin/class-gtm-lead-magnet.php

assets/js/clicutcl-attribution.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@
9999
let s = String(value);
100100
s = s.replace(/[\u0000-\u001F\u007F]/g, ' ').trim();
101101
if (!s) return '';
102+
// Reject unsubstituted ad-platform dynamic parameter macros, e.g.
103+
// Facebook {{campaign.name}}, {{adset.name}}, {{ad.name}}, etc.
104+
// These appear literally in URLs when not served through the ad platform.
105+
if (/^\{\{.+\}\}$/.test(s)) return '';
102106
if (s.length > maxLen) s = s.slice(0, maxLen);
103107
return s;
104108
}

changelog.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
Changelog
22
========
33

4+
= 1.8.8 =
5+
* **Security: reject ad-platform macros in attribution capture** (`includes/Core/class-attribution-provider.php`, `assets/js/clicutcl-attribution.js`): Facebook (and other ad platforms') dynamic parameter macros — `{{campaign.name}}`, `{{adset.name}}`, `{{ad.name}}`, `{{placement}}`, etc. — appear literally in landing-page URLs when ads aren't actually served through the ad platform (manual preview, test traffic, misconfigured campaigns). Previously these unsubstituted placeholders flowed into the `wp_clicktrail_*` attribution tables and downstream destinations (GA4, Meta CAPI, Google Ads conversions) as if they were real campaign / adset / ad names, polluting reports. Now rejected via `^\{\{.+\}\}$` regex in both the server-side sanitize loop in `Attribution_Provider::sanitize_meta()` and the client-side `sanitizeValue()` helper. The two implementations are intentionally symmetric so both capture paths (REST batch + server-side enrichment) drop these strings.
6+
* **Fix: consent gate no longer defaults to ON when Consent Mode is disabled** (`includes/Core/class-attribution-provider.php` `should_populate()`, `includes/class-clicutcl-core.php` `build_consent_bridge_config()`): Two paths previously read a `require_consent` option from `Attribution_Settings::get_all()`, defaulting to `true` when the option was unset. The legacy `require_consent` field was removed from the admin UI several releases ago, so on every site without Consent Mode + an active CMP, the implicit-true default silently blocked all attribution from being persisted. Both call sites now: (a) only consult Consent Mode when explicitly enabled, and (b) default to `false` (no gate) when Consent Mode is disabled. Sites running Consent Mode are unaffected — the consent decision still routes through `Consent_Mode_Settings::is_consent_required_for_request()`.
7+
* **Housekeeping: GTM Starter Kit lead-magnet banner disabled at runtime** (`includes/class-clicutcl-core.php` `init_modules()`, `.distignore`): The GTM Starter Kit is now distributed via the marketing site rather than as an in-plugin banner. `GTM_Lead_Magnet::init()` runtime call commented out; the class file (`includes/admin/class-gtm-lead-magnet.php`), the kit JSON (`assets/gtm-starter-kit.json`), the build helper (`assets/build-starter-kit.py`), and the Shopify GTM templates dir (`assets/shopify-gtm-container-templates-master/`) added to `.distignore` so they're excluded from the WP.org SVN release. The class file stays in the repo for future re-activation.
8+
* **`.distignore` cleanup**: Also added `README.md`, `dist/`, `assets/funnelsheet-logo.png`, and `assets/GTM-*.json` to the WP.org-SVN exclude list. These were leaking into prior releases without serving any user-facing purpose.
9+
10+
Source: triage of the stashed pre-7.0 WIP — vault `01-projects/clicktrail/wip-triage-2026-05-28/triage-report.md`. The stash also carried 42 files of pure CRLF→LF re-encoding noise (no semantic intent), which is deliberately not applied. The triage agent's analysis is the authoritative record of why those files are out.
11+
412
= 1.8.7 =
513
* **Brazilian Portuguese (pt_BR) translations refreshed** (`languages/click-trail-handler-pt_BR.po`, `languages/click-trail-handler-pt_BR.mo`): The pt_BR `.po` file was last regenerated at v1.5.2 (POT-Creation-Date 2025-12-26) and was missing every translatable string added in the 9 subsequent releases (1.6.0 → 1.8.6). Regenerated POT from current source via `xgettext` over PHP (`clicutcl.php`, `uninstall.php`, `includes/**.php`) and JS (`assets/js/*.js`) with the standard WP translation keyword set; merged into the existing pt_BR.po preserving prior translations; filled the new untranslated entries via AI translation pass; recompiled `.mo`. Final state: 533 fully-translated strings + 81 carried-over strings flagged `fuzzy` for human review where the source msgid changed since the original translation was made.
614
* **German (de_DE) translations added** (`languages/click-trail-handler-de_DE.po`, `languages/click-trail-handler-de_DE.mo`): First locale beyond Portuguese. Generated `de_DE.po` template from the regenerated POT via `msginit --locale=de_DE`; AI-translated all 614 strings with a formal-business register (Sie-form); recompiled `.mo`. Tech terms (ClickTrail, WooCommerce, GTM, GA4, UTM, sGTM, Click IDs), printf placeholders, and HTML tags preserved verbatim.

clicutcl.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Plugin Name: ClickTrail – UTM, Click ID & Ad Tracking (with Consent)
44
* Plugin URI: https://github.com/vizuh/click-trail-handler
55
* Description: Consent-aware marketing attribution for WordPress. Captures UTMs, click IDs, and referrers, enriches WooCommerce orders and forms, collects browser events, and supports optional server-side delivery.
6-
* Version: 1.8.7
6+
* Version: 1.8.8
77
* Author: Vizuh
88
* Author URI: https://vizuh.com
99
* License: GPL-2.0-or-later
@@ -20,7 +20,7 @@
2020
}
2121

2222
// Define Constants
23-
define( 'CLICUTCL_VERSION', '1.8.7' );
23+
define( 'CLICUTCL_VERSION', '1.8.8' );
2424
define( 'CLICUTCL_DIR', plugin_dir_path( __FILE__ ) );
2525
define( 'CLICUTCL_URL', plugin_dir_url( __FILE__ ) );
2626
define( 'CLICUTCL_BASENAME', plugin_basename( __FILE__ ) );

includes/Core/class-attribution-provider.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,7 @@ public static function get_payload() {
159159
* @return bool
160160
*/
161161
public static function should_populate() {
162-
$options = Attribution_Settings::get_all();
163-
$require_consent = isset( $options['require_consent'] ) ? (bool) $options['require_consent'] : true; // Default to true for safety.
162+
$require_consent = false; // Default: no consent gate unless Consent Mode is explicitly enabled.
164163
$cookie_name = 'ct_consent';
165164

166165
if ( class_exists( 'CLICUTCL\\Modules\\Consent_Mode\\Consent_Mode_Settings' ) ) {
@@ -224,7 +223,14 @@ public static function sanitize( $data ) {
224223

225224
// Handle simple values only; no nested arrays expected in flattened payload.
226225
if ( is_scalar( $value ) ) {
227-
$sanitized[ $meta_key ] = sanitize_text_field( $value );
226+
$clean = sanitize_text_field( $value );
227+
// Reject unsubstituted ad-platform dynamic parameter macros, e.g.
228+
// Facebook {{campaign.name}}, {{adset.name}}, {{ad.name}}, etc.
229+
// These appear literally in URLs when not served through the ad platform.
230+
if ( preg_match( '/^\{\{.+\}\}$/', $clean ) ) {
231+
continue;
232+
}
233+
$sanitized[ $meta_key ] = $clean;
228234
}
229235
}
230236

includes/class-clicutcl-core.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,11 @@ private function define_admin_hooks() {
143143
$wc_admin->init();
144144
}
145145

146-
// GTM Starter Kit lead magnet — settings-page banner after first order tracked.
147-
if ( class_exists( 'CLICUTCL\\Admin\\GTM_Lead_Magnet' ) ) {
148-
\CLICUTCL\Admin\GTM_Lead_Magnet::init();
149-
}
146+
// GTM Starter Kit lead magnet — disabled in 1.8.8; kit offered on the website instead.
147+
// Reactivate when in-plugin distribution is ready.
148+
// if ( class_exists( 'CLICUTCL\\Admin\\GTM_Lead_Magnet' ) ) {
149+
// \CLICUTCL\Admin\GTM_Lead_Magnet::init();
150+
// }
150151
}
151152

152153
/**
@@ -352,9 +353,14 @@ private function build_consent_bridge_config( array $options, bool $debug_active
352353
$gcm_analytics_key = isset( $consent_settings['gcm_analytics_key'] ) ? sanitize_key( (string) $consent_settings['gcm_analytics_key'] ) : 'analytics_storage';
353354
$gcm_analytics_key = '' !== $gcm_analytics_key ? $gcm_analytics_key : 'analytics_storage';
354355
$bridge_debug = (bool) $debug_active || ( defined( 'WP_DEBUG' ) && WP_DEBUG );
355-
$require_consent = isset( $options['require_consent'] ) ? (bool) $options['require_consent'] : true;
356356
if ( $enable_consent ) {
357+
// Consent Mode is active — delegate entirely to the configured mode/region logic.
357358
$require_consent = $consent_settings_obj->is_consent_required_for_request();
359+
} else {
360+
// Consent Mode is disabled — never gate attribution on a legacy hidden setting.
361+
// The legacy `require_consent` field is no longer exposed in the UI, so silently
362+
// defaulting it to true would block all attribution for sites without a CMP.
363+
$require_consent = false;
358364
}
359365

360366
return array(

readme.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ Yes. ClickTrail can listen to its own banner, Cookiebot, OneTrust, Complianz, GT
212212

213213
== Changelog ==
214214

215+
= 1.8.8 =
216+
* **Security**: Reject unsubstituted ad-platform dynamic-parameter macros (Facebook `{{campaign.name}}`, `{{adset.name}}`, etc.) during attribution capture. These placeholders appear literally in landing-page URLs when ads aren't served through the ad platform, and previously flowed into attribution storage and downstream destinations as if they were real campaign names. Applied to both PHP server-side sanitizer and the client-side `sanitizeValue()` helper.
217+
* **Fix**: Consent gate no longer defaults to ON when Consent Mode is disabled. Two paths previously read a removed legacy `require_consent` option (default TRUE) — on any site without Consent Mode + a CMP, that implicit default silently blocked all attribution. Now the gate is only active when Consent Mode is explicitly enabled.
218+
* **Housekeeping**: GTM Starter Kit lead-magnet banner disabled in-plugin; the kit is now distributed via the website. The class file stays for future re-activation; the runtime init() call is commented out and the source files added to `.distignore` so they're not bundled in the WP.org SVN release.
219+
215220
= 1.8.7 =
216221
* **Brazilian Portuguese (pt_BR) translations refreshed**: Regenerated from current source code (was last updated at v1.5.2). 533 strings translated, 81 strings carried over from the prior translation but flagged for human review where the underlying source string changed.
217222
* **German (de_DE) translations added**: Full translation of all 614 strings — first locale beyond Portuguese.

0 commit comments

Comments
 (0)