Changeset 3452023
- Timestamp:
- 02/02/2026 11:49:37 AM (3 weeks ago)
- Location:
- tracksharp/trunk
- Files:
-
- 6 edited
-
Tracksharp-tracker.php (modified) (2 diffs)
-
assets/tracksharp-tracker.js (modified) (7 diffs)
-
includes/class-tracksharp-loader.php (modified) (2 diffs)
-
includes/class-tracksharp-rest.php (modified) (6 diffs)
-
includes/class-tracksharp-woocommerce.php (modified) (9 diffs)
-
readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
tracksharp/trunk/Tracksharp-tracker.php
r3444685 r3452023 4 4 * Plugin Name: TrackSharp – Server Side Tracking for WooCommerce 5 5 * Description: First-party server-side tracking for WooCommerce. GA4 Measurement Protocol, Meta Conversions API (CAPI), Google Ads Conversion attribution, Enhanced Conversions, and WP Consent API integration. 6 * Version: 1.2. 76 * Version: 1.2.8 7 7 * Author: Kaya van Vliet 8 8 * Requires PHP: 7.4 … … 17 17 // Plugin Constants 18 18 // ============================================================================ 19 define( 'TRACKSHARP_VERSION', '1.2. 7' );19 define( 'TRACKSHARP_VERSION', '1.2.8' ); 20 20 define( 'TRACKSHARP_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 21 21 define( 'TRACKSHARP_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); -
tracksharp/trunk/assets/tracksharp-tracker.js
r3444685 r3452023 35 35 })(); 36 36 DBG("[WS] boot", { cfg: DBG.clone(CFG) }); 37 38 // ---- Referral Preservation (TAGGRS-inspired) ---- 39 (function () { 40 try { 41 var ref = document.referrer; 42 var host = location.hostname; 43 // If we have a referrer and it's not from our own site, store it as the initial referrer 44 if (ref && ref.indexOf(host) === -1) { 45 if (!sessionStorage.getItem("ws_initial_referrer")) { 46 sessionStorage.setItem("ws_initial_referrer", ref); 47 setCookie("ws_initial_referrer", ref, 30); // 30 days 48 DBG("[WS] stored initial referrer:", ref); 49 } 50 } 51 } catch (_) {} 52 })(); 37 53 38 54 // ---- Utils ---- … … 1106 1122 // with the server-side MP purchase (same event_id + transaction_id). 1107 1123 var gaBrowserFired = false; 1108 // Keep browser + server GA4 event names aligned for deduplication and dashboard metrics.1109 // When comparison mode is enabled, server purchases are sent as "purchase_sst".1110 var gaEventName = (CFG && CFG.ga4_purchase_event_name) || "purchase";1111 if (gaEventName !== "purchase" && gaEventName !== "purchase_sst") {1112 gaEventName = "purchase";1113 }1114 1115 1124 try { 1116 1125 var isPurchase = name === "purchase"; 1126 var isEcommerce = isPurchase || ["view_item", "add_to_cart", "begin_checkout", "view_item_list", "select_item", "remove_from_cart", "view_cart", "add_shipping_info", "add_payment_info"].indexOf(name) !== -1; 1127 1128 // Keep browser + server GA4 event names aligned for deduplication and dashboard metrics. 1129 // When comparison mode is enabled, server purchases are sent as "purchase_sst". 1130 var gaEventName = isPurchase ? ((CFG && CFG.ga4_purchase_event_name) || "purchase") : name; 1131 if (isPurchase && gaEventName !== "purchase" && gaEventName !== "purchase_sst") { 1132 gaEventName = "purchase"; 1133 } 1117 1134 1118 1135 var txidAudit = … … 1121 1138 : undefined; 1122 1139 1123 if (is Purchase && allowGa4Browser && CFG.ga4_mid) {1140 if (isEcommerce && allowGa4Browser && CFG.ga4_mid) { 1124 1141 if (gaEventName === "purchase_sst") { 1125 1142 DBG("[WS] GA4 browser purchase suppressed (purchase_sst comparison mode)", { … … 1159 1176 } 1160 1177 1161 gaParams.send_to = CFG.ga4_mid; 1178 // Attribution enrichment: ensure the original referrer is never lost in browser hits 1179 try { 1180 var preservedRef = sessionStorage.getItem("ws_initial_referrer"); 1181 if (preservedRef) gaParams.page_referrer = preservedRef; 1182 gaParams.page_location = location.href; 1183 } catch (_) {} 1184 1185 gaParams.send_to = CFG.ga4_mid; 1162 1186 try { 1163 1187 window.gtag("event", gaEventName, gaParams); 1164 DBG("[WS] GA4 browser purchasesent:", gaEventName, CFG.ga4_mid, gaParams);1165 if ( name === "purchase") {1188 DBG("[WS] GA4 browser event sent:", gaEventName, CFG.ga4_mid, gaParams); 1189 if (isEcommerce) { 1166 1190 gaBrowserFired = true; 1167 1191 } … … 1173 1197 } 1174 1198 1175 // Always log a GA4 browser receipt (audit-only) for purchases (except purchase_sst).1199 // Always log a GA4 browser receipt (audit-only) for ecommerce events. 1176 1200 // This powers the Dashboard breakdown and lets the server decide whether a server-side 1177 1201 // fallback is needed. The receipt includes whether TrackSharp actually sent the browser hit. 1178 if (isPurchase && !!statsGranted && gaEventName !== "purchase_sst" && txidAudit) { 1202 // We audit all ecommerce events in reporting_first mode to ensure the dashboard remains accurate. 1203 var shouldAudit = isPurchase || (ga4AttributionMode === "reporting_first" && isEcommerce); 1204 if (shouldAudit && !!statsGranted && gaEventName !== "purchase_sst") { 1179 1205 try { 1180 await wsAuditBrowser("ga4", gaEventName, eventId, txidAudit, { 1206 // Use txidAudit for purchases, or eventId for other ecommerce events to ensure uniqueness in log if needed 1207 var auditId = txidAudit || eventId; 1208 await wsAuditBrowser("ga4", gaEventName, eventId, auditId, { 1181 1209 ga4_browser_sent: gaBrowserFired ? 1 : 0, 1182 1210 ga4_attribution_mode: ga4AttributionMode, … … 1233 1261 }, 1234 1262 page_url: pageUrl, 1263 page_referrer: 1264 sessionStorage.getItem("ws_initial_referrer") || 1265 document.referrer || 1266 undefined, 1267 user_agent: navigator.userAgent, 1235 1268 consent: (function () { 1236 1269 var tc = … … 1253 1286 ecom: payload && payload.ecom ? payload.ecom : undefined, 1254 1287 user: userPayload, 1288 ga4_browser_sent: gaBrowserFired ? 1 : 0, 1289 ga4_attribution_mode: ga4AttributionMode, 1255 1290 }; 1256 1291 -
tracksharp/trunk/includes/class-tracksharp-loader.php
r3444685 r3452023 137 137 // WooCommerce 138 138 $this->woocommerce->register_hooks(); 139 // Referral preservation (TAGGRS method 4) 140 add_action( 'wp_head', [$this, 'capture_initial_referrer'], 1 ); 139 141 // Admin 140 142 if ( is_admin() ) { … … 287 289 } 288 290 291 /** 292 * Capture initial referrer high in the head. 293 * 294 * @return void 295 */ 296 public function capture_initial_referrer() { 297 $js = '(function(){'; 298 $js .= 'try {'; 299 $js .= 'var ref = document.referrer;'; 300 $js .= 'var host = location.hostname;'; 301 $js .= 'if (ref && ref.indexOf(host) === -1) {'; 302 $js .= 'if (!sessionStorage.getItem("ws_initial_referrer")) {'; 303 $js .= 'sessionStorage.setItem("ws_initial_referrer", ref);'; 304 $js .= '}'; 305 $js .= '}'; 306 $js .= '} catch(_){}'; 307 $js .= '})();'; 308 echo '<script>' . $js . '</script>' . "\n"; 309 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped 310 } 311 289 312 } -
tracksharp/trunk/includes/class-tracksharp-rest.php
r3444685 r3452023 220 220 $ga4_result = ['ok' => false, 'reason' => 'ga4 blocked by consent']; 221 221 if ($server_allows['ga4']) { 222 $ga4_result = $this->send_ga4_event($payload, $event, $event_id, $ga4_client_id, $session_id, $currency, $value, $items, $ga_user_data, $user_id); 222 $page_url = sanitize_url($payload['page_url'] ?? ''); 223 $page_referrer = sanitize_url($payload['page_referrer'] ?? ''); 224 $user_agent = sanitize_text_field($payload['user_agent'] ?? $client_ua); 225 226 $ga4_browser_sent = !empty($payload['ga4_browser_sent']); 227 $mode = sanitize_text_field($payload['ga4_attribution_mode'] ?? ''); 228 229 // In reporting-first mode, if the browser hit was sent, we skip the server-side send 230 // to avoid duplication. The browser hit is canonical for attribution. 231 if ($mode === 'reporting_first' && $ga4_browser_sent) { 232 $ga4_result = ['ok' => true, 'source' => 'browser_primary', 'reason' => 'skipped (browser sent)']; 233 } else { 234 $ga4_result = $this->send_ga4_event( 235 $payload, 236 $event, 237 $event_id, 238 $ga4_client_id, 239 $session_id, 240 $currency, 241 $value, 242 $items, 243 $ga_user_data, 244 $user_id, 245 $page_url, 246 $page_referrer, 247 $user_agent 248 ); 249 } 223 250 } 224 251 … … 278 305 $st_h = isset($addr['region']) ? $this->hasher->h256_or_null($this->hasher->norm_str($addr['region'])) : ($addr['region_hashed'] ?? null); 279 306 $zp_h = isset($addr['postal']) ? $this->hasher->h256_or_null($this->hasher->norm_zip($addr['postal'])) : ($addr['postal_hashed'] ?? null); 307 $street_h = isset($addr['street']) ? $this->hasher->h256_or_null($this->hasher->norm_street($addr['street'])) : ($addr['street_hashed'] ?? null); 280 308 281 309 $country_h = $country_raw ? $this->hasher->h256_or_null($country_raw) : ($addr['country_hashed'] ?? null); … … 291 319 'st' => $st_h, 292 320 'zp' => $zp_h, 321 'street' => $street_h, 293 322 'country' => $country_h, 294 323 'country_raw' => $country_raw, … … 323 352 'sha256_first_name' => $hashed['fn'] ?: null, 324 353 'sha256_last_name' => $hashed['ln'] ?: null, 354 'sha256_street' => $hashed['street'] ?: null, 325 355 'city' => $city, 326 356 'region' => $region, … … 368 398 * @param string $currency Currency code. 369 399 * @param float $value Event value. 370 * @param array $items Items array. 371 * @param array $ga_user_data GA4 user data. 372 * @param string $user_id User ID. 400 * @param array $items Items array. 401 * @param array $ga_user_data GA4 user data. 402 * @param string $user_id User ID. 403 * @param string $page_location Optional page URL. 404 * @param string $page_referrer Optional page referrer. 405 * @param string $user_agent Optional user agent. 373 406 * @return array Result. 374 407 */ 375 private function send_ga4_event($payload, $event, $event_id, $ga4_client_id, $session_id, $currency, $value, $items, $ga_user_data, $user_id )408 private function send_ga4_event($payload, $event, $event_id, $ga4_client_id, $session_id, $currency, $value, $items, $ga_user_data, $user_id, $page_location = '', $page_referrer = '', $user_agent = '') 376 409 { 377 410 $params = [ … … 381 414 'event_id' => $event_id, 382 415 'engagement_time_msec' => 1, 416 'page_location' => $page_location ?: null, 417 'page_referrer' => $page_referrer ?: null, 418 'user_agent' => $user_agent ?: null, 419 'ua' => $user_agent ?: null, // Some implementations expect 'ua' parameter 383 420 ]; 384 421 385 422 if (!empty($payload['ids']['ga_session_id'])) { 386 $params[' ga_session_id'] = (int) $payload['ids']['ga_session_id'];423 $params['session_id'] = (int) $payload['ids']['ga_session_id']; 387 424 } 388 425 if (!empty($payload['ids']['ga_session_number'])) { 389 $params[' ga_session_number'] = (int) $payload['ids']['ga_session_number'];426 $params['session_number'] = (int) $payload['ids']['ga_session_number']; 390 427 } 391 428 -
tracksharp/trunk/includes/class-tracksharp-woocommerce.php
r3444685 r3452023 140 140 $order->update_meta_data('_ws_wbraid', sanitize_text_field(wp_unslash($_COOKIE['ws_wbraid']))); 141 141 } 142 143 // Capture attribution hints for server-side fallback 144 $order->update_meta_data('_ws_user_agent', isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : ''); 145 $order->update_meta_data('_ws_page_url', (is_ssl() ? 'https://' : 'http://') . sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'] ?? '')) . sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'] ?? ''))); 146 147 $initial_ref = isset($_COOKIE['ws_initial_referrer']) ? sanitize_url(wp_unslash($_COOKIE['ws_initial_referrer'])) : ''; 148 if (!$initial_ref && !empty($_SERVER['HTTP_REFERER'])) { 149 $initial_ref = sanitize_url(wp_unslash($_SERVER['HTTP_REFERER'])); 150 } 151 $order->update_meta_data('_ws_page_referrer', $initial_ref); 142 152 143 153 // --- GA4: Prefer real GA cookies over TrackSharp fallbacks --- … … 413 423 $ctx['fn_h'], 414 424 $ctx['ln_h'], 425 $ctx['street_h'], 415 426 $ctx['billing_city'], 416 427 $ctx['billing_st'], 417 428 $ctx['billing_zp'], 418 429 $ctx['billing_ctry'], 419 $order_stats_ok 430 $order_stats_ok, 431 $ctx['page_url'], 432 $ctx['page_referrer'], 433 $ctx['user_agent'] 420 434 ); 421 435 } … … 479 493 $billing_st = $this->hasher->norm_str($order->get_billing_state()); 480 494 $billing_zp = $this->hasher->norm_zip($order->get_billing_postcode()); 495 $billing_street = $this->hasher->norm_street($order->get_billing_address_1() . ' ' . $order->get_billing_address_2()); 481 496 482 497 $em_h = $this->hasher->h256_or_null($billing_email); … … 487 502 $st_h = $this->hasher->h256_or_null($billing_st); 488 503 $zp_h = $this->hasher->h256_or_null($billing_zp); 504 $street_h = $this->hasher->h256_or_null($billing_street); 489 505 $country_h = $this->hasher->h256_or_null($billing_ctry); 490 506 $external_id_h = $sid ? $this->hasher->h256_or_null($sid) : null; … … 530 546 'st_h' => $st_h, 531 547 'zp_h' => $zp_h, 548 'street_h' => $street_h ?? null, 532 549 'country_h' => $country_h, 533 550 'external_id_h' => $external_id_h, … … 535 552 'client_ua' => $client_ua, 536 553 'user_id' => $user_id, 554 'page_url' => (string) $order->get_meta('_ws_page_url'), 555 'page_referrer' => (string) $order->get_meta('_ws_page_referrer'), 556 'user_agent' => (string) $order->get_meta('_ws_user_agent') ?: $client_ua, 537 557 ]; 538 558 } … … 541 561 * Send GA4 purchase event. 542 562 */ 543 private function send_ga4_purchase($order, $order_id, $items, $currency, $value, $event_id, $ga_cid, $ga_sid, $ga_sno, $gclid, $gbraid, $wbraid, $user_id, $em_h, $ph_h, $fn_h, $ln_h, $ billing_city, $billing_st, $billing_zp, $billing_ctry, $order_stats_ok)563 private function send_ga4_purchase($order, $order_id, $items, $currency, $value, $event_id, $ga_cid, $ga_sid, $ga_sno, $gclid, $gbraid, $wbraid, $user_id, $em_h, $ph_h, $fn_h, $ln_h, $street_h, $billing_city, $billing_st, $billing_zp, $billing_ctry, $order_stats_ok, $page_url = '', $page_referrer = '', $user_agent = '') 544 564 { 545 565 $event_name = TrackSharp_Features::ga4_purchase_event_name(); … … 560 580 'sha256_first_name' => $fn_h ?: null, 561 581 'sha256_last_name' => $ln_h ?: null, 582 'sha256_street' => $street_h ?: null, 562 583 'city' => $billing_city ?: null, 563 584 'region' => $billing_st ?: null, … … 599 620 'event_id' => $event_id, 600 621 'engagement_time_msec' => 1, 601 'ga_session_id' => $ga_sid ?: null, 602 'ga_session_number' => $ga_sno ?: null, 622 'session_id' => $ga_sid ?: null, 623 'session_number' => $ga_sno ?: null, 624 'page_location' => $page_url ?: null, 625 'page_referrer' => $page_referrer ?: null, 626 'user_agent' => $user_agent ?: null, 627 'ua' => $user_agent ?: null, 603 628 ] + (TrackSharp_Features::is_google_ads_available() ? array_filter([ 604 629 'gclid' => $gclid ?: null, -
tracksharp/trunk/readme.txt
r3444685 r3452023 1 1 === TrackSharp: Server-Side GA4 Tracking + Attribution Audit for WooCommerce === 2 Contributors: kayavanvliet 2 Contributors: kayavanvliet, freemius 3 3 Tags: woocommerce, ga4, server-side tracking, google ads, facebook pixel 4 4 Requires at least: 6.1 5 5 Tested up to: 6.9 6 6 Requires PHP: 7.4 7 Stable tag: 1.2. 77 Stable tag: 1.2.8 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 106 106 == Changelog == 107 107 108 = 1.2.8 = 109 * New: Referral preservation to improve GA4 attribution accuracy. 110 * New: All e-commerce events now sent via browser when reporting_first mode is enabled. 111 * Improved: Browser-side event logging for all events. 112 * Improved: client_id handling across events. 113 108 114 = 1.2.7 = 109 115 * New: GA4 Purchase Mode setting (server-only vs browser + server backup; Google tag required for best GA4 attribution). … … 126 132 127 133 == Upgrade Notice == 134 135 = 1.2.8 = 136 Improved GA4 attribution with referral preservation and enhanced browser-side e-commerce tracking 128 137 129 138 = 1.2.7 =
Note: See TracChangeset
for help on using the changeset viewer.