Shulman UTM Attribution for Elementor

Description

Tracks attribution data from submitted fields, cookie snapshots, first-click cookies, server-side fallback, and WooCommerce attribution in a defined source-of-truth hierarchy.

Features:
* Elementor lead capture with attribution snapshot
* WooCommerce order attribution support
* First-touch and last-touch source tracking
* Leads dashboard with filters and CSV export
* Stores WooCommerce customer phone numbers alongside attribution data
* Backward compatibility for legacy cookies, options, and meta keys

Installation

  1. Upload the plugin ZIP in WordPress admin or copy the plugin folder to /wp-content/plugins/.
  2. Activate the plugin.
  3. Configure settings from the plugin admin screen.

Reviews

Read all 1 review

Contributors & Developers

“Shulman UTM Attribution for Elementor” is open source software. The following people have contributed to this plugin.

Contributors

Changelog

1.10.20

  • Removed the remaining Plugin Check warnings in the leads table by inlining the final count/items SQL execution path instead of passing intermediate query-string variables to $wpdb methods.
  • Kept the safe prepared-query flow, source filter compatibility, sorting, pagination, and caching introduced in 1.10.19.

1.10.19

  • Refactored the leads table query builder to use a single prepared-query flow for search, source filters, form filters, sorting, and pagination.
  • Fixed the malformed SQL / placeholder mismatches that triggered Plugin Check prepared SQL errors and unescaped DB parameter warnings in class-leads-table.php.
  • Added object-cache wrapping for lead count and lead list queries to address direct-query caching guidance in the admin table.

1.10.18

  • Fix: extracted direct/untracked classification into a private classify_missing_attribution() method. Both build_lead_snapshot() (Elementor lead path) and save_utm_to_order() (WooCommerce path) now call this shared method, ensuring consistent behaviour. The WooCommerce built-in attribution signal (wc_source) is included as a signal on the WooCommerce path and correctly absent on the Elementor lead path.
  • Fix: attribution confidence resolved by resolve_attribution_dataset() is now preserved through build_lead_snapshot() when meaningful attribution exists. Confidence is only overridden in the direct/untracked branch.
  • Fix: extracted a private get_known_referrer_map() method. Both resolve_source_from_referrer() and server_side_fallback() use this single map, eliminating the divergence between the two functions. Added missing platforms from resolve_source_from_referrer(): youtu.be, t.me, telegram.me, reddit, pinterest, snapchat, duckduckgo, baidu, yandex.
  • Fix: server_side_fallback() now matches referrers against the parsed host only (not strpos() on the full URL). This prevents false positives where a domain like notgoogle.com could have matched the ‘google.’ pattern.
  • Fix: removed the incorrect $ute_direct_count variable from leads-page.php (it was computed as total minus tracked, which double-counts both direct and untracked together). All percentage calculations and template references already used the correct $ute_direct_only_count and $ute_untracked_count from dedicated DB queries.
  • Fix: lead deletion in leads-page.php now performs wp_safe_redirect() + exit after a successful delete. The ?action=delete&lead_id= URL is no longer left in the browser’s address bar. The success notice is displayed on the next page load via a short-lived user transient.

1.10.17

  • Fix: replaced single-line phpcs:ignore comments with phpcs:disable / phpcs:enable blocks around the two multiline $wpdb->get_var() / $wpdb->prepare() queries that span multiple lines. The previous inline ignore was placed on the SQL string line but PHPCS was flagging the get_var() and prepare() lines above it — the disable/enable block now covers the entire multiline statement correctly.

1.10.16

  • Fix: added phpcs:ignore annotations to all dashboard query lines that reference $source_filter_sql. PHPCS static analysis cannot trace the variable origin and incorrectly flagged it as an unescaped or unprepared DB parameter. The value is derived entirely from hardcoded strings inside build_source_filter_clause() — no user input reaches the SQL fragment directly.
  • Fix: updated “Tested up to” header in readme.txt from 6.9 to 7.0.

