Plugin Directory

Changeset 3633752


Ignore:
Timestamp:
08/04/2026 06:06:25 AM (3 months ago)
Author:
auxanoglobalservices14
Message:

Release -v 2.2.9

Location:
utm-tracker-for-gravity-forms/trunk
Files:
1 added
10 edited

Legend:

Unmodified
Added
Removed
  • utm-tracker-for-gravity-forms/trunk/CHANGELOG.md

    r3606943 r3633752  
    22
    33All notable changes to **UTM Tracker for Gravity Forms** are documented in this file.
     4
     5## [2.2.9] — 2026-07-10
     6
     7### Added
     8
     9- **UTM merge tags for Gravity Forms notifications and confirmations** (`includes/Core/MergeTags.php`):
     10  - `{utm_tracker:<key>}` is registered for every supported tracking value (via `GravityFormsInjector::get_injectable_groups()`, so the drop-down only offers actively tracked keys), plus `{utm_tracker:summary}` which renders the whole attribution block — bold labels + `<br />` in HTML format, plain `Label: value` lines in text format.
     11  - Registered through `gform_custom_merge_tags` (form editor drop-down) and resolved through `gform_replace_merge_tags`, so the tags work in notification subjects and bodies, confirmations, and anywhere else Gravity Forms processes merge tags.
     12  - Resolution order: the entry's hidden field whose parameter name (`inputName`) matches the key wins; otherwise the value falls back to the visitor's live tracked value via `TrackerValues::get_value()` with `FieldRegistry::resolve_cookie()` alias handling — so tags resolve even on forms with no tracking fields.
     13  - Honors Gravity Forms' `$url_encode` and `$esc_html` arguments for scalar values; the summary escapes each value individually so its own markup survives. Unknown keys replace with an empty string, matching core GF behavior.
     14  - New filter `utgf_merge_tag_value( $value, $key, $form, $entry )` to adjust a resolved value before output.
     15  - Covered by a 14-check functional test, including an end-to-end run through `GFCommon::replace_variables()`.
     16
     17### Improved
     18
     19- **Tracking tab redesigned for scannability** (`SettingsPage.php`, `admin.css`): an "everything below is automatic" info strip on top; the six UTM parameters are now definition cards with plain-English meanings and examples (`utm_source` — "Where the traffic comes from — google, facebook, newsletter"); the eight click IDs are grouped into platform cards (Google Ads, Meta, Microsoft, TikTok, LinkedIn, X/Twitter) with count pills on both section headings and an automatic "Other Platforms" bucket for any future keys.
     20- **Attribution Model redesigned as professional selection cards** (`SettingsPage.php`, `admin.css`): each model is a selectable card with an icon, a "Recommended" badge on Last Touch, a visual visitor-journey example (Google Ads → Facebook → Form submit) highlighting in green which campaign gets credit under that model, a "Best for" line, a visible selected state with check icon and focus ring, and a reassurance note that switching is non-destructive and the First/Last Touch summary fields are always captured regardless. Same radio inputs underneath — no settings/backend changes.
     21- **Dynamic Population Mapping Matrix rebuilt from the live field registry** (`SettingsPage.php` Gravity Forms tab): previously it listed only built-in URL keys plus aliases — custom parameters were missing entirely and every aliased field carried a wrong "Visitor / Session" badge. Now it renders from `GravityFormsInjector::get_injectable_groups()`: one table per real category (UTM, Click IDs, Page & Landing, Attribution, Visitor & Session, Custom Parameters) with proper field labels, the exact Parameter Name Key, the stored cookie name when it differs, count pills per group and in the card heading, and a note that the matrix reflects currently-enabled settings. The Integration Guide's numbered steps also moved from inline styles to shared `.utgf-steps` CSS classes.
     22- **Custom Parameters supports bulk workflows in the pill editor**: pasting a list (newline / comma / semicolon / space separated) into the tag input adds every valid parameter in one go with an "Added X, skipped Y (invalid, built-in, or duplicate)" summary; Enter on multi-token input does the same. A "Bulk edit (one per line)" toggle reveals the classic textarea — kept in live two-way sync with the pills — for users who prefer the original editing style. The permanent helper line ("Type one parameter and press Enter…") shows by default and returns after any validation message.
     23- **Custom Parameters upgraded to a tag-style pill editor** (`SettingsPage.php`, `admin.js`, `admin.css`): parameters render as removable pills with an inline "type a parameter and press Enter" input; live validation with friendly messages (format, built-in/reserved keys via a JSON reserved-list from `Parameters::utm_keys()` + `click_id_keys()`, duplicates); Backspace on the empty input removes the last pill; a live count pill sits on the section heading; "remember to Save Changes" hints after every add/remove. Progressive enhancement — the original textarea remains the submitted field, kept in sync and shown as-is when JavaScript is unavailable, so saving is byte-identical to before.
     24- **Custom Parameters now suggests the globally popular parameters** (`SettingsPage.php`, `admin.js`, `admin.css`): a "Popular parameters — click to add" chip row above the textarea, grouped by use case — Affiliate & referral (`ref`, `affiliate`, `partner`, `subid`), Promotions (`coupon`, `promo`, `discount`), Google Ads ValueTrack (`keyword`, `matchtype`, `adgroup`, `creative`, `placement`), Sales & email (`salesperson`, `campaign_owner`, `newsletter`). Clicking a chip appends the parameter to the textarea; chips already present show a green check and are inert. State refreshes live as the textarea is edited.
     25- **Developer tab now documents Merge Tags first** (`includes/Admin/SettingsPage.php`): a new "Merge Tags" sub-tab — shown first and active by default since it needs no coding — with copy-ready examples (subject line, `{utm_tracker:summary}`, individual and custom-parameter tags) and the fallback-behavior note. The hooks tables are now complete: added `utgf_after_subscribe` (action) plus `utgf_field_registry`, `utgf_merge_tag_value`, `utgf_process_subscription`, `utgf_subscription_endpoint`, and `utgf_subscription_api_key` (filters).
     26- **Debug page is now self-explanatory for non-technical users** (`includes/Admin/DebugPage.php`): a new "How to test tracking in 3 steps" card at the top with a one-click **Simulate Campaign Visit** button (homepage + sample UTM parameters), and plain-language descriptions on all four diagnostic sections explaining what each shows and why it may legitimately be empty on a fresh browser or admin URL. Fixed the "Stored Tracking Cookies" heading capitalization. The Cookies page (`CookiesPage.php`) description now clarifies that the list is the admin's own per-browser test view.
     27
     28### Notes
     29
     30- No changes to tracking, attribution, cookies, REST/JS APIs, shortcodes, or Gravity Forms field population — fully backward compatible.
    431
    532## [2.2.8] — 2026-07-09
  • utm-tracker-for-gravity-forms/trunk/README.md

    r3606943 r3633752  
    11# UTM Tracker for Gravity Forms
    22
    3 **Version:** 2.2.8 
     3**Version:** 2.2.9 
    44**Requires:** WordPress 5.0+, PHP 7.4+, Gravity Forms 
    55**License:** GPLv2 or later
    … …  
    251251---
    252252
     253## Merge tags (Gravity Forms notifications & confirmations)
     254
     255Every tracked value is available as a merge tag: `{utm_tracker:<key>}` — for example `{utm_tracker:utm_source}`, `{utm_tracker:gclid}`, `{utm_tracker:traffic_channel}`, `{utm_tracker:landing_page}`, or any custom parameter. `{utm_tracker:summary}` renders the lead's full attribution block (labels + values).
     256
     257- Tags appear in the merge tag drop-down of the form editor and work in notification subjects, bodies, and confirmations.
     258- Values resolve from the entry's mapped hidden field first, falling back to the visitor's live tracked value — so the tags work even on forms without tracking fields.
     259- Filter a resolved value with `utgf_merge_tag_value( $value, $key, $form, $entry )`.
     260
     261Example notification subject: `New lead — {utm_tracker:utm_source} / {utm_tracker:utm_medium}`
     262
     263---
     264
    253265## Developer hooks
    254266
    … …  
    274286| `utm_tracker_ai_search_mediums` | UTM medium values for AI Search detection |
    275287| `utm_tracker_ai_search_domains` | Referrer hostnames for AI Search detection |
     288| `utgf_merge_tag_value` | Adjust a resolved `{utm_tracker:*}` merge tag value before output |
    276289| `utgf_process_subscription` | Let an external provider handle the wizard email signup (return true/WP_Error, or null to continue) |
    277290| `utgf_subscription_endpoint` | Override the newsletter endpoint URL the signup posts to |
  • utm-tracker-for-gravity-forms/trunk/assets/css/admin.css

    r3605522 r3633752  
    37743774  }
    37753775}
     3776
     3777/* Custom Parameters: popular-parameter suggestion chips */
     3778.utgf-param-suggest {
     3779  margin: 0 0 12px;
     3780}
     3781.utgf-param-suggest-group {
     3782  display: flex;
     3783  flex-wrap: wrap;
     3784  align-items: center;
     3785  gap: 6px;
     3786  margin-bottom: 6px;
     3787}
     3788.utgf-param-suggest-label {
     3789  font-size: 12px;
     3790  font-weight: 500;
     3791  color: var(--utgf-text-muted);
     3792  min-width: 170px;
     3793}
     3794.utgf-param-chip {
     3795  border: 1px solid var(--utgf-border, #e2e8f0);
     3796  background: #fff;
     3797  border-radius: 999px;
     3798  padding: 2px 12px;
     3799  font-size: 12px;
     3800  font-family: Consolas, Monaco, monospace;
     3801  color: var(--utgf-text);
     3802  cursor: pointer;
     3803  transition: border-color 0.15s ease, color 0.15s ease;
     3804}
     3805.utgf-param-chip:hover {
     3806  border-color: var(--utgf-primary);
     3807  color: var(--utgf-primary);
     3808}
     3809.utgf-param-chip.is-added {
     3810  background: var(--utgf-success-light, #ecfdf5);
     3811  border-color: var(--utgf-success, #16a34a);
     3812  color: var(--utgf-success, #16a34a);
     3813  cursor: default;
     3814}
     3815.utgf-param-chip.is-added::after {
     3816  content: " \2713";
     3817}
     3818
     3819/* Attribution Model: selectable model cards */
     3820.utgf-attr-cards {
     3821  display: grid;
     3822  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
     3823  gap: 16px;
     3824  margin-top: 16px;
     3825}
     3826.utgf-attr-card-label {
     3827  display: block;
     3828  position: relative;
     3829  cursor: pointer;
     3830}
     3831.utgf-attr-card-label input[type="radio"] {
     3832  position: absolute;
     3833  opacity: 0;
     3834  pointer-events: none;
     3835}
     3836.utgf-attr-card {
     3837  height: 100%;
     3838  box-sizing: border-box;
     3839  border: 2px solid var(--utgf-border, #e2e8f0);
     3840  border-radius: 12px;
     3841  padding: 20px;
     3842  background: #fff;
     3843  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
     3844}
     3845.utgf-attr-card-label:hover .utgf-attr-card {
     3846  border-color: var(--utgf-primary);
     3847}
     3848.utgf-attr-card-label input:checked + .utgf-attr-card {
     3849  border-color: var(--utgf-primary);
     3850  background: #f8faff;
     3851  box-shadow: 0 0 0 3px rgba(44, 75, 255, 0.12);
     3852}
     3853.utgf-attr-card-label input:focus-visible + .utgf-attr-card {
     3854  outline: 2px solid var(--utgf-primary);
     3855  outline-offset: 2px;
     3856}
     3857.utgf-attr-card-head {
     3858  display: flex;
     3859  align-items: center;
     3860  gap: 8px;
     3861  margin-bottom: 10px;
     3862}
     3863.utgf-attr-card-icon {
     3864  width: 34px;
     3865  height: 34px;
     3866  flex-shrink: 0;
     3867  border-radius: 8px;
     3868  background: var(--utgf-bg, #f8fafc);
     3869  display: inline-flex;
     3870  align-items: center;
     3871  justify-content: center;
     3872  color: var(--utgf-primary);
     3873}
     3874.utgf-attr-card-icon svg {
     3875  width: 18px;
     3876  height: 18px;
     3877}
     3878.utgf-attr-card-title {
     3879  font-size: 15px;
     3880  font-weight: 600;
     3881  color: var(--utgf-text);
     3882}
     3883.utgf-attr-card-check {
     3884  margin-left: auto;
     3885  color: var(--utgf-primary);
     3886  opacity: 0;
     3887  transition: opacity 0.15s ease;
     3888  display: inline-flex;
     3889}
     3890.utgf-attr-card-check svg {
     3891  width: 20px;
     3892  height: 20px;
     3893}
     3894.utgf-attr-card-label input:checked + .utgf-attr-card .utgf-attr-card-check {
     3895  opacity: 1;
     3896}
     3897.utgf-attr-card-desc {
     3898  margin: 0 0 14px;
     3899  font-size: 13px;
     3900  line-height: 1.6;
     3901  color: var(--utgf-text-muted);
     3902}
     3903.utgf-attr-journey {
     3904  display: flex;
     3905  align-items: center;
     3906  flex-wrap: wrap;
     3907  gap: 6px;
     3908  padding: 10px 12px;
     3909  background: var(--utgf-bg, #f8fafc);
     3910  border: 1px solid var(--utgf-border, #e2e8f0);
     3911  border-radius: 8px;
     3912  margin-bottom: 10px;
     3913}
     3914.utgf-attr-step {
     3915  font-size: 12px;
     3916  padding: 3px 10px;
     3917  border-radius: 999px;
     3918  background: #fff;
     3919  border: 1px solid var(--utgf-border, #e2e8f0);
     3920  color: var(--utgf-text-muted);
     3921  white-space: nowrap;
     3922}
     3923.utgf-attr-step.is-credited {
     3924  background: var(--utgf-success-light, #ecfdf5);
     3925  border-color: var(--utgf-success, #16a34a);
     3926  color: var(--utgf-success, #16a34a);
     3927  font-weight: 600;
     3928}
     3929.utgf-attr-step.utgf-attr-submit {
     3930  border-style: dashed;
     3931}
     3932.utgf-attr-arrow {
     3933  color: var(--utgf-text-muted);
     3934  font-size: 12px;
     3935}
     3936.utgf-attr-credit {
     3937  margin: 0 0 6px;
     3938  font-size: 12px;
     3939  font-weight: 600;
     3940  color: var(--utgf-success, #16a34a);
     3941}
     3942.utgf-attr-best {
     3943  margin: 0;
     3944  font-size: 12px;
     3945  color: var(--utgf-text-muted);
     3946}
     3947
     3948/* Tracking tab: info note, count pills, UTM definition cards, click-ID platform grid */
     3949.utgf-track-note {
     3950  display: flex;
     3951  align-items: flex-start;
     3952  gap: 10px;
     3953  padding: 12px 14px;
     3954  margin: 12px 0 6px;
     3955  background: #eff6ff;
     3956  border: 1px solid #bfdbfe;
     3957  border-radius: 8px;
     3958  font-size: 13px;
     3959  line-height: 1.6;
     3960  color: #1e40af;
     3961}
     3962.utgf-track-note svg {
     3963  width: 16px;
     3964  height: 16px;
     3965  flex-shrink: 0;
     3966  margin-top: 3px;
     3967}
     3968.utgf-count-pill {
     3969  display: inline-block;
     3970  margin-left: 6px;
     3971  padding: 1px 9px;
     3972  border-radius: 999px;
     3973  background: var(--utgf-bg, #f8fafc);
     3974  border: 1px solid var(--utgf-border, #e2e8f0);
     3975  font-size: 11px;
     3976  font-weight: 600;
     3977  color: var(--utgf-text-muted);
     3978  vertical-align: middle;
     3979}
     3980.utgf-param-defs {
     3981  display: grid;
     3982  grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
     3983  gap: 8px 16px;
     3984  margin: 10px 0 6px;
     3985}
     3986.utgf-param-def {
     3987  display: flex;
     3988  align-items: baseline;
     3989  gap: 10px;
     3990  padding: 9px 12px;
     3991  border: 1px solid var(--utgf-border, #e2e8f0);
     3992  border-radius: 8px;
     3993  background: #fff;
     3994}
     3995.utgf-param-def code {
     3996  background: var(--utgf-bg, #f8fafc);
     3997  padding: 2px 8px;
     3998  border-radius: 5px;
     3999  font-size: 12px;
     4000  font-weight: 600;
     4001  white-space: nowrap;
     4002  color: var(--utgf-primary);
     4003}
     4004.utgf-param-def span {
     4005  font-size: 12px;
     4006  color: var(--utgf-text-muted);
     4007  line-height: 1.5;
     4008}
     4009.utgf-platform-grid {
     4010  display: grid;
     4011  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
     4012  gap: 8px;
     4013  margin: 10px 0 6px;
     4014}
     4015.utgf-platform {
     4016  display: flex;
     4017  flex-direction: column;
     4018  gap: 7px;
     4019  padding: 10px 12px;
     4020  border: 1px solid var(--utgf-border, #e2e8f0);
     4021  border-radius: 8px;
     4022  background: #fff;
     4023}
     4024.utgf-platform-name {
     4025  font-size: 12px;
     4026  font-weight: 600;
     4027  color: var(--utgf-text);
     4028}
     4029.utgf-platform-keys {
     4030  display: flex;
     4031  flex-wrap: wrap;
     4032  gap: 4px;
     4033}
     4034.utgf-platform-keys code {
     4035  background: var(--utgf-bg, #f8fafc);
     4036  border: 1px solid var(--utgf-border, #e2e8f0);
     4037  padding: 2px 9px;
     4038  border-radius: 999px;
     4039  font-size: 11.5px;
     4040  color: var(--utgf-text-muted);
     4041}
     4042
     4043/* Custom Parameters: tag-style pill editor */
     4044.utgf-param-editor {
     4045  margin: 4px 0 14px;
     4046  max-width: 720px;
     4047}
     4048.utgf-param-pills {
     4049  display: flex;
     4050  flex-wrap: wrap;
     4051  align-items: center;
     4052  gap: 6px;
     4053  padding: 8px;
     4054  min-height: 46px;
     4055  box-sizing: border-box;
     4056  background: #fff;
     4057  border: 1px solid var(--utgf-border, #e2e8f0);
     4058  border-radius: 8px;
     4059  cursor: text;
     4060  transition: border-color 0.15s ease, box-shadow 0.15s ease;
     4061}
     4062.utgf-param-pills:focus-within {
     4063  border-color: var(--utgf-primary);
     4064  box-shadow: 0 0 0 3px rgba(44, 75, 255, 0.1);
     4065}
     4066.utgf-param-pill {
     4067  display: inline-flex;
     4068  align-items: center;
     4069  gap: 4px;
     4070  background: var(--utgf-bg, #f8fafc);
     4071  border: 1px solid var(--utgf-border, #e2e8f0);
     4072  border-radius: 999px;
     4073  padding: 3px 6px 3px 12px;
     4074}
     4075.utgf-param-pill code {
     4076  background: none;
     4077  padding: 0;
     4078  font-size: 12.5px;
     4079  font-weight: 600;
     4080  color: var(--utgf-text);
     4081}
     4082.utgf-param-pill-remove {
     4083  border: 0;
     4084  background: transparent;
     4085  color: var(--utgf-text-muted);
     4086  font-size: 15px;
     4087  line-height: 1;
     4088  cursor: pointer;
     4089  padding: 2px 7px;
     4090  border-radius: 50%;
     4091  transition: color 0.15s ease, background 0.15s ease;
     4092}
     4093.utgf-param-pill-remove:hover {
     4094  color: #b91c1c;
     4095  background: #fee2e2;
     4096}
     4097.utgf-param-input {
     4098  flex: 1 1 220px;
     4099  min-width: 180px;
     4100  border: 0 !important;
     4101  outline: none !important;
     4102  box-shadow: none !important;
     4103  background: transparent;
     4104  font-size: 13px;
     4105  padding: 4px 6px;
     4106}
     4107.utgf-param-hint {
     4108  margin: 6px 2px 0;
     4109  font-size: 12px;
     4110  color: var(--utgf-text-muted);
     4111  min-height: 18px;
     4112}
     4113.utgf-param-hint.is-error {
     4114  color: #b91c1c;
     4115}
     4116
     4117/* Custom Parameters: bulk edit toggle link */
     4118.utgf-param-bulk-toggle {
     4119  background: none;
     4120  border: 0;
     4121  padding: 0;
     4122  margin: 4px 2px 0;
     4123  font-size: 12px;
     4124  color: var(--utgf-primary);
     4125  cursor: pointer;
     4126  text-decoration: underline;
     4127}
     4128.utgf-param-bulk-toggle:hover {
     4129  color: var(--utgf-primary-hover, #1532d4);
     4130}
     4131
     4132/* Gravity Forms tab: numbered integration steps + mapping matrix groups */
     4133.utgf-steps {
     4134  margin-top: 24px;
     4135  display: flex;
     4136  flex-direction: column;
     4137  gap: 20px;
     4138}
     4139.utgf-step {
     4140  display: flex;
     4141  gap: 16px;
     4142  align-items: flex-start;
     4143}
     4144.utgf-step-num {
     4145  width: 32px;
     4146  height: 32px;
     4147  flex-shrink: 0;
     4148  border-radius: 50%;
     4149  background: var(--utgf-bg, #f1f5f9);
     4150  color: var(--utgf-primary);
     4151  display: flex;
     4152  align-items: center;
     4153  justify-content: center;
     4154  font-weight: 700;
     4155}
     4156.utgf-step-body h3 {
     4157  margin: 0 0 6px;
     4158  font-size: 14px;
     4159  font-weight: 600;
     4160}
     4161.utgf-step-body p {
     4162  margin: 0;
     4163}
     4164.utgf-matrix-group {
     4165  margin: 24px 0 8px;
     4166  font-size: 14px;
     4167}
  • utm-tracker-for-gravity-forms/trunk/assets/js/admin.js

    r3598572 r3633752  
    996996    }
    997997})();
     998
     999/**
     1000 * Custom Parameters: tag-style pill editor + popular-parameter suggestion chips.
     1001 *
     1002 * The visible editor renders each parameter as a removable pill with an
     1003 * inline "type and press Enter" input, validating live against format,
     1004 * built-in (reserved) keys, and duplicates. The original textarea stays as
     1005 * the submitted form field (hidden while JS is active, functional without it)
     1006 * and is kept in sync. Suggestion chips add through the same path and show a
     1007 * check mark once present.
     1008 */
     1009(function () {
     1010    'use strict';
     1011
     1012    function initParamEditor() {
     1013        const textarea = document.getElementById('utgf_custom_parameters');
     1014        const editor = document.getElementById('utgf_param_editor');
     1015        const pillsWrap = document.getElementById('utgf_param_pills');
     1016        const input = document.getElementById('utgf_param_input');
     1017        const hint = document.getElementById('utgf_param_hint');
     1018        const countPill = document.getElementById('utgf_param_count');
     1019        const fallback = document.getElementById('utgf_param_fallback');
     1020        const suggest = document.getElementById('utgf_param_suggestions');
     1021
     1022        if (!textarea || !editor || !pillsWrap || !input) {
     1023            return;
     1024        }
     1025
     1026        let reserved = [];
     1027        try {
     1028            reserved = JSON.parse(editor.dataset.reserved || '[]');
     1029        } catch (err) {
     1030            reserved = [];
     1031        }
     1032
     1033        const defaultHint = editor.dataset.hint || '';
     1034
     1035        function params() {
     1036            return textarea.value
     1037                .split('\n')
     1038                .map(function (line) { return line.trim().toLowerCase(); })
     1039                .filter(Boolean);
     1040        }
     1041
     1042        function setHint(message, isError) {
     1043            if (!hint) {
     1044                return;
     1045            }
     1046            hint.textContent = message || defaultHint;
     1047            hint.classList.toggle('is-error', !!isError && !!message);
     1048        }
     1049
     1050        function renderPills(list) {
     1051            pillsWrap.querySelectorAll('.utgf-param-pill').forEach(function (pill) { pill.remove(); });
     1052
     1053            list.forEach(function (param) {
     1054                const pill = document.createElement('span');
     1055                pill.className = 'utgf-param-pill';
     1056                pill.dataset.param = param;
     1057
     1058                const text = document.createElement('code');
     1059                text.textContent = param;
     1060                pill.appendChild(text);
     1061
     1062                const remove = document.createElement('button');
     1063                remove.type = 'button';
     1064                remove.className = 'utgf-param-pill-remove';
     1065                remove.setAttribute('aria-label', 'Remove ' + param);
     1066                remove.textContent = '×';
     1067                pill.appendChild(remove);
     1068
     1069                pillsWrap.insertBefore(pill, input);
     1070            });
     1071        }
     1072
     1073        function refreshChips(list) {
     1074            if (!suggest) {
     1075                return;
     1076            }
     1077            suggest.querySelectorAll('.utgf-param-chip').forEach(function (chip) {
     1078                chip.classList.toggle('is-added', list.indexOf(chip.dataset.param) !== -1);
     1079            });
     1080        }
     1081
     1082        function renderAll(list) {
     1083            renderPills(list);
     1084            refreshChips(list);
     1085            if (countPill) {
     1086                countPill.textContent = String(list.length);
     1087            }
     1088        }
     1089
     1090        function sync(list) {
     1091            textarea.value = list.join('\n');
     1092            renderAll(list);
     1093        }
     1094
     1095        function addParam(raw) {
     1096            const param = String(raw || '').trim().toLowerCase();
     1097
     1098            if (!param) {
     1099                return false;
     1100            }
     1101            if (!/^[a-z0-9_]+$/.test(param)) {
     1102                setHint('Only lowercase letters, numbers, and underscores are allowed.', true);
     1103                return false;
     1104            }
     1105            if (reserved.indexOf(param) !== -1) {
     1106                setHint('"' + param + '" is a built-in parameter and is already tracked automatically.', true);
     1107                return false;
     1108            }
     1109
     1110            const list = params();
     1111            if (list.indexOf(param) !== -1) {
     1112                setHint('"' + param + '" is already in your list.', true);
     1113                return false;
     1114            }
     1115
     1116            list.push(param);
     1117            sync(list);
     1118            setHint('Added "' + param + '" — remember to click Save Changes.', false);
     1119            return true;
     1120        }
     1121
     1122        function removeParam(param) {
     1123            sync(params().filter(function (p) { return p !== param; }));
     1124            setHint('Removed "' + param + '" — remember to click Save Changes.', false);
     1125        }
     1126
     1127        function bulkAdd(text) {
     1128            const tokens = String(text || '')
     1129                .split(/[\n\r,;\s]+/)
     1130                .map(function (token) { return token.trim().toLowerCase(); })
     1131                .filter(Boolean);
     1132
     1133            if (!tokens.length) {
     1134                return;
     1135            }
     1136
     1137            const list = params();
     1138            let added = 0;
     1139            let skipped = 0;
     1140
     1141            tokens.forEach(function (token) {
     1142                if (/^[a-z0-9_]+$/.test(token) && reserved.indexOf(token) === -1 && list.indexOf(token) === -1) {
     1143                    list.push(token);
     1144                    added++;
     1145                } else {
     1146                    skipped++;
     1147                }
     1148            });
     1149
     1150            sync(list);
     1151            setHint(
     1152                'Added ' + added + ' parameter' + (1 === added ? '' : 's') +
     1153                (skipped ? ', skipped ' + skipped + ' (invalid, built-in, or duplicate)' : '') +
     1154                ' — remember to click Save Changes.',
     1155                false
     1156            );
     1157        }
     1158
     1159        input.addEventListener('keydown', function (event) {
     1160            if (event.key === 'Enter' || event.key === ',') {
     1161                event.preventDefault();
     1162                if (/[\s,;]/.test(input.value.trim())) {
     1163                    bulkAdd(input.value);
     1164                    input.value = '';
     1165                } else if (addParam(input.value)) {
     1166                    input.value = '';
     1167                }
     1168            } else if (event.key === 'Backspace' && input.value === '') {
     1169                const list = params();
     1170                if (list.length) {
     1171                    removeParam(list[list.length - 1]);
     1172                }
     1173            }
     1174        });
     1175
     1176        input.addEventListener('input', function () {
     1177            setHint('', false);
     1178        });
     1179
     1180        // Bulk paste: a pasted list (newline / comma / space separated) becomes
     1181        // pills in one go, with an added/skipped summary. Single tokens paste normally.
     1182        input.addEventListener('paste', function (event) {
     1183            const clipboard = event.clipboardData || window.clipboardData;
     1184            const text = clipboard ? clipboard.getData('text') : '';
     1185
     1186            if (!text || !/[\n\r,;\s]/.test(text.trim())) {
     1187                return;
     1188            }
     1189
     1190            event.preventDefault();
     1191            bulkAdd(text);
     1192            input.value = '';
     1193        });
     1194
     1195        pillsWrap.addEventListener('click', function (event) {
     1196            const remove = event.target.closest('.utgf-param-pill-remove');
     1197            if (remove) {
     1198                removeParam(remove.parentNode.dataset.param);
     1199                input.focus();
     1200                return;
     1201            }
     1202            if (event.target === pillsWrap) {
     1203                input.focus();
     1204            }
     1205        });
     1206
     1207        if (suggest) {
     1208            suggest.addEventListener('click', function (event) {
     1209                const chip = event.target.closest('.utgf-param-chip');
     1210                if (!chip || chip.classList.contains('is-added')) {
     1211                    return;
     1212                }
     1213                addParam(chip.dataset.param);
     1214            });
     1215        }
     1216
     1217        // Bulk edit toggle: reveal the classic one-per-line textarea, kept in
     1218        // live sync with the pills (the textarea is the single source of truth).
     1219        const bulkToggle = document.getElementById('utgf_param_bulk_toggle');
     1220        if (bulkToggle && fallback) {
     1221            bulkToggle.addEventListener('click', function () {
     1222                const opening = 'none' === fallback.style.display;
     1223                fallback.style.display = opening ? '' : 'none';
     1224                bulkToggle.textContent = opening
     1225                    ? (bulkToggle.dataset.labelClose || 'Back to tag view')
     1226                    : (bulkToggle.dataset.labelOpen || 'Bulk edit (one per line)');
     1227                if (opening) {
     1228                    textarea.focus();
     1229                }
     1230            });
     1231
     1232            textarea.addEventListener('input', function () {
     1233                renderAll(params());
     1234                setHint('', false);
     1235            });
     1236        }
     1237
     1238        // Progressive enhancement: show the pill editor, hide the raw textarea.
     1239        editor.style.display = '';
     1240        if (fallback) {
     1241            fallback.style.display = 'none';
     1242        }
     1243        sync(params());
     1244        setHint('', false);
     1245    }
     1246
     1247    if (document.readyState === 'loading') {
     1248        document.addEventListener('DOMContentLoaded', initParamEditor);
     1249    } else {
     1250        initParamEditor();
     1251    }
     1252})();
  • utm-tracker-for-gravity-forms/trunk/includes/Admin/CookiesPage.php

    r3592757 r3633752  
    5959                <div class="utgf-card utgf-cookie-manager-card">
    6060                        <h2><?php esc_html_e( 'Local Cookie Manager', 'utm-tracker-for-gravity-forms' ); ?></h2>
    61                         <p class="description"><?php esc_html_e( 'Inspect and manage tracking cookies stored in your current browser session.', 'utm-tracker-for-gravity-forms' ); ?></p>
     61                        <p class="description"><?php esc_html_e( 'The tracking values saved in YOUR browser from your own visits — the same values your forms would receive if you submitted one right now. Every visitor has their own private set in their own browser; this list is your personal test view, useful for checking captured values and clearing them between tests.', 'utm-tracker-for-gravity-forms' ); ?></p>
    6262
    6363                        <!-- Toolbar (Search, Filter, Count) -->
  • utm-tracker-for-gravity-forms/trunk/includes/Admin/DebugPage.php

    r3592757 r3633752  
    5252                }
    5353
    54                 $debug_url = add_query_arg( 'utgf_debug', '1', home_url( '/' ) );
    55                
     54                $debug_url    = add_query_arg( 'utgf_debug', '1', home_url( '/' ) );
     55                $simulate_url = home_url( '/?utm_source=test_source&utm_medium=test_medium&utm_campaign=test_campaign' );
     56
    5657                // Render layout header
    5758                Layout::render_header( 'debug' );
    … …  
    8990                                </div>
    9091                        </div>
     92                </div>
     93
     94                <!-- How to test (plain-language guide) -->
     95                <div class="utgf-card">
     96                        <h2><?php esc_html_e( 'How to test tracking in 3 steps', 'utm-tracker-for-gravity-forms' ); ?></h2>
     97                        <p class="description"><?php esc_html_e( 'This page shows what the plugin has captured in YOUR browser, as if you were a visitor. A fresh browser shows empty sections — that is normal until you simulate a campaign visit.', 'utm-tracker-for-gravity-forms' ); ?></p>
     98                        <ol style="margin: 12px 0 16px 20px; line-height: 1.8;">
     99                                <li><?php esc_html_e( 'Click "Simulate Campaign Visit" below — it opens your homepage with sample campaign parameters, exactly like a visitor clicking an ad.', 'utm-tracker-for-gravity-forms' ); ?></li>
     100                                <li><?php esc_html_e( 'Come back to this tab and reload this page — the sections below now show the values that were captured and saved.', 'utm-tracker-for-gravity-forms' ); ?></li>
     101                                <li><?php esc_html_e( 'Open a page containing your Gravity Form and submit it — the same values arrive in the entry and in your notification email.', 'utm-tracker-for-gravity-forms' ); ?></li>
     102                        </ol>
     103                        <a class="button button-primary" href="<?php echo esc_url( $simulate_url ); ?>" target="_blank" rel="noopener noreferrer">
     104                                <i data-lucide="rocket" class="utgf-lucide-icon"></i>
     105                                <?php esc_html_e( 'Simulate Campaign Visit', 'utm-tracker-for-gravity-forms' ); ?>
     106                        </a>
    91107                </div>
    92108
    … …  
    119135                                </div>
    120136                                <div class="utgf-collapsible-content">
    121                                         <p class="description"><?php esc_html_e( 'The full URL representing the request parameters detected by the browser.', 'utm-tracker-for-gravity-forms' ); ?></p>
     137                                        <p class="description"><?php esc_html_e( 'The address of the page you are viewing right now. Tracking begins whenever a visitor opens a URL containing campaign parameters (for example ?utm_source=google). This admin URL normally has none — that is expected.', 'utm-tracker-for-gravity-forms' ); ?></p>
    122138                                        <code id="utgf_debug_url_val" style="padding: 10px; font-size: 13px; background: #f8fafc; border: 1px solid #e2e8f0; display: block; overflow-x: auto; color: var(--utgf-text);">URL</code>
    123139                                </div>
    … …  
    131147                                </div>
    132148                                <div class="utgf-collapsible-content">
     149                                        <p class="description"><?php esc_html_e( 'What the plugin remembers about your own visit history in this browser: the first and the most recent campaign that brought you to the site, and the pages you landed on. Empty rows simply mean this browser has no campaign history yet.', 'utm-tracker-for-gravity-forms' ); ?></p>
    133150                                        <table class="utgf-admin-table">
    134151                                                <tbody id="utgf_debug_attribution_list">
    … …  
    146163                                </div>
    147164                                <div class="utgf-collapsible-content">
     165                                        <p class="description"><?php esc_html_e( 'Campaign parameters found in the current page address. This is almost always empty here in the admin — it fills the moment you open any page whose URL contains tracking parameters. Use "Simulate Campaign Visit" above to see it in action.', 'utm-tracker-for-gravity-forms' ); ?></p>
    148166                                        <table class="utgf-admin-table">
    149167                                                <thead>
    … …  
    164182                        <div class="utgf-collapsible-card utgf-expanded" id="utgf_debug_card_cookies" style="display: none;">
    165183                                <div class="utgf-collapsible-header">
    166                                         <span><?php esc_html_e( '4. Stored tracking Cookies', 'utm-tracker-for-gravity-forms' ); ?></span>
    167                                         <i data-lucide="chevron-down" class="utgf-collapsible-toggle-icon"></i>
    168                                 </div>
    169                                 <div class="utgf-collapsible-content">
     184                                        <span><?php esc_html_e( '4. Stored Tracking Cookies', 'utm-tracker-for-gravity-forms' ); ?></span>
     185                                        <i data-lucide="chevron-down" class="utgf-collapsible-toggle-icon"></i>
     186                                </div>
     187                                <div class="utgf-collapsible-content">
     188                                        <p class="description"><?php esc_html_e( 'The values currently saved in your browser — this is exactly what your forms would receive if you submitted one right now. Empty means no campaign visit has happened in this browser yet: click "Simulate Campaign Visit", then come back and reload this page.', 'utm-tracker-for-gravity-forms' ); ?></p>
    170189                                        <table class="utgf-admin-table">
    171190                                                <thead>
  • utm-tracker-for-gravity-forms/trunk/includes/Admin/SettingsPage.php

    r3599816 r3633752  
    377377                                                <p class="description"><?php esc_html_e( 'The following parameters are automatically monitored. Create matching hidden fields in Gravity Forms to automatically fill their values.', 'utm-tracker-for-gravity-forms' ); ?></p>
    378378
    379                                                 <h3><?php esc_html_e( 'Standard UTM Campaign Parameters', 'utm-tracker-for-gravity-forms' ); ?></h3>
    380                                                 <div class="utgf-badge-grid">
    381                                                         <?php foreach ( Parameters::utm_keys() as $key ) : ?>
    382                                                                 <span class="utgf-badge utgf-badge-utm"><?php echo esc_html( $key ); ?></span>
     379                                                <div class="utgf-track-note">
     380                                                        <i data-lucide="info"></i>
     381                                                        <span><?php esc_html_e( 'Everything below is captured automatically whenever it appears in a visitor\'s URL — there is nothing to enable here. To store a value on a form entry, add a matching hidden field (or use One-Click Field Setup on the Gravity Forms tab).', 'utm-tracker-for-gravity-forms' ); ?></span>
     382                                                </div>
     383
     384                                                <h3><?php esc_html_e( 'Standard UTM Campaign Parameters', 'utm-tracker-for-gravity-forms' ); ?> <span class="utgf-count-pill"><?php echo esc_html( (string) count( Parameters::utm_keys() ) ); ?></span></h3>
     385                                                <div class="utgf-param-defs">
     386                                                        <?php
     387                                                        $utgf_utm_descriptions = [
     388                                                                'utm_source'   => __( 'Where the traffic comes from — google, facebook, newsletter', 'utm-tracker-for-gravity-forms' ),
     389                                                                'utm_medium'   => __( 'The marketing channel — cpc, email, social', 'utm-tracker-for-gravity-forms' ),
     390                                                                'utm_campaign' => __( 'The campaign name — summer_sale_2026', 'utm-tracker-for-gravity-forms' ),
     391                                                                'utm_term'     => __( 'The paid search keyword the visitor searched', 'utm-tracker-for-gravity-forms' ),
     392                                                                'utm_content'  => __( 'The ad or link variant — hero_banner_blue', 'utm-tracker-for-gravity-forms' ),
     393                                                                'utm_id'       => __( 'The campaign ID from your ads platform', 'utm-tracker-for-gravity-forms' ),
     394                                                        ];
     395                                                        foreach ( Parameters::utm_keys() as $key ) :
     396                                                                ?>
     397                                                                <div class="utgf-param-def">
     398                                                                        <code><?php echo esc_html( $key ); ?></code>
     399                                                                        <span><?php echo esc_html( $utgf_utm_descriptions[ $key ] ?? '' ); ?></span>
     400                                                                </div>
    383401                                                        <?php endforeach; ?>
    384402                                                </div>
    385403
    386                                                 <h3><?php esc_html_e( 'Advertising Click Identifier Keys', 'utm-tracker-for-gravity-forms' ); ?></h3>
    387                                                 <div class="utgf-badge-grid">
    388                                                         <?php foreach ( Parameters::click_id_keys() as $key ) : ?>
    389                                                                 <span class="utgf-badge utgf-badge-utm"><?php echo esc_html( $key ); ?></span>
     404                                                <h3><?php esc_html_e( 'Advertising Click IDs', 'utm-tracker-for-gravity-forms' ); ?> <span class="utgf-count-pill"><?php echo esc_html( (string) count( Parameters::click_id_keys() ) ); ?></span></h3>
     405                                                <p class="description"><?php esc_html_e( 'Ad platforms add these to the URL automatically when someone clicks your ad — so paid leads are matched to campaigns even when UTMs are missing.', 'utm-tracker-for-gravity-forms' ); ?></p>
     406                                                <div class="utgf-platform-grid">
     407                                                        <?php
     408                                                        $utgf_click_platforms = [
     409                                                                __( 'Google Ads', 'utm-tracker-for-gravity-forms' )           => [ 'gclid', 'gbraid', 'wbraid' ],
     410                                                                __( 'Meta (Facebook & Instagram)', 'utm-tracker-for-gravity-forms' ) => [ 'fbclid' ],
     411                                                                __( 'Microsoft Ads (Bing)', 'utm-tracker-for-gravity-forms' ) => [ 'msclkid' ],
     412                                                                __( 'TikTok Ads', 'utm-tracker-for-gravity-forms' )           => [ 'ttclid' ],
     413                                                                __( 'LinkedIn Ads', 'utm-tracker-for-gravity-forms' )         => [ 'li_fat_id' ],
     414                                                                __( 'X / Twitter Ads', 'utm-tracker-for-gravity-forms' )      => [ 'twclid' ],
     415                                                        ];
     416                                                        $utgf_known_click_keys = array_merge( ...array_values( $utgf_click_platforms ) );
     417
     418                                                        foreach ( $utgf_click_platforms as $utgf_platform => $utgf_platform_keys ) :
     419                                                                ?>
     420                                                                <div class="utgf-platform">
     421                                                                        <span class="utgf-platform-name"><?php echo esc_html( $utgf_platform ); ?></span>
     422                                                                        <span class="utgf-platform-keys">
     423                                                                                <?php foreach ( $utgf_platform_keys as $key ) : ?>
     424                                                                                        <code><?php echo esc_html( $key ); ?></code>
     425                                                                                <?php endforeach; ?>
     426                                                                        </span>
     427                                                                </div>
    390428                                                        <?php endforeach; ?>
     429
     430                                                        <?php $utgf_other_click_keys = array_diff( Parameters::click_id_keys(), $utgf_known_click_keys ); ?>
     431                                                        <?php if ( ! empty( $utgf_other_click_keys ) ) : ?>
     432                                                                <div class="utgf-platform">
     433                                                                        <span class="utgf-platform-name"><?php esc_html_e( 'Other Platforms', 'utm-tracker-for-gravity-forms' ); ?></span>
     434                                                                        <span class="utgf-platform-keys">
     435                                                                                <?php foreach ( $utgf_other_click_keys as $key ) : ?>
     436                                                                                        <code><?php echo esc_html( $key ); ?></code>
     437                                                                                <?php endforeach; ?>
     438                                                                        </span>
     439                                                                </div>
     440                                                        <?php endif; ?>
    391441                                                </div>
    392442
    393                                                 <table class="form-table" role="presentation" style="margin-top: 20px;">
    394                                                         <tr>
    395                                                                 <th scope="row">
    396                                                                         <label for="utgf_custom_parameters"><?php esc_html_e( 'Custom Parameters', 'utm-tracker-for-gravity-forms' ); ?></label>
    397                                                                         <span class="utgf-tooltip-trigger" title="<?php esc_attr_e( 'Define extra query keys to track (like affiliate, promo, ref, partner).', 'utm-tracker-for-gravity-forms' ); ?>">
    398                                                                                 <i data-lucide="help-circle"></i>
    399                                                                         </span>
    400                                                                 </th>
    401                                                                 <td>
    402                                                                         <textarea name="<?php echo esc_attr( Settings::OPTION_KEY ); ?>[custom_parameters]" id="utgf_custom_parameters" rows="6" class="large-text code" placeholder="affiliate&#10;coupon&#10;salesperson&#10;campaign_owner"><?php echo esc_textarea( implode( "\n", (array) $settings['custom_parameters'] ) ); ?></textarea>
    403                                                                         <p class="description"><?php esc_html_e( 'Enter one parameter per line. Only alphanumeric characters and underscores are allowed. Duplicates and built-in parameters are ignored.', 'utm-tracker-for-gravity-forms' ); ?></p>
    404                                                                 </td>
    405                                                         </tr>
    406                                                 </table>
     443                                                <h3 style="margin-top: 28px;"><?php esc_html_e( 'Custom Parameters', 'utm-tracker-for-gravity-forms' ); ?> <span class="utgf-count-pill" id="utgf_param_count"><?php echo esc_html( (string) count( (array) $settings['custom_parameters'] ) ); ?></span></h3>
     444                                                <p class="description"><?php esc_html_e( 'Track your own URL keys — for example ?affiliate=partner_007&coupon=SAVE20. They behave exactly like UTMs: captured on arrival and available in forms, reports, merge tags, and the URL Builder.', 'utm-tracker-for-gravity-forms' ); ?></p>
     445
     446                                                <?php $utgf_param_hint_default = __( 'Type one parameter and press Enter. Only alphanumeric characters and underscores are allowed. Duplicates and built-in parameters are ignored.', 'utm-tracker-for-gravity-forms' ); ?>
     447                                                <div class="utgf-param-editor" id="utgf_param_editor" style="display: none;" data-hint="<?php echo esc_attr( $utgf_param_hint_default ); ?>" data-reserved="<?php echo esc_attr( (string) wp_json_encode( array_merge( Parameters::utm_keys(), Parameters::click_id_keys() ) ) ); ?>">
     448                                                        <div class="utgf-param-pills" id="utgf_param_pills">
     449                                                                <input type="text" id="utgf_param_input" class="utgf-param-input" placeholder="<?php esc_attr_e( 'Type a parameter and press Enter…', 'utm-tracker-for-gravity-forms' ); ?>" autocomplete="off" />
     450                                                        </div>
     451                                                        <p class="utgf-param-hint" id="utgf_param_hint" aria-live="polite"><?php echo esc_html( $utgf_param_hint_default ); ?></p>
     452                                                        <button type="button" class="utgf-param-bulk-toggle" id="utgf_param_bulk_toggle" data-label-open="<?php esc_attr_e( 'Bulk edit (one per line)', 'utm-tracker-for-gravity-forms' ); ?>" data-label-close="<?php esc_attr_e( 'Back to tag view', 'utm-tracker-for-gravity-forms' ); ?>"><?php esc_html_e( 'Bulk edit (one per line)', 'utm-tracker-for-gravity-forms' ); ?></button>
     453                                                </div>
     454
     455                                                <div class="utgf-param-suggest" id="utgf_param_suggestions">
     456                                                                                <p class="description" style="margin: 0 0 8px;"><?php esc_html_e( 'Popular parameters — click to add:', 'utm-tracker-for-gravity-forms' ); ?></p>
     457                                                                                <?php
     458                                                                                $utgf_param_suggestions = [
     459                                                                                        __( 'Affiliate & referral', 'utm-tracker-for-gravity-forms' )   => [ 'ref', 'affiliate', 'partner', 'subid' ],
     460                                                                                        __( 'Promotions', 'utm-tracker-for-gravity-forms' )            => [ 'coupon', 'promo', 'discount' ],
     461                                                                                        __( 'Google Ads (ValueTrack)', 'utm-tracker-for-gravity-forms' ) => [ 'keyword', 'matchtype', 'adgroup', 'creative', 'placement' ],
     462                                                                                        __( 'Sales & email', 'utm-tracker-for-gravity-forms' )         => [ 'salesperson', 'campaign_owner', 'newsletter' ],
     463                                                                                ];
     464                                                                                foreach ( $utgf_param_suggestions as $utgf_suggest_group => $utgf_suggest_keys ) :
     465                                                                                        ?>
     466                                                                                        <div class="utgf-param-suggest-group">
     467                                                                                                <span class="utgf-param-suggest-label"><?php echo esc_html( $utgf_suggest_group ); ?></span>
     468                                                                                                <?php foreach ( $utgf_suggest_keys as $utgf_suggest_key ) : ?>
     469                                                                                                        <button type="button" class="utgf-param-chip" data-param="<?php echo esc_attr( $utgf_suggest_key ); ?>"><?php echo esc_html( $utgf_suggest_key ); ?></button>
     470                                                                                                <?php endforeach; ?>
     471                                                                                        </div>
     472                                                                                <?php endforeach; ?>
     473                                                                        </div>
     474
     475                                                <div id="utgf_param_fallback">
     476                                                        <textarea name="<?php echo esc_attr( Settings::OPTION_KEY ); ?>[custom_parameters]" id="utgf_custom_parameters" rows="6" class="large-text code" placeholder="affiliate&#10;coupon&#10;salesperson&#10;campaign_owner"><?php echo esc_textarea( implode( "\n", (array) $settings['custom_parameters'] ) ); ?></textarea>
     477                                                        <p class="description"><?php esc_html_e( 'Enter one parameter per line. Only alphanumeric characters and underscores are allowed. Duplicates and built-in parameters are ignored.', 'utm-tracker-for-gravity-forms' ); ?></p>
     478                                                </div>
    407479                                        </div>
    408480                                        <?php
    … …  
    413485                                        <div class="utgf-card">
    414486                                                <h2><?php esc_html_e( 'Attribution Model', 'utm-tracker-for-gravity-forms' ); ?></h2>
    415                                                 <p class="description"><?php esc_html_e( 'Select how cookie values behave when a visitor visits your site multiple times via different marketing campaigns.', 'utm-tracker-for-gravity-forms' ); ?></p>
    416 
    417                                                 <div class="utgf-radio-tiles">
    418                                                         <label class="utgf-radio-tile-label">
     487                                                <p class="description"><?php esc_html_e( 'When the same visitor returns through a different campaign, which campaign should get the credit? Pick the model that matches how you report.', 'utm-tracker-for-gravity-forms' ); ?></p>
     488
     489                                                <div class="utgf-attr-cards">
     490                                                        <label class="utgf-attr-card-label">
    419491                                                                <input type="radio" name="<?php echo esc_attr( Settings::OPTION_KEY ); ?>[attribution_model]" value="last_touch" <?php checked( $settings['attribution_model'], 'last_touch' ); ?> />
    420                                                                 <div class="utgf-radio-tile">
    421                                                                         <span class="utgf-radio-tile-title"><?php esc_html_e( 'Last Touch Attribution', 'utm-tracker-for-gravity-forms' ); ?></span>
    422                                                                         <span class="utgf-radio-tile-subtitle"><?php esc_html_e( 'The cookie values are replaced when new parameter values are detected in the URL. The latest campaign details always overwrite previous ones.', 'utm-tracker-for-gravity-forms' ); ?></span>
     492                                                                <div class="utgf-attr-card">
     493                                                                        <div class="utgf-attr-card-head">
     494                                                                                <span class="utgf-attr-card-icon"><i data-lucide="refresh-cw"></i></span>
     495                                                                                <span class="utgf-attr-card-title"><?php esc_html_e( 'Last Touch', 'utm-tracker-for-gravity-forms' ); ?></span>
     496                                                                                <span class="utgf-badge utgf-badge-success"><?php esc_html_e( 'Recommended', 'utm-tracker-for-gravity-forms' ); ?></span>
     497                                                                                <span class="utgf-attr-card-check"><i data-lucide="check-circle-2"></i></span>
     498                                                                        </div>
     499                                                                        <p class="utgf-attr-card-desc"><?php esc_html_e( 'The most recent campaign wins. When a visitor returns through a new campaign, the new values replace the old ones.', 'utm-tracker-for-gravity-forms' ); ?></p>
     500                                                                        <div class="utgf-attr-journey" aria-hidden="true">
     501                                                                                <span class="utgf-attr-step"><?php esc_html_e( 'Google Ads', 'utm-tracker-for-gravity-forms' ); ?></span>
     502                                                                                <span class="utgf-attr-arrow">&rarr;</span>
     503                                                                                <span class="utgf-attr-step is-credited"><?php esc_html_e( 'Facebook', 'utm-tracker-for-gravity-forms' ); ?></span>
     504                                                                                <span class="utgf-attr-arrow">&rarr;</span>
     505                                                                                <span class="utgf-attr-step utgf-attr-submit"><?php esc_html_e( 'Form submit', 'utm-tracker-for-gravity-forms' ); ?></span>
     506                                                                        </div>
     507                                                                        <p class="utgf-attr-credit"><?php esc_html_e( 'Facebook gets the credit — the campaign that finally converted.', 'utm-tracker-for-gravity-forms' ); ?></p>
     508                                                                        <p class="utgf-attr-best"><?php esc_html_e( 'Best for: knowing which campaign actually converts leads.', 'utm-tracker-for-gravity-forms' ); ?></p>
    423509                                                                </div>
    424510                                                        </label>
    425                                                         <label class="utgf-radio-tile-label">
     511                                                        <label class="utgf-attr-card-label">
    426512                                                                <input type="radio" name="<?php echo esc_attr( Settings::OPTION_KEY ); ?>[attribution_model]" value="first_touch" <?php checked( $settings['attribution_model'], 'first_touch' ); ?> />
    427                                                                 <div class="utgf-radio-tile">
    428                                                                         <span class="utgf-radio-tile-title"><?php esc_html_e( 'First Touch Attribution', 'utm-tracker-for-gravity-forms' ); ?></span>
    429                                                                         <span class="utgf-radio-tile-subtitle"><?php esc_html_e( 'The cookie values remain locked to the first parameters detected. Any subsequent parameters in later sessions are ignored.', 'utm-tracker-for-gravity-forms' ); ?></span>
     513                                                                <div class="utgf-attr-card">
     514                                                                        <div class="utgf-attr-card-head">
     515                                                                                <span class="utgf-attr-card-icon"><i data-lucide="flag"></i></span>
     516                                                                                <span class="utgf-attr-card-title"><?php esc_html_e( 'First Touch', 'utm-tracker-for-gravity-forms' ); ?></span>
     517                                                                                <span class="utgf-attr-card-check"><i data-lucide="check-circle-2"></i></span>
     518                                                                        </div>
     519                                                                        <p class="utgf-attr-card-desc"><?php esc_html_e( 'The original campaign stays locked in. Later visits through other campaigns never overwrite the first one.', 'utm-tracker-for-gravity-forms' ); ?></p>
     520                                                                        <div class="utgf-attr-journey" aria-hidden="true">
     521                                                                                <span class="utgf-attr-step is-credited"><?php esc_html_e( 'Google Ads', 'utm-tracker-for-gravity-forms' ); ?></span>
     522                                                                                <span class="utgf-attr-arrow">&rarr;</span>
     523                                                                                <span class="utgf-attr-step"><?php esc_html_e( 'Facebook', 'utm-tracker-for-gravity-forms' ); ?></span>
     524                                                                                <span class="utgf-attr-arrow">&rarr;</span>
     525                                                                                <span class="utgf-attr-step utgf-attr-submit"><?php esc_html_e( 'Form submit', 'utm-tracker-for-gravity-forms' ); ?></span>
     526                                                                        </div>
     527                                                                        <p class="utgf-attr-credit"><?php esc_html_e( 'Google Ads gets the credit — the campaign that first brought the visitor.', 'utm-tracker-for-gravity-forms' ); ?></p>
     528                                                                        <p class="utgf-attr-best"><?php esc_html_e( 'Best for: knowing how leads first discover you.', 'utm-tracker-for-gravity-forms' ); ?></p>
    430529                                                                </div>
    431530                                                        </label>
    432531                                                </div>
     532
     533                                                <p class="description" style="margin-top: 14px;">
     534                                                        <?php esc_html_e( 'You can switch models anytime — it only affects how values update from now on; already-captured entries are unchanged. Tip: the dedicated First Touch and Last Touch summary fields are always captured regardless of this setting, so you never lose either side of the story.', 'utm-tracker-for-gravity-forms' ); ?>
     535                                                </p>
    433536                                        </div>
    434537                                        <?php
    … …  
    500603                                <p class="description"><?php esc_html_e( 'Follow these simple steps to start capturing tracking values inside form entries.', 'utm-tracker-for-gravity-forms' ); ?></p>
    501604
    502                                 <div style="margin-top: 24px; display: flex; flex-direction: column; gap: 20px;">
    503                                         <div style="display: flex; gap: 16px; align-items: flex-start;">
    504                                                 <div style="background: #f1f5f9; color: var(--utgf-primary); width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; flex-shrink: 0;">1</div>
    505                                                 <div>
    506                                                         <h3 style="margin: 0 0 6px 0; font-size: 14px; font-weight: 700;"><?php esc_html_e( 'Add Hidden Fields', 'utm-tracker-for-gravity-forms' ); ?></h3>
    507                                                         <p style="margin: 0;" class="description"><?php esc_html_e( 'Open your form editor in Gravity Forms. Insert a Hidden field for each parameter you want to track (e.g., Campaign Source, Click ID, Referrer).', 'utm-tracker-for-gravity-forms' ); ?></p>
     605                                <div class="utgf-steps">
     606                                        <div class="utgf-step">
     607                                                <div class="utgf-step-num">1</div>
     608                                                <div class="utgf-step-body">
     609                                                        <h3><?php esc_html_e( 'Add Hidden Fields', 'utm-tracker-for-gravity-forms' ); ?></h3>
     610                                                        <p class="description"><?php esc_html_e( 'Open your form editor in Gravity Forms. Insert a Hidden field for each parameter you want to track (e.g., Campaign Source, Click ID, Referrer).', 'utm-tracker-for-gravity-forms' ); ?></p>
    508611                                                </div>
    509612                                        </div>
    510613
    511                                         <div style="display: flex; gap: 16px; align-items: flex-start;">
    512                                                 <div style="background: #f1f5f9; color: var(--utgf-primary); width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; flex-shrink: 0;">2</div>
    513                                                 <div>
    514                                                         <h3 style="margin: 0 0 6px 0; font-size: 14px; font-weight: 700;"><?php esc_html_e( 'Enable Dynamic Population', 'utm-tracker-for-gravity-forms' ); ?></h3>
    515                                                         <p style="margin: 0;" class="description"><?php esc_html_e( 'Click on the Hidden field in the editor. Expand the "Advanced" field options card. Check the box labeled "Allow field to be populated dynamically".', 'utm-tracker-for-gravity-forms' ); ?></p>
     614                                        <div class="utgf-step">
     615                                                <div class="utgf-step-num">2</div>
     616                                                <div class="utgf-step-body">
     617                                                        <h3><?php esc_html_e( 'Enable Dynamic Population', 'utm-tracker-for-gravity-forms' ); ?></h3>
     618                                                        <p class="description"><?php esc_html_e( 'Click on the Hidden field in the editor. Expand the "Advanced" field options card. Check the box labeled "Allow field to be populated dynamically".', 'utm-tracker-for-gravity-forms' ); ?></p>
    516619                                                </div>
    517620                                        </div>
    518621
    519                                         <div style="display: flex; gap: 16px; align-items: flex-start;">
    520                                                 <div style="background: #f1f5f9; color: var(--utgf-primary); width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; flex-shrink: 0;">3</div>
    521                                                 <div>
    522                                                         <h3 style="margin: 0 0 6px 0; font-size: 14px; font-weight: 700;"><?php esc_html_e( 'Enter Parameter Name', 'utm-tracker-for-gravity-forms' ); ?></h3>
    523                                                         <p style="margin: 0;" class="description"><?php esc_html_e( 'In the "Parameter Name" input field, enter the exact key corresponding to the cookie parameter. For standard source tracking, enter "utm_source". For Click ID tracking, enter "gclid". The field label can be anything.', 'utm-tracker-for-gravity-forms' ); ?></p>
     622                                        <div class="utgf-step">
     623                                                <div class="utgf-step-num">3</div>
     624                                                <div class="utgf-step-body">
     625                                                        <h3><?php esc_html_e( 'Enter Parameter Name', 'utm-tracker-for-gravity-forms' ); ?></h3>
     626                                                        <p class="description"><?php esc_html_e( 'In the "Parameter Name" input field, enter the exact key corresponding to the cookie parameter. For standard source tracking, enter "utm_source". For Click ID tracking, enter "gclid". The field label can be anything.', 'utm-tracker-for-gravity-forms' ); ?></p>
    524627                                                </div>
    525628                                        </div>
    … …  
    527630                        </div>
    528631
     632                        <?php
     633                        $utgf_matrix_groups = GravityFormsInjector::get_injectable_groups();
     634                        $utgf_matrix_total  = 0;
     635                        foreach ( $utgf_matrix_groups as $utgf_matrix_group ) {
     636                                $utgf_matrix_total += count( $utgf_matrix_group['fields'] );
     637                        }
     638                        ?>
    529639                        <div class="utgf-card">
    530                                 <h2><?php esc_html_e( 'Dynamic Population Mapping Matrix', 'utm-tracker-for-gravity-forms' ); ?></h2>
    531                                 <p class="description"><?php esc_html_e( 'Use these exact keys inside the Dynamic Population settings box in Gravity Forms.', 'utm-tracker-for-gravity-forms' ); ?></p>
    532 
    533                                 <table class="utgf-admin-table" style="width: 100%;">
    534                                         <thead>
    535                                                 <tr>
    536                                                         <th><?php esc_html_e( 'Target Value', 'utm-tracker-for-gravity-forms' ); ?></th>
    537                                                         <th><?php esc_html_e( 'Parameter Name Key', 'utm-tracker-for-gravity-forms' ); ?></th>
    538                                                         <th><?php esc_html_e( 'Type Badge', 'utm-tracker-for-gravity-forms' ); ?></th>
    539                                                 </tr>
    540                                         </thead>
    541                                         <tbody>
    542                                                 <?php foreach ( Parameters::builtin_url_keys() as $key ) : ?>
    543                                                         <tr>
    544                                                                 <td data-label="Target Value"><strong><?php echo esc_html( str_replace( '_', ' ', $key ) ); ?></strong></td>
    545                                                                 <td data-label="Parameter Name Key"><code><?php echo esc_html( $key ); ?></code></td>
    546                                                                 <td data-label="Type Badge"><span class="utgf-badge utgf-badge-utm"><?php esc_html_e( 'UTM / Click ID', 'utm-tracker-for-gravity-forms' ); ?></span></td>
    547                                                         </tr>
    548                                                 <?php endforeach; ?>
    549                                                
    550                                                 <?php foreach ( Parameters::field_aliases() as $alias => $key ) : ?>
    551                                                         <tr>
    552                                                                 <td data-label="Target Value"><strong><?php echo esc_html( str_replace( '_', ' ', $alias ) ); ?></strong></td>
    553                                                                 <td data-label="Parameter Name Key"><code><?php echo esc_html( $alias ); ?></code> <?php echo ( $alias !== $key ) ? ' <span class="description">(maps to ' . esc_html( $key ) . ')</span>' : ''; ?></td>
    554                                                                 <td data-label="Type Badge"><span class="utgf-badge utgf-badge-visitor"><?php esc_html_e( 'Visitor / Session', 'utm-tracker-for-gravity-forms' ); ?></span></td>
    555                                                         </tr>
    556                                                 <?php endforeach; ?>
    557                                         </tbody>
    558                                 </table>
     640                                <h2><?php esc_html_e( 'Dynamic Population Mapping Matrix', 'utm-tracker-for-gravity-forms' ); ?> <span class="utgf-count-pill"><?php echo esc_html( (string) $utgf_matrix_total ); ?></span></h2>
     641                                <p class="description"><?php esc_html_e( 'Every value you can capture, grouped by category — including your own custom parameters. Use the exact Parameter Name Key inside the hidden field\'s Dynamic Population setting in Gravity Forms.', 'utm-tracker-for-gravity-forms' ); ?></p>
     642
     643                                <?php foreach ( $utgf_matrix_groups as $utgf_matrix_group ) : ?>
     644                                        <h3 class="utgf-matrix-group"><?php echo esc_html( $utgf_matrix_group['group'] ); ?> <span class="utgf-count-pill"><?php echo esc_html( (string) count( $utgf_matrix_group['fields'] ) ); ?></span></h3>
     645                                        <table class="utgf-admin-table" style="width: 100%;">
     646                                                <thead>
     647                                                        <tr>
     648                                                                <th><?php esc_html_e( 'Field', 'utm-tracker-for-gravity-forms' ); ?></th>
     649                                                                <th><?php esc_html_e( 'Parameter Name Key', 'utm-tracker-for-gravity-forms' ); ?></th>
     650                                                                <th><?php esc_html_e( 'Stored As (cookie)', 'utm-tracker-for-gravity-forms' ); ?></th>
     651                                                        </tr>
     652                                                </thead>
     653                                                <tbody>
     654                                                        <?php foreach ( $utgf_matrix_group['fields'] as $utgf_matrix_field ) : ?>
     655                                                                <tr>
     656                                                                        <td data-label="Field"><strong><?php echo esc_html( $utgf_matrix_field['label'] ); ?></strong></td>
     657                                                                        <td data-label="Parameter Name Key"><code><?php echo esc_html( $utgf_matrix_field['key'] ); ?></code></td>
     658                                                                        <td data-label="Stored As (cookie)">
     659                                                                                <?php if ( $utgf_matrix_field['cookie'] !== $utgf_matrix_field['key'] ) : ?>
     660                                                                                        <code><?php echo esc_html( $utgf_matrix_field['cookie'] ); ?></code>
     661                                                                                <?php else : ?>
     662                                                                                        <span class="description">&mdash;</span>
     663                                                                                <?php endif; ?>
     664                                                                        </td>
     665                                                                </tr>
     666                                                        <?php endforeach; ?>
     667                                                </tbody>
     668                                        </table>
     669                                <?php endforeach; ?>
     670
     671                                <p class="description" style="margin-top: 14px;"><?php esc_html_e( 'Only values enabled in your settings appear here — turn on more under the General and Tracking tabs, and new Custom Parameters show up automatically. One-Click Field Setup above uses these exact keys for you.', 'utm-tracker-for-gravity-forms' ); ?></p>
    559672                        </div>
    560673                        <?php
    … …  
    639752                                <!-- Developer Sub-Tabs -->
    640753                                <div class="utgf-doc-tabs">
    641                                         <div class="utgf-doc-tab utgf-doc-tab-active" data-target="utgf-doc-js"><?php esc_html_e( 'JavaScript API', 'utm-tracker-for-gravity-forms' ); ?></div>
     754                                        <div class="utgf-doc-tab utgf-doc-tab-active" data-target="utgf-doc-mergetags"><?php esc_html_e( 'Merge Tags', 'utm-tracker-for-gravity-forms' ); ?></div>
     755                                        <div class="utgf-doc-tab" data-target="utgf-doc-js"><?php esc_html_e( 'JavaScript API', 'utm-tracker-for-gravity-forms' ); ?></div>
    642756                                        <div class="utgf-doc-tab" data-target="utgf-doc-rest"><?php esc_html_e( 'REST API', 'utm-tracker-for-gravity-forms' ); ?></div>
    643757                                        <div class="utgf-doc-tab" data-target="utgf-doc-shortcodes"><?php esc_html_e( 'Shortcodes', 'utm-tracker-for-gravity-forms' ); ?></div>
    … …  
    645759                                </div>
    646760
     761                                <!-- Merge Tags Content (no coding needed — most used) -->
     762                                <div class="utgf-doc-tab-content utgf-doc-content-active" id="utgf-doc-mergetags">
     763                                        <h3><?php esc_html_e( 'Attribution in notification emails & confirmations', 'utm-tracker-for-gravity-forms' ); ?></h3>
     764                                        <p class="description"><?php esc_html_e( 'No coding needed. Put these tags in any Gravity Forms notification subject, body, or confirmation — they are also listed in the merge tag drop-down of the form editor. Values come from the entry\'s hidden fields, with a fallback to the visitor\'s live tracked values, so they work even on forms without tracking fields.', 'utm-tracker-for-gravity-forms' ); ?></p>
     765
     766                                        <div class="utgf-code-block">
     767                                                <button class="utgf-copy-btn"><?php esc_html_e( 'Copy', 'utm-tracker-for-gravity-forms' ); ?></button>
     768                                                <pre><code><span class="utgf-code-comment">&lt;!-- Notification subject line --&gt;</span>
     769New lead &#8212; {utm_tracker:utm_source} / {utm_tracker:utm_campaign}
     770
     771<span class="utgf-code-comment">&lt;!-- Full attribution block: every captured value with its label --&gt;</span>
     772{utm_tracker:summary}
     773
     774<span class="utgf-code-comment">&lt;!-- Individual values: any tracked key works --&gt;</span>
     775{utm_tracker:utm_medium}
     776{utm_tracker:gclid}
     777{utm_tracker:traffic_channel}
     778{utm_tracker:landing_page}
     779{utm_tracker:first_touch_source}
     780{utm_tracker:device_type}
     781
     782<span class="utgf-code-comment">&lt;!-- Custom parameters work too --&gt;</span>
     783{utm_tracker:affiliate}
     784{utm_tracker:campaign_owner}</code></pre>
     785                                        </div>
     786                                        <p class="description"><?php esc_html_e( 'Tip: {utm_tracker:summary} already includes every tracked value — combine it with individual tags only when you want a value highlighted separately (for example in the subject line).', 'utm-tracker-for-gravity-forms' ); ?></p>
     787                                </div>
     788
    647789                                <!-- JavaScript API Content -->
    648                                 <div class="utgf-doc-tab-content utgf-doc-content-active" id="utgf-doc-js">
     790                                <div class="utgf-doc-tab-content" id="utgf-doc-js">
    649791                                        <h3><?php esc_html_e( 'Global UTMTracker object', 'utm-tracker-for-gravity-forms' ); ?></h3>
    650792                                        <p class="description"><?php esc_html_e( 'Use the global UTMTracker class on the client side to query or clear tracked values.', 'utm-tracker-for-gravity-forms' ); ?></p>
    … …  
    788930                                                                <td><?php esc_html_e( 'Fires after new plugin configurations are updated.', 'utm-tracker-for-gravity-forms' ); ?></td>
    789931                                                        </tr>
     932                                                        <tr>
     933                                                                <td><code>utgf_after_subscribe</code></td>
     934                                                                <td><?php esc_html_e( 'Fires after the setup wizard email signup is processed (receives email and consent flag).', 'utm-tracker-for-gravity-forms' ); ?></td>
     935                                                        </tr>
    790936                                                </tbody>
    791937                                        </table>
    … …  
    809955                                                        <tr><td><code>utm_tracker_ai_search_mediums</code></td><td><?php esc_html_e( 'Filter UTM medium values used for AI Search detection.', 'utm-tracker-for-gravity-forms' ); ?></td></tr>
    810956                                                        <tr><td><code>utm_tracker_ai_search_domains</code></td><td><?php esc_html_e( 'Filter referrer hostnames used for AI Search detection.', 'utm-tracker-for-gravity-forms' ); ?></td></tr>
     957                                                        <tr><td><code>utgf_field_registry</code></td><td><?php esc_html_e( 'Extend or modify the central field registry used by the URL Builder and field setup.', 'utm-tracker-for-gravity-forms' ); ?></td></tr>
     958                                                        <tr><td><code>utgf_merge_tag_value</code></td><td><?php esc_html_e( 'Adjust a resolved {utm_tracker:*} merge tag value before output.', 'utm-tracker-for-gravity-forms' ); ?></td></tr>
     959                                                        <tr><td><code>utgf_process_subscription</code></td><td><?php esc_html_e( 'Let an external provider handle the wizard email signup (return true, WP_Error, or null to continue).', 'utm-tracker-for-gravity-forms' ); ?></td></tr>
     960                                                        <tr><td><code>utgf_subscription_endpoint</code></td><td><?php esc_html_e( 'Override the newsletter endpoint URL the wizard signup posts to.', 'utm-tracker-for-gravity-forms' ); ?></td></tr>
     961                                                        <tr><td><code>utgf_subscription_api_key</code></td><td><?php esc_html_e( 'Override the Bearer API key sent with the signup request.', 'utm-tracker-for-gravity-forms' ); ?></td></tr>
    811962                                                </tbody>
    812963                                        </table>
  • utm-tracker-for-gravity-forms/trunk/includes/Plugin.php

    r3606943 r3633752  
    1212use UTGF\Api\UrlBuilderRestController;
    1313use UTGF\Core\FieldRegistry;
     14use UTGF\Core\MergeTags;
    1415use UTGF\Core\Settings;
    1516use UTGF\Data\ReportsRepository;
    … …  
    6263        private function define_constants(): void {
    6364                if ( ! defined( 'UTGF_VERSION' ) ) {
    64                         define( 'UTGF_VERSION', '2.2.8' );
     65                        define( 'UTGF_VERSION', '2.2.9' );
    6566                }
    6667        }
    … …  
    9899                new Frontend();
    99100                new Shortcodes();
     101                new MergeTags();
    100102                new RestApi();
    101103                new UrlBuilderRestController();
  • utm-tracker-for-gravity-forms/trunk/readme.txt

    r3606950 r3633752  
    55Tested up to: 7.0
    66Requires PHP: 7.4
    7 Stable tag: 2.2.8
     7Stable tag: 2.2.9
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    2323* **Set up in minutes** — a guided wizard connects your forms and creates the hidden fields for you
    2424* **Captures everything that matters** — UTMs, click IDs from every major ad platform, landing pages, referrer, device data, and your own custom parameters
     25* **Attribution in your inbox** — merge tags like `{utm_tracker:utm_source}` put the campaign behind each lead right in your notification emails
    2526* **Attribution built in** — first-touch and last-touch models, plus automatic traffic channel detection including AI Search
    2627* **Reports included** — a live attribution dashboard with interactive charts, right inside WordPress
    … …  
    103104Yes. Hidden fields are refreshed when a form is rendered, on each page of a multi-step form, and again at submission — including AJAX-powered forms and multiple forms on one page.
    104105
     106= Can I see UTM values in my notification emails? =
     107
     108Yes. Use merge tags in any Gravity Forms notification or confirmation — for example `{utm_tracker:utm_source}` in the subject line, or `{utm_tracker:summary}` in the body to print the lead's whole attribution block. The tags are listed in the merge tag drop-down of the form editor and resolve from the entry's hidden fields, falling back to the visitor's live tracking values when the form has no matching field.
     109
    105110= Can I track custom URL parameters? =
    106111
    … …  
    158163== Upgrade Notice ==
    159164
     165= 2.2.9 =
     166New UTM merge tags for Gravity Forms notifications and confirmations — put {utm_tracker:utm_source} in a subject line or {utm_tracker:summary} in the body to see each lead's attribution in your inbox. No changes to tracking or attribution.
     167
    160168= 2.2.8 =
    161169Optional "Stay Updated" email signup in the Get Started wizard (explicit consent, skippable), plus a fix for forms with File Upload fields that could block submission. No changes to tracking or attribution.
    … …  
    167175
    168176The complete, detailed changelog ships as CHANGELOG.md inside the plugin.
     177
     178= 2.2.9 =
     179* New: **UTM merge tags** for Gravity Forms notifications and confirmations. Every tracked value is available as `{utm_tracker:<key>}` (for example `{utm_tracker:utm_source}`, `{utm_tracker:traffic_channel}`, `{utm_tracker:landing_page}`), and `{utm_tracker:summary}` prints the lead's whole attribution block with labels.
     180* Tags appear in the merge tag drop-down of the form editor, work in subjects, bodies, and confirmations, and resolve from the entry's hidden fields with a fallback to the visitor's live tracked values — so they work even on forms without tracking fields.
     181* New developer filter: `utgf_merge_tag_value` to adjust a resolved value before output.
     182* Improved: the Debug page now opens with a plain-language "How to test tracking in 3 steps" guide and a one-click Simulate Campaign Visit button, and every diagnostic section explains in simple words what it shows and why it may be empty. The Cookies page description was clarified the same way.
     183* No changes to tracking, attribution, cookies, REST/JS APIs, shortcodes, or Gravity Forms field population — fully backward compatible.
    169184
    170185= 2.2.8 =
  • utm-tracker-for-gravity-forms/trunk/utm-tracker-for-gravity-forms.php

    r3606943 r3633752  
    44 * Plugin URI:  https://wordpress.org/plugins/utm-tracker-for-gravity-forms/
    55 * Description: Complete marketing attribution for Gravity Forms — UTM tracking, click IDs, landing pages, traffic channel detection, REST/JS APIs, and session data.
    6  * Version:     2.2.8
     6 * Version:     2.2.9
    77 * Author:      Auxano Global Services
    88 * Author URI:  https://auxanoglobalservices.com
Note: See TracChangeset for help on using the changeset viewer.