Changeset 3633752
- Timestamp:
- 08/04/2026 06:06:25 AM (3 months ago)
- Location:
- utm-tracker-for-gravity-forms/trunk
- Files:
-
- 1 added
- 10 edited
-
CHANGELOG.md (modified) (1 diff)
-
README.md (modified) (3 diffs)
-
assets/css/admin.css (modified) (1 diff)
-
assets/js/admin.js (modified) (1 diff)
-
includes/Admin/CookiesPage.php (modified) (1 diff)
-
includes/Admin/DebugPage.php (modified) (6 diffs)
-
includes/Admin/SettingsPage.php (modified) (8 diffs)
-
includes/Core/MergeTags.php (added)
-
includes/Plugin.php (modified) (3 diffs)
-
readme.txt (modified) (5 diffs)
-
utm-tracker-for-gravity-forms.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
utm-tracker-for-gravity-forms/trunk/CHANGELOG.md
r3606943 r3633752 2 2 3 3 All 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. 4 31 5 32 ## [2.2.8] — 2026-07-09 -
utm-tracker-for-gravity-forms/trunk/README.md
r3606943 r3633752 1 1 # UTM Tracker for Gravity Forms 2 2 3 **Version:** 2.2. 83 **Version:** 2.2.9 4 4 **Requires:** WordPress 5.0+, PHP 7.4+, Gravity Forms 5 5 **License:** GPLv2 or later … … 251 251 --- 252 252 253 ## Merge tags (Gravity Forms notifications & confirmations) 254 255 Every 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 261 Example notification subject: `New lead — {utm_tracker:utm_source} / {utm_tracker:utm_medium}` 262 263 --- 264 253 265 ## Developer hooks 254 266 … … 274 286 | `utm_tracker_ai_search_mediums` | UTM medium values for AI Search detection | 275 287 | `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 | 276 289 | `utgf_process_subscription` | Let an external provider handle the wizard email signup (return true/WP_Error, or null to continue) | 277 290 | `utgf_subscription_endpoint` | Override the newsletter endpoint URL the signup posts to | -
utm-tracker-for-gravity-forms/trunk/assets/css/admin.css
r3605522 r3633752 3774 3774 } 3775 3775 } 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 996 996 } 997 997 })(); 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 59 59 <div class="utgf-card utgf-cookie-manager-card"> 60 60 <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> 62 62 63 63 <!-- Toolbar (Search, Filter, Count) --> -
utm-tracker-for-gravity-forms/trunk/includes/Admin/DebugPage.php
r3592757 r3633752 52 52 } 53 53 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 56 57 // Render layout header 57 58 Layout::render_header( 'debug' ); … … 89 90 </div> 90 91 </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> 91 107 </div> 92 108 … … 119 135 </div> 120 136 <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> 122 138 <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> 123 139 </div> … … 131 147 </div> 132 148 <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> 133 150 <table class="utgf-admin-table"> 134 151 <tbody id="utgf_debug_attribution_list"> … … 146 163 </div> 147 164 <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> 148 166 <table class="utgf-admin-table"> 149 167 <thead> … … 164 182 <div class="utgf-collapsible-card utgf-expanded" id="utgf_debug_card_cookies" style="display: none;"> 165 183 <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> 170 189 <table class="utgf-admin-table"> 171 190 <thead> -
utm-tracker-for-gravity-forms/trunk/includes/Admin/SettingsPage.php
r3599816 r3633752 377 377 <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> 378 378 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> 383 401 <?php endforeach; ?> 384 402 </div> 385 403 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> 390 428 <?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; ?> 391 441 </div> 392 442 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 coupon salesperson 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 coupon salesperson 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> 407 479 </div> 408 480 <?php … … 413 485 <div class="utgf-card"> 414 486 <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"> 419 491 <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">→</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">→</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> 423 509 </div> 424 510 </label> 425 <label class="utgf- radio-tile-label">511 <label class="utgf-attr-card-label"> 426 512 <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">→</span> 523 <span class="utgf-attr-step"><?php esc_html_e( 'Facebook', 'utm-tracker-for-gravity-forms' ); ?></span> 524 <span class="utgf-attr-arrow">→</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> 430 529 </div> 431 530 </label> 432 531 </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> 433 536 </div> 434 537 <?php … … 500 603 <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> 501 604 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> 508 611 </div> 509 612 </div> 510 613 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> 516 619 </div> 517 620 </div> 518 621 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> 524 627 </div> 525 628 </div> … … 527 630 </div> 528 631 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 ?> 529 639 <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">—</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> 559 672 </div> 560 673 <?php … … 639 752 <!-- Developer Sub-Tabs --> 640 753 <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> 642 756 <div class="utgf-doc-tab" data-target="utgf-doc-rest"><?php esc_html_e( 'REST API', 'utm-tracker-for-gravity-forms' ); ?></div> 643 757 <div class="utgf-doc-tab" data-target="utgf-doc-shortcodes"><?php esc_html_e( 'Shortcodes', 'utm-tracker-for-gravity-forms' ); ?></div> … … 645 759 </div> 646 760 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"><!-- Notification subject line --></span> 769 New lead — {utm_tracker:utm_source} / {utm_tracker:utm_campaign} 770 771 <span class="utgf-code-comment"><!-- Full attribution block: every captured value with its label --></span> 772 {utm_tracker:summary} 773 774 <span class="utgf-code-comment"><!-- Individual values: any tracked key works --></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"><!-- Custom parameters work too --></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 647 789 <!-- 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"> 649 791 <h3><?php esc_html_e( 'Global UTMTracker object', 'utm-tracker-for-gravity-forms' ); ?></h3> 650 792 <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> … … 788 930 <td><?php esc_html_e( 'Fires after new plugin configurations are updated.', 'utm-tracker-for-gravity-forms' ); ?></td> 789 931 </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> 790 936 </tbody> 791 937 </table> … … 809 955 <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> 810 956 <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> 811 962 </tbody> 812 963 </table> -
utm-tracker-for-gravity-forms/trunk/includes/Plugin.php
r3606943 r3633752 12 12 use UTGF\Api\UrlBuilderRestController; 13 13 use UTGF\Core\FieldRegistry; 14 use UTGF\Core\MergeTags; 14 15 use UTGF\Core\Settings; 15 16 use UTGF\Data\ReportsRepository; … … 62 63 private function define_constants(): void { 63 64 if ( ! defined( 'UTGF_VERSION' ) ) { 64 define( 'UTGF_VERSION', '2.2. 8' );65 define( 'UTGF_VERSION', '2.2.9' ); 65 66 } 66 67 } … … 98 99 new Frontend(); 99 100 new Shortcodes(); 101 new MergeTags(); 100 102 new RestApi(); 101 103 new UrlBuilderRestController(); -
utm-tracker-for-gravity-forms/trunk/readme.txt
r3606950 r3633752 5 5 Tested up to: 7.0 6 6 Requires PHP: 7.4 7 Stable tag: 2.2. 87 Stable tag: 2.2.9 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 23 23 * **Set up in minutes** — a guided wizard connects your forms and creates the hidden fields for you 24 24 * **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 25 26 * **Attribution built in** — first-touch and last-touch models, plus automatic traffic channel detection including AI Search 26 27 * **Reports included** — a live attribution dashboard with interactive charts, right inside WordPress … … 103 104 Yes. 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. 104 105 106 = Can I see UTM values in my notification emails? = 107 108 Yes. 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 105 110 = Can I track custom URL parameters? = 106 111 … … 158 163 == Upgrade Notice == 159 164 165 = 2.2.9 = 166 New 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 160 168 = 2.2.8 = 161 169 Optional "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. … … 167 175 168 176 The 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. 169 184 170 185 = 2.2.8 = -
utm-tracker-for-gravity-forms/trunk/utm-tracker-for-gravity-forms.php
r3606943 r3633752 4 4 * Plugin URI: https://wordpress.org/plugins/utm-tracker-for-gravity-forms/ 5 5 * 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. 86 * Version: 2.2.9 7 7 * Author: Auxano Global Services 8 8 * Author URI: https://auxanoglobalservices.com
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)