1.10.15

  • Feature: resizable columns on the Attribution Dashboard leads table. Column widths are persisted in localStorage and survive page refresh. A resize handle appears on each column header; drag left/right to resize. Minimum column width: 60 px. Horizontal scroll is preserved. Sorting, filtering, bulk actions, pagination, CSV export, and column visibility are all unaffected. localStorage access is wrapped in try/catch so the table remains fully usable when localStorage is unavailable (Private Browsing, restricted environments).
  • Improvement: Form column in the leads table now displays plain text instead of a clickable filter link. Filtering by form is still available through the Form dropdown above the table.
  • Feature: paid Meta source normalisation. When utm_medium indicates paid traffic (paid, cpc, paid_social, ppc, display), source aliases fb / facebook / ig / instagram are normalised to the canonical value “meta” before storage. Organic Facebook and Instagram traffic is intentionally NOT normalised — platform-level attribution is preserved for organic sources. Implemented as a single reusable helper normalize_paid_meta_source($source, $medium) called from the attribution processing layer, ensuring consistent output across DB storage, Elementor hidden fields, WooCommerce order meta, webhook payloads, and CSV export.
  • Improvement: normalisation applied in both required places — at the end of resolve_attribution_dataset() covering utm_source, last_touch_source, and first_touch_source; and in build_lead_snapshot() when setting first_touch_source from the FC snapshot, using the first-click medium as the paid-intent signal.
  • Improvement: Attribution Dashboard source filter is backward-compatible with pre-normalisation records. Filtering by “meta” returns both new canonical “meta” records and legacy paid records stored as fb / facebook / ig / instagram. Legacy paid aliases are merged under a single “Meta (paid)” entry in the source dropdown.
  • Fix: PHPCS/WPCS — replaced unreliable inline phpcs:ignore comments with a phpcs:disable / phpcs:enable block in enqueue_admin_assets() around the reads of $_GET[‘page’] and $_GET[‘post_type’]. Added an explanatory comment confirming these are WordPress admin routing parameters, not user-submitted form data, and that nonce verification is not applicable.

1.10.14

  • Fix: corrected inline PHPCS suppression placement for WordPress.Security.NonceVerification.Recommended warnings.
  • Improvement: moved phpcs:ignore comments directly to the relevant $_GET access lines so static analysis correctly recognises the intentional nonce verification flow.
  • No functional plugin behavior changes.

1.10.13

  • Fix: CSV export now correctly sends a downloadable file instead of printing raw CSV text into the admin page.
  • Fix: Export logic moved from the admin page callback (leads-page.php) to an admin_init hook in the main plugin class. This ensures HTTP response headers are set before WordPress outputs any HTML, which is the only reliable way to trigger a file download in the WordPress admin.
  • Fix: Added ob_end_clean() loop before sending CSV headers to flush any output already buffered by WordPress or other plugins.
  • Fix: UTF-8 BOM is now written via fprintf() instead of echo, avoiding encoding issues.
  • Fix: echo "" (a double-encoded BOM) replaced with the correct "\xEF\xBB\xBF" BOM byte sequence.
  • Improvement: leads-page.php now contains an early-return guard for export requests, preventing any HTML from leaking into the response if the admin_init handler is somehow bypassed.
  • Fix: admin-export-orders.js was an empty IIFE stub — the WooCommerce orders Export CSV button was unresponsive on click. Added a delegated click handler that builds and submits a POST form to admin-post.php with the correct action and nonce values.

1.10.12

  • Fix: prevent stale “untracked” Elementor hidden-field values from conflicting with the final server-side attribution snapshot.
  • Fix: ensure webhook / CRM / Google Sheets attribution data stays consistent with the Leads Dashboard resolver output.
  • Improvement: hidden attribution fields may now replace weaker “untracked” values with resolved attribution data such as direct, google, facebook, whatsapp, etc.
  • Improvement: “untracked” is now preserved only for real tracking failures or fully missing attribution signals.
  • Added: should_overwrite_hidden_attribution_field() helper — centralises the overwrite decision for attribution field enrichment.

1.10.11

  • Feature: automatic best-effort enrichment of Elementor hidden attribution fields
    before all form actions run (Webhook, Email, CRM, Elementor Submissions, etc.).
  • Enrichment is strictly limited to fields whose Elementor type is “hidden”.
    Visible form fields are never touched under any condition.
  • Two enrichment paths:
    • Known attribution field IDs (utm_source, utm_medium, utm_campaign, utm_content,
      utm_term, gclid, fbclid, referrer, landing_page, conversion_page,
      first_touch_source, last_touch_source, confidence, lc_source, lc_medium,
      lc_campaign, fc_source, attribution_source, attribution_medium) are filled
      from the central resolver (UTM Click ID Referrer UA First Touch
      Direct Untracked).
    • Custom hidden fields whose ID exactly matches a URL query parameter
      (e.g. utm_platform, camp_name, ad_id, adset_name) are filled from that
      parameter’s raw value. No inference or mapping is applied to custom fields.
  • Generic aliases (source, medium, campaign) removed from resolver-based enrichment
    to prevent conflicts with unrelated form fields.
  • Existing field values are never overwritten — only empty recognised fields are enriched.
  • Strictly non-blocking and fail-safe: if enrichment fails for any reason, form
    submission continues normally. No fatal errors, no stopped submissions, no changes
    to required field validation or user-facing fields.
  • WP_DEBUG mode: logs a short, data-free diagnostic message if enrichment throws.

1.10.10

  • Fix: prevent direct traffic from being incorrectly classified as untracked.
  • Fix: ensure last_touch_source stays consistent with resolved utm_source.
    Previously, when utm_source resolved to “direct”, last_touch_source was incorrectly
    set to “untracked” because is_meaningful_attribution_source() excludes “direct”.
    last_touch_source now correctly mirrors “direct” when that is the resolved state.
  • Fix: improve webhook / Elementor hidden-field data consistency for unattributed traffic.
  • Improvement: clearer separation between direct and untracked states across resolver,
    lead snapshot, and WooCommerce order meta.

1.10.9

  • Fixed: confidence is now explicit when both utm_source AND utm_medium are present in the URL,
    even when fbclid is also present. fbclid no longer downgrades confidence from explicit to inferred
    when full explicit UTM exists. Partial UTM + fbclid correctly stays inferred.
  • Added: com.google.android.googlequicksearchbox referrer is now classified as google / organic.
    This covers traffic from Android Google app surfaces (Google Discover, Search widget, Lens, Google app).
    Previously this could fall through to an untracked or referral classification.

1.10.8

Explicit UTM Click IDs Referrer User-Agent First Touch Direct Untracked.
* Improved: fbclid split logic — fbclid alone now resolves to facebook/social (organic intent).
Only upgrades to facebook/paid when utm_campaign, utm_content, utm_term, or dynamic ad variables are present.
* Fixed: utm_medium=social (and any explicit utm_medium) is never overwritten by click ID inference.
* Improved: direct vs untracked are now clearly separated. “direct” means tracking is functioning
but no attributable source was found. “untracked” is reserved for complete tracking failure
(all signals absent: no UTM, no click ID, no referrer, no UA match, no cookie, no first-touch).
* Added: attribution confidence field (explicit / inferred / fallback / untracked) stored
in DB and order meta.
* Fixed: server_side_fallback no longer returns “untracked” for missing/internal referrers;
the caller now decides direct vs untracked based on full signal inventory.
* Improved: WooCommerce orders column and meta box now distinguish direct vs untracked visually.
* Improved: Dashboard UTM source filter now lists direct and untracked as separate options.
* Updated: DB schema version 1.4.0 — adds confidence column to wp_ute_leads table.
* Updated: utm-tracker.js — fbclid click ID split logic, confidence field in cookie snapshot,
internal navigation returns “direct” not “untracked”.

1.10.7

  • Fixed: fbclid normalization no longer overrides an explicitly set utm_medium=social.
    Previously, any visit with fbclid and utm_medium=social (e.g. an organic Facebook post
    tagged manually) would have its medium silently replaced with paid_social.
    Now only empty or “none” medium values are inferred as paid_social from fbclid presence.

1.10.6

  • Fixed remaining Plugin Check SQL issues (prepared statements compliance)
  • Improved security of database queries.
  • Cleaned uninstall logic for safer execution.

1.10.5

  • Removed remaining prebuilt SQL variables and inlined prepare() calls.
  • Reworked admin queries to avoid interpolated SQL variable patterns flagged by review.
  • Kept phone capture, dashboard display, and CSV export support intact.

1.10.4

  • Fixed admin SQL queries to use prepared statements consistently.
  • Escaped interpolated table names in dashboard queries.
  • Added missing translators comments and refreshed readme metadata.

1.10.3

  • Added plugin readme file for compliance.
  • Improved static analysis compatibility for admin SQL and translations.
  • Kept lead phone field support in DB, admin table, and CSV export.

1.10.2

  • Added lead phone field support.
  • Added phone column to leads screen and CSV export.