Plugin Directory

Changeset 3452023


Ignore:
Timestamp:
02/02/2026 11:49:37 AM (3 weeks ago)
Author:
kayavanvliet
Message:

Improved browser + server fallback mode

Location:
tracksharp/trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • tracksharp/trunk/Tracksharp-tracker.php

    r3444685 r3452023  
    44 * Plugin Name: TrackSharp – Server Side Tracking for WooCommerce
    55 * 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.7
     6 * Version:     1.2.8
    77 * Author:      Kaya van Vliet
    88 * Requires PHP: 7.4
     
    1717// Plugin Constants
    1818// ============================================================================
    19 define( 'TRACKSHARP_VERSION', '1.2.7' );
     19define( 'TRACKSHARP_VERSION', '1.2.8' );
    2020define( 'TRACKSHARP_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    2121define( 'TRACKSHARP_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
  • tracksharp/trunk/assets/tracksharp-tracker.js

    r3444685 r3452023  
    3535  })();
    3636  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  })();
    3753
    3854  // ---- Utils ----
     
    11061122    // with the server-side MP purchase (same event_id + transaction_id).
    11071123    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    
    11151124    try {
    11161125      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      }
    11171134
    11181135      var txidAudit =
     
    11211138          : undefined;
    11221139
    1123       if (isPurchase && allowGa4Browser && CFG.ga4_mid) {
     1140      if (isEcommerce && allowGa4Browser && CFG.ga4_mid) {
    11241141        if (gaEventName === "purchase_sst") {
    11251142          DBG("[WS] GA4 browser purchase suppressed (purchase_sst comparison mode)", {
     
    11591176        }
    11601177       
    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;
    11621186          try {
    11631187            window.gtag("event", gaEventName, gaParams);
    1164             DBG("[WS] GA4 browser purchase sent:", gaEventName, CFG.ga4_mid, gaParams);
    1165             if (name === "purchase") {
     1188            DBG("[WS] GA4 browser event sent:", gaEventName, CFG.ga4_mid, gaParams);
     1189            if (isEcommerce) {
    11661190              gaBrowserFired = true;
    11671191            }
     
    11731197      }
    11741198
    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.
    11761200      // This powers the Dashboard breakdown and lets the server decide whether a server-side
    11771201      // 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") {
    11791205        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, {
    11811209            ga4_browser_sent: gaBrowserFired ? 1 : 0,
    11821210            ga4_attribution_mode: ga4AttributionMode,
     
    12331261        },
    12341262        page_url: pageUrl,
     1263        page_referrer:
     1264          sessionStorage.getItem("ws_initial_referrer") ||
     1265          document.referrer ||
     1266          undefined,
     1267        user_agent: navigator.userAgent,
    12351268        consent: (function () {
    12361269          var tc =
     
    12531286        ecom: payload && payload.ecom ? payload.ecom : undefined,
    12541287        user: userPayload,
     1288        ga4_browser_sent: gaBrowserFired ? 1 : 0,
     1289        ga4_attribution_mode: ga4AttributionMode,
    12551290      };
    12561291
  • tracksharp/trunk/includes/class-tracksharp-loader.php

    r3444685 r3452023  
    137137        // WooCommerce
    138138        $this->woocommerce->register_hooks();
     139        // Referral preservation (TAGGRS method 4)
     140        add_action( 'wp_head', [$this, 'capture_initial_referrer'], 1 );
    139141        // Admin
    140142        if ( is_admin() ) {
     
    287289    }
    288290
     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
    289312}
  • tracksharp/trunk/includes/class-tracksharp-rest.php

    r3444685 r3452023  
    220220        $ga4_result = ['ok' => false, 'reason' => 'ga4 blocked by consent'];
    221221        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            }
    223250        }
    224251
     
    278305        $st_h = isset($addr['region']) ? $this->hasher->h256_or_null($this->hasher->norm_str($addr['region'])) : ($addr['region_hashed'] ?? null);
    279306        $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);
    280308
    281309        $country_h = $country_raw ? $this->hasher->h256_or_null($country_raw) : ($addr['country_hashed'] ?? null);
     
    291319            'st' => $st_h,
    292320            'zp' => $zp_h,
     321            'street' => $street_h,
    293322            'country' => $country_h,
    294323            'country_raw' => $country_raw,
     
    323352            'sha256_first_name' => $hashed['fn'] ?: null,
    324353            'sha256_last_name' => $hashed['ln'] ?: null,
     354            'sha256_street' => $hashed['street'] ?: null,
    325355            'city' => $city,
    326356            'region' => $region,
     
    368398     * @param string $currency     Currency code.
    369399     * @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.
    373406     * @return array Result.
    374407     */
    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 = '')
    376409    {
    377410        $params = [
     
    381414            'event_id' => $event_id,
    382415            '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
    383420        ];
    384421
    385422        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'];
    387424        }
    388425        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'];
    390427        }
    391428
  • tracksharp/trunk/includes/class-tracksharp-woocommerce.php

    r3444685 r3452023  
    140140            $order->update_meta_data('_ws_wbraid', sanitize_text_field(wp_unslash($_COOKIE['ws_wbraid'])));
    141141        }
     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);
    142152
    143153        // --- GA4: Prefer real GA cookies over TrackSharp fallbacks ---
     
    413423            $ctx['fn_h'],
    414424            $ctx['ln_h'],
     425            $ctx['street_h'],
    415426            $ctx['billing_city'],
    416427            $ctx['billing_st'],
    417428            $ctx['billing_zp'],
    418429            $ctx['billing_ctry'],
    419             $order_stats_ok
     430            $order_stats_ok,
     431            $ctx['page_url'],
     432            $ctx['page_referrer'],
     433            $ctx['user_agent']
    420434        );
    421435    }
     
    479493            $billing_st = $this->hasher->norm_str($order->get_billing_state());
    480494            $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());
    481496
    482497            $em_h = $this->hasher->h256_or_null($billing_email);
     
    487502            $st_h = $this->hasher->h256_or_null($billing_st);
    488503            $zp_h = $this->hasher->h256_or_null($billing_zp);
     504            $street_h = $this->hasher->h256_or_null($billing_street);
    489505            $country_h = $this->hasher->h256_or_null($billing_ctry);
    490506            $external_id_h = $sid ? $this->hasher->h256_or_null($sid) : null;
     
    530546            'st_h' => $st_h,
    531547            'zp_h' => $zp_h,
     548            'street_h' => $street_h ?? null,
    532549            'country_h' => $country_h,
    533550            'external_id_h' => $external_id_h,
     
    535552            'client_ua' => $client_ua,
    536553            '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,
    537557        ];
    538558    }
     
    541561     * Send GA4 purchase event.
    542562     */
    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 = '')
    544564    {
    545565        $event_name = TrackSharp_Features::ga4_purchase_event_name();
     
    560580                'sha256_first_name' => $fn_h ?: null,
    561581                'sha256_last_name' => $ln_h ?: null,
     582                'sha256_street' => $street_h ?: null,
    562583                'city' => $billing_city ?: null,
    563584                'region' => $billing_st ?: null,
     
    599620                        'event_id' => $event_id,
    600621                        '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,
    603628                    ] + (TrackSharp_Features::is_google_ads_available() ? array_filter([
    604629                            'gclid' => $gclid ?: null,
  • tracksharp/trunk/readme.txt

    r3444685 r3452023  
    11=== TrackSharp: Server-Side GA4 Tracking + Attribution Audit for WooCommerce ===
    2 Contributors: kayavanvliet
     2Contributors: kayavanvliet, freemius
    33Tags: woocommerce, ga4, server-side tracking, google ads, facebook pixel
    44Requires at least: 6.1
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 1.2.7
     7Stable tag: 1.2.8
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    106106== Changelog ==
    107107
     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
    108114= 1.2.7 =
    109115* New: GA4 Purchase Mode setting (server-only vs browser + server backup; Google tag required for best GA4 attribution).
     
    126132
    127133== Upgrade Notice ==
     134
     135= 1.2.8 =
     136Improved GA4 attribution with referral preservation and enhanced browser-side e-commerce tracking
    128137
    129138= 1.2.7 =
Note: See TracChangeset for help on using the changeset viewer.