1(function() { 2 'use strict'; 3 4 // ============================================ 5 // ATTRIBUTION TRACKING SECTION 6 // ============================================ 7 8 var config = { 9 debug: true, // Set to false in production 10 cookieExpiration: 30 * 24 * 60 * 60 * 1000, // 30 days 11 cookieVersion: 'v2' // Must match WordPress site 12 }; 13 14 function log(msg, data) { 15 if (config.debug && console && console.log) { 16 console.log('[Attribution]', msg, data || ''); 17 } 18 } 19 20 // ============================================ 21 // FIXED LISTS â MUST MATCH ODOO 22 // ============================================ 23 24 var KNOWN_SOURCES = [ 25 'google','bing','yahoo','duckduckgo','yandex','baidu','brave','live', 26 'facebook','linkedin','twitter','tiktok','instagram','youtube','reddit', 27 'chatgpt','perplexity','claude','gemini','copilot', 28 'epsglobal','newsletter','direct','other' 29 ]; 30 31 var KNOWN_MEDIUMS = [ 32 'organic','cpc','cpm','paid_social','social','email', 33 'referral','affiliate','ai','display','(none)','other' 34 ]; 35 36 var SOURCE_CONTAINS_MAP = [ 37 { match: 'chatgpt.com', source: 'chatgpt' }, 38 { match: 'chat.openai.com', source: 'chatgpt' }, 39 { match: 'openai.com', source: 'chatgpt' }, 40 { match: 'perplexity.ai', source: 'perplexity' }, 41 { match: 'claude.ai', source: 'claude' }, 42 { match: 'gemini.google.com', source: 'gemini' }, 43 { match: 'copilot.microsoft.com', source: 'copilot' }, 44 { match: 'bing.com/chat', source: 'copilot' }, 45 { match: 'epsglobal', source: 'epsglobal' }, 46 { match: 'bing.com', source: 'bing' }, 47 { match: 'live.com', source: 'live' }, 48 { match: 'search.brave.com', source: 'brave' }, 49 { match: 'brave.com', source: 'brave' }, 50 { match: 'facebook.com', source: 'facebook' }, 51 { match: 'fb.com', source: 'facebook' }, 52 { match: 'linkedin.com', source: 'linkedin' }, 53 { match: 'lnkd.in', source: 'linkedin' }, 54 { match: 't.co', source: 'twitter' }, 55 { match: 'x.com', source: 'twitter' }, 56 { match: 'twitter.com', source: 'twitter' }, 57 { match: 'reddit.com', source: 'reddit' }, 58 { match: 'youtube.com', source: 'youtube' }, 59 { match: 'youtu.be', source: 'youtube' }, 60 { match: 'instagram.com', source: 'instagram' }, 61 { match: 'tiktok.com', source: 'tiktok' } 62 ]; 63 64 var AI_SOURCES = ['chatgpt','perplexity','claude','gemini','copilot']; 65 66 function resolveSource(raw) { 67 if (!raw) return 'other'; 68 var s = raw.toString().toLowerCase().replace(/^www\./, ''); 69 if (s === '(direct)' || s === 'direct') return 'direct'; 70 if (KNOWN_SOURCES.indexOf(s) > -1) return s; 71 for (var i = 0; i < SOURCE_CONTAINS_MAP.length; i++) { 72 if (s.indexOf(SOURCE_CONTAINS_MAP[i].match) > -1) { 73 return SOURCE_CONTAINS_MAP[i].source; 74 } 75 } 76 if (/(^|\.)google\./.test(s)) return 'google'; 77 if (/(^|\.)yahoo\./.test(s)) return 'yahoo'; 78 if (/(^|\.)yandex\./.test(s)) return 'yandex'; 79 if (/(^|\.)duckduckgo\./.test(s)) return 'duckduckgo'; 80 if (/(^|\.)baidu\./.test(s)) return 'baidu'; 81 return 'other'; 82 } 83 84 function normaliseAttribution(gaReferral) { 85 var src = resolveSource(gaReferral.utmcsr); 86 var med = (gaReferral.utmcmd || '').toString().toLowerCase(); 87 gaReferral.utmcsr = src; 88 if (AI_SOURCES.indexOf(src) > -1) { 89 if (med === 'referral' || med === '' || med === 'other' || KNOWN_MEDIUMS.indexOf(med) === -1) { 90 med = 'ai'; 91 } 92 } 93 if (src === 'direct') { med = '(none)'; } 94 if (med === '' || KNOWN_MEDIUMS.indexOf(med) === -1) { med = 'other'; } 95 gaReferral.utmcmd = med; 96 return gaReferral; 97 } 98 99 function initAttributionTracking(callback) { 100 try { 101 log('Starting attribution tracking'); 102 103 var referrer = document.referrer || ''; 104 var gaReferral = { 105 'utmcsr': '(direct)', 106 'utmcmd': '(none)', 107 'utmccn': '(not set)' 108 }; 109 110 var thisHostname = document.location.hostname; 111 var thisDomain = getDomain(thisHostname); 112 var referringDomain = getDomain(referrer); 113 var sessionCookie = getCookie('__utmzzses'); 114 var cookieExpiration = new Date(Date.now() + config.cookieExpiration); 115 var qs = document.location.search.replace('?', ''); 116 var hash = document.location.hash.replace('#', ''); 117 var gaParams = parseGoogleParams(qs + '#' + hash); 118 var referringInfo = parseGaReferrer(referrer); 119 var storedVals = getCookie('__utmz') || getCookie('__utmzz'); 120 var newCookieVals = []; 121 122 var keyMap = { 123 'utm_source': 'utmcsr', 124 'utm_medium': 'utmcmd', 125 'utm_campaign': 'utmccn', 126 'utm_content': 'utmcct', 127 'utm_term': 'utmctr', 128 'gclid': 'utmgclid', 129 'dclid': 'utmdclid', 130 'msclkid': 'utmmsclid', 131 'fbclid': 'utmfbclid', 132 'ttclid': 'utmttclid', 133 'li_fat_id': 'utmliid',
134 'twclid': 'utmtwclid', 135 'ref': 'utmref' 136 }; 137 138 var keyFilter = ['utmcsr','utmcmd','utmccn','utmcct','utmctr','utmgclid','utmdclid','utmmsclid','utmfbclid','utmttclid','utmliid','utmtwclid','utmref']; 139 140 if (sessionCookie && referringDomain === thisDomain) { 141 gaParams = null; 142 referringInfo = null; 143 } 144 145 if (gaParams && (gaParams.utm_source || gaParams.gclid || gaParams.dclid || gaParams.msclkid || gaParams.fbclid || gaParams.ttclid || gaParams.li_fat_id || gaParams.twclid)) { 146 log('Processing URL parameters', gaParams); 147 for (var key in gaParams) { 148 if (gaParams.hasOwnProperty(key) && typeof gaParams[key] !== 'undefined') { 149 var keyName = keyMap[key]; 150 if (keyName) { gaReferral[keyName] = gaParams[key]; } 151 } 152 } 153 154 // Click-ID inference only fills in source/medium when they were 155 // NOT explicitly provided via utm_source â explicit UTMs win. 156 if (!gaParams.utm_source) { 157 if (gaParams.gclid || gaParams.dclid) { 158 gaReferral.utmcsr = 'google'; 159 gaReferral.utmcmd = gaParams.gclid ? 'cpc' : 'cpm'; 160 } else if (gaParams.msclkid) { 161 gaReferral.utmcsr = 'bing'; gaReferral.utmcmd = 'cpc'; 162 } else if (gaParams.fbclid) { 163 gaReferral.utmcsr = 'facebook'; gaReferral.utmcmd = 'paid_social'; 164 } else if (gaParams.ttclid) { 165 gaReferral.utmcsr = 'tiktok'; gaReferral.utmcmd = 'paid_social'; 166 } else if (gaParams.li_fat_id) { 167 gaReferral.utmcsr = 'linkedin'; gaReferral.utmcmd = 'paid_social'; 168 } else if (gaParams.twclid) { 169 gaReferral.utmcsr = 'twitter'; gaReferral.utmcmd = 'paid_social'; 170 } 171 } else if (!gaParams.utm_medium) { 172 // utm_source given without utm_medium: infer medium from click ID 173 if (gaParams.gclid || gaParams.msclkid) { 174 gaReferral.utmcmd = 'cpc'; 175 } else if (gaParams.dclid) { 176 gaReferral.utmcmd = 'cpm'; 177 } else if (gaParams.fbclid || gaParams.ttclid || gaParams.li_fat_id || gaParams.twclid) { 178 gaReferral.utmcmd = 'paid_social'; 179 } 180 } 181 } else if (referringInfo) { 182 log('Processing referrer info', referringInfo); 183 gaReferral.utmcsr = referringInfo.source; 184 gaReferral.utmcmd = referringInfo.medium; 185 if (referringInfo.term) { gaReferral.utmctr = referringInfo.term; } 186 } else if (storedVals) { 187 log('Using stored values'); 188 var values = {}; 189 var raw = storedVals.split('|'); 190 for (var i = 0; i < raw.length; i++) { 191 // Split on the FIRST '=' only so values containing '=' survive 192 var eqPos = raw[i].indexOf('='); 193 if (eqPos > -1) { 194 var _key = raw[i].substring(0, eqPos).split('.').pop(); 195 values[_key] = raw[i].substring(eqPos + 1); 196 } 197 } 198 gaReferral = values; 199 } 200 201 gaReferral = normaliseAttribution(gaReferral); 202 log('Normalised attribution', { source: gaReferral.utmcsr, medium: gaReferral.utmcmd }); 203 204 for (var key in gaReferral) { 205 if (gaReferral.hasOwnProperty(key) && typeof gaReferral[key] !== 'undefined' && keyFilter.indexOf(key) > -1) { 206 newCookieVals.push(key + '=' + gaReferral[key]); 207 } 208 } 209 210 var cookieValue = newCookieVals.join('|'); 211 var currentCookieName = 'currentTrafficSource_' + config.cookieVersion; 212 var initialCookieName = 'initialTrafficSource_' + config.cookieVersion; 213 214 if (cookieValue) { 215 log('Setting cookies', cookieValue); 216 writeCookie(currentCookieName, cookieValue, cookieExpiration, '/', thisDomain); 217 if (!getCookie(initialCookieName)) { 218 writeCookie(initialCookieName, cookieValue, cookieExpiration, '/', thisDomain); 219 } 220 writeCookie('__utmzzses', '1', null, '/', thisDomain); 221 log('Attribution tracking complete'); 222 } 223 224 if (callback && typeof callback === 'function') { callback(); } 225 226 } catch (error) { 227 console.error('[Attribution Error]', error); 228 if (callback && typeof callback === 'function') { callback(); } 229 } 230 } 231 232 function parseGoogleParams(str) { 233 var campaignParams = ['source','medium','campaign','term','content']; 234 // FIXED: 'msclkid' was previously written as '(d|g|ms)cli
234d', which only 235 // matched 'msclid' â Microsoft's param has a 'k' (msclkid), so Bing paid 236 // clicks were never detected and fell through to bing/organic. 237 // Also anchored to start-of-string or a separator so e.g. 'href=' or 238 // 'xref=' can't false-match the 'ref' parameter. 239 var regex = new RegExp('(^|[?&#])((utm_(' + campaignParams.join('|') + ')|gclid|dclid|msclkid|fbclid|ttclid|li_fat_id|twclid|ref)=([^&#]*))', 'gi'); 240 var paramsObj = {}; 241 var match; 242 243 while ((match = regex.exec(str)) !== null) { 244 // match[2] is 'name=value'; split on the FIRST '=' only so values 245 // containing '=' (some click IDs, base64-ish tokens) aren't dropped 246 var pair = match[2]; 247 var eqPos = pair.indexOf('='); 248 if (eqPos > -1) { 249 var name = pair.substring(0, eqPos).toLowerCase(); 250 var value = pair.substring(eqPos + 1); 251 try { 252 paramsObj[name] = decodeURIComponent(value.replace(/\+/g, ' ')); 253 } catch (e) { 254 paramsObj[name] = value; // malformed encoding â keep raw 255 } 256 } 257 } 258 259 return Object.keys(paramsObj).length > 0 ? paramsObj : null; 260 } 261 262 function parseGaReferrer(referrer) { 263 if (!referrer) return null; 264 var searchEngines = { 265 'google': { p: 'q', n: 'google' }, 266 'yahoo.com': { p: 'p', n: 'yahoo' }, 267 'bing.com': { p: 'q', n: 'bing' }, 268 'baidu.com': { p: 'wd', n: 'baidu' }, 269 'yandex': { p: 'text', n: 'yandex' }, 270 'duckduckgo.com': { p: 'q', n: 'duckduckgo' }, 271 'search.brave.com':{ p: 'q', n: 'brave' } 272 }; 273 var a = document.createElement('a'); 274 a.href = referrer; 275 var hostname = a.hostname.toLowerCase(); 276 var values = null; 277 for (var domain in searchEngines) { 278 if (hostname.indexOf(domain) > -1) { 279 var searchEngine = searchEngines[domain]; 280 var termRegex = new RegExp(searchEngine.p + '=([^&#]*)', 'i'); 281 var match = a.search.match(termRegex); 282 values = { 283 source: searchEngine.n, 284 medium: 'organic', 285 term: match ? decodeURIComponent(match[1]) : '(not provided)' 286 }; 287 break; 288 } 289 } 290 if (!values && hostname && hostname !== document.location.hostname) { 291 values = { source: hostname, medium: 'referral' }; 292 } 293 return values; 294 } 295 296 function getDomain(url) { 297 if (!url) return null; 298 try { 299 var a = document.createElement('a'); 300 a.href = url; 301 var hostname = a.hostname.toLowerCase().replace(/^www\./, ''); 302 var matches = hostname.match(/([^.]+\.(?:co\.uk|com\.au|co\.nz|co\.za|com\.br|co\.jp|co\.kr|co\.in|[^.]+))$/); 303 return matches ? matches[0] : hostname; 304 } catch(e) { 305 return document.location.hostname; 306 } 307 } 308 309 function writeCookie(name, value, expiration, path, domain) { 310 var cookieStr = name + '=' + encodeURIComponent(value); 311 if (expiration) { cookieStr += '; Expires=' + expiration.toUTCString(); } 312 if (path) { cookieStr += '; Path=' + path; } 313 if (domain) { cookieStr += '; Domain=' + domain; } 314 cookieStr += '; SameSite=Lax'; 315 if (window.location.protocol === 'https:') { cookieStr += '; Secure'; } 316 document.cookie = cookieStr; 317 } 318 319 function getCookie(name) { 320 var nameEQ = name + '='; 321 var ca = document.cookie.split(';'); 322 for (var i = 0; i < ca.length; i++) { 323 var c = ca[i].trim(); 324 if (c.indexOf(nameEQ) === 0) { return decodeURIComponent(c.substring(nameEQ.length)); } 325 } 326 return null; 327 } 328 329 // ============================================ 330 // FORM POPULATION SECTION 331 // ============================================ 332 333 function parseCookieValue(cookieValue) { 334 if (!cookieValue) return {}; 335 var params = {}; 336 var pairs = cookieValue.split('|'); 337 for (var i = 0; i < pairs.length; i++) { 338 // Split on the FIRST '=' only so values containing '=' survive 339 var eqPos = pairs[i].indexOf('='); 340 if (eqPos > -1) { 341 var key = pairs[i].substring(0, eqPos); 342 var value = pairs[i].substring(eqPos + 1); 343 params[key] = decodeURIComponent(value.replace(/\+/g, ' ')); 344 } 345 } 346 return params; 347 } 348 349 function setFieldValue(fieldId, value) { 350 var field = document.getElementById(fieldId); 351 if (field) { 352 field.value = value || ''; 353 log('Set field ' + fieldId + ' to: ' + (value || '(empty)')); 354 } else { 355 log('Field not found: ' + fieldId); 356 } 357 } 358 359 function populateFormFields() { 360 log('Starting form population'); 361 362 // Read the versioned initial traffic source cookie (matches WordPress) 363 var cookieValue = getCookie('initialTrafficSource_' + config.cookieVersion); 364 if (!cookieValue) { 365 log('initialTrafficSource_' + config.cookieVersion + ' cookie not found'); 366 return; 367 } 368 369 log('Cookie value found:', cookieValue); 370 var params = parseCookieValue(cookieValue); 371 log('Parsed params:', params); 372 373 // Kentico form field IDs â unchanged from original 374 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl01_On_lineForm_plcUp_viewBiz_Os_txtText', params.utmcsr); 375 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl01_On_lineForm_plcUp_viewBiz_Om_txtText', params.utmcmd); 376 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl01_On_lineForm_plcUp_viewBiz_Oc_txtText', params.utmccn); 377 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl01_On_lineForm_plcUp_viewBiz_Ok_txtText', params.utmcct); 378 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl18_On_lineForm2_viewBiz_Os_txtText', params.utmcsr); 379 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl18_On_lineForm2_viewBiz_Om_txtText', params.utmcmd); 380 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl18_On_lineForm2_viewBiz_Oc_txtText', params.utmccn); 381 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl18_On_lineForm2_viewBiz_Ok_txtText', params.utmcct); 382 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl16_On_lineForm2_viewBiz_Os_txtText', params.utmcsr); 383 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl16_On_lineForm2_viewBiz_Om_txtText', params.utmcmd); 384 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl16_On_lineForm2_viewBiz_Oc_txtText', params.utmccn); 385 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl16_On_lineForm2_viewBiz_Ok_txtText', params.utmcct); 386 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl02_On_lineForm_plcUp_viewBiz_Os_txtText', params.utmcsr); 387 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl02_On_lineForm_plcUp_viewBiz_Om_txtText', params.utmcmd); 388 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl02_On_lineForm_plcUp_viewBiz_Oc_txtText', params.utmccn); 389 setFieldValue('p_lt_ctl07_pageplaceholder_p_lt_ctl02_On_lineForm_plcUp_viewBiz_Ok_txtText', params.utmcct); 390 391 log('Form population complete'); 392 } 393 394 // ============================================ 395 // INITIALIZATION 396 // ============================================ 397
398 function init() { 399 initAttributionTracking(function() { 400 if (document.readyState === 'loading') { 401 document.addEventListener('DOMContentLoaded', populateFormFields); 402 } else { 403 populateFormFields(); 404 } 405 }); 406 } 407 408 init(); 409 410})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.