PageSourceSearch

https://www.cinnamoncc.com/wp-content/plugins/cinnamon-analytics-dashboard/assets/js/frontend.js?ver=1.0.6

js cinnamoncc.com collected 2026-10-07 15:06:11 UTC 36,819 bytes, 1,096 lines download raw bytes

1(function () {
2	'use strict';
3
4	var config = window.CinnamonAnalyticsFrontend || {};
5	if (!config.eventsUrl || !config.tokenUrl) {
6		return;
7	}
8
9	var MAX_BATCH = Math.max(1, Math.min(20, Number(config.maxBatch) || 20));
10	var conversionClasses = new Set(Array.isArray(config.conversionClasses) ? config.conversionClasses : []);
11	var browserEvents = new Set(Array.isArray(config.browserEvents) ? config.browserEvents : []);
12	var formStates = new WeakMap();
13	var queue = [];
14	var flushTimer = 0;
15	var flushing = false;
16	var pendingKeepalive = false;
17	var token = typeof config.token === 'string' ? config.token : '';
18	var tokenExpires = Number(config.tokenExpires) || 0;
19	var tokenPromise = null;
20	var sequence = 0;
21	// The live theme captures this exact global form collection when its bundle
22	// executes. Preserve that source-side distinction from the submitted form.
23	var legacyContactForms = Array.prototype.slice.call(document.querySelectorAll('div#contact.contact-container form.wpcf7-form'));
24
25	var ATTRIBUTION_KEYS = [
26		'gclid', 'gbraid', 'wbraid', 'campaign', 'adgroup', 'keyword', 'infinity_vid',
27		'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'ga_client_id'
28	];
29	var MARKETING_ATTRIBUTION_KEYS = [
30		'gclid', 'gbraid', 'wbraid', 'campaign', 'adgroup', 'keyword', 'infinity_vid'
31	];
32	var STATISTICS_ATTRIBUTION_KEYS = [
33		'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'ga_client_id'
34	];
35	var gaClientId = '';
36	var gaClientIdRequested = false;
37	var carehqGaEventIds = new Set();
38
39	function validUuid(value) {
40		return typeof value === 'string' && /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i.test(value);
41	}
42
43	function uuid() {
44		if (window.crypto && typeof window.crypto.randomUUID === 'function') {
45			return window.crypto.randomUUID();
46		}
47		var bytes = new Uint8Array(16);
48		if (window.crypto && typeof window.crypto.getRandomValues === 'function') {
49			window.crypto.getRandomValues(bytes);
50		} else {
51			for (var i = 0; i < bytes.length; i++) {
52				bytes[i] = Math.floor(Math.random() * 256);
53			}
54		}
55		bytes[6] = (bytes[6] & 15) | 64;
56		bytes[8] = (bytes[8] & 63) | 128;
57		var hex = Array.prototype.map.call(bytes, function (byte) {
58			return byte.toString(16).padStart(2, '0');
59		}).join('');
60		return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' + hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20);
61	}
62
63	function storageGet(storage, key) {
64		try {
65			return storage ? storage.getItem(key) : null;
66		} catch (error) {
67			return null;
68		}
69	}
70
71	function storageSet(storage, key, value) {
72		try {
73			if (storage) {
74				storage.setItem(key, value);
75			}
76		} catch (error) {
77			// Storage can be unavailable in privacy modes; in-memory state remains valid.
78		}
79	}
80
81	function storageRemove(storage, key) {
82		try {
83			if (storage) {
84				storage.removeItem(key);
85			}
86		} catch (error) {
87			// Storage can be unavailable in privacy modes.
88		}
89	}
90
91	function storageObject(name) {
92		try {
93			return window[name] || null;
94		} catch (error) {
95			return null;
96		}
97	}
98
99	// Accessing the storage property itself can throw in sandboxed/private
100	// contexts, so resolve it once inside a guard before any collector work.
101	var sessionStore = storageObject('sessionStorage');
102	var localStore = storageObject('localStorage');
103
104	function pageUrl() {
105		try {
106			var url = new URL(window.location.href);
107			url.hash = '';
108			url.search = '';
109			url.username = '';
110			url.password = '';
111			return url.href.slice(0, 2048);
112		} catch (error) {
113			return '';
114		}
115	}
116
117	function referrerUrl() {
118		try {
119			if (!document.referrer) {
120				return '';
121			}
122			var url = new URL(document.referrer);
123			url.hash = '';
124			url.search = '';
125			url.username = '';
126			url.password = '';
127			return url.href.slice(0, 2048);
128		} catch (error) {
129			return '';
130		}
131	}
132
133	function sessionUuid() {
134		var key = 'cinnamonAnalyticsSessionId';
135		var current = consentSnapshot().statistics ? storageGet(sessionStore, key) : null;
136		if (!validUuid(current)) {
137			current = uuid();
138			if (consentSnapshot().statistics) {
139				storageSet(sessionStore, key, current);
140			}
141		}
142		return current;
143	}
144
145	var sessionId = sessionUuid();
146	var landingPage = '';
147	var initialReferrer = null;
148
149	function consentSnapshot() {
150		var cookiebot = window.Cookiebot || null;
151		var consent = cookiebot && cookiebot.consent ? cookiebot.consent : null;
152		var known = !!(cookiebot && (cookiebot.hasResponse === true || cookiebot.consented === true || cookiebot.declined === true));
153		if (!consent || !known) {
154			return { known: false, statistics: false, marketing: false, telemetry: false, source: 'unknown' };
155		}
156		var statistics = consent.statistics === true;
157		var marketing = consent.marketing === true;
158		return {
159			known: true,
160			statistics: statistics,
161			marketing: marketing,
162			telemetry: statistics || marketing,
163			source: 'cookiebot'
164		};
165	}
166
167	function initialiseStatisticsContext() {
168		if (!consentSnapshot().statistics) {
169			return false;
170		}
171		storageSet(sessionStore, 'cinnamonAnalyticsSessionId', sessionId);
172		if (!landingPage) {
173			landingPage = storageGet(sessionStore, 'cinnamonAnalyticsLandingPage') || pageUrl();
174			storageSet(sessionStore, 'cinnamonAnalyticsLandingPage', landingPage);
175		}
176		if (initialReferrer === null) {
177			initialReferrer = storageGet(sessionStore, 'cinnamonAnalyticsInitialReferrer');
178			if (initialReferrer === null) {
179				initialReferrer = referrerUrl();
180			}
181			storageSet(sessionStore, 'cinnamonAnalyticsInitialReferrer', initialReferrer);
182		}
183		return true;
184	}
185
186	function integrationSnapshot() {
187		var bingTruthy = !!window.uetq;
188		var bingPush = !!(window.uetq && typeof window.uetq.push === 'function');
189		return {
190			bingQueueTruthy: bingTruthy,
191			bingPush: bingPush,
192			bing: !bingTruthy || bingPush,
193			meta: typeof window.fbq === 'function',
194			infinity: typeof window._its === 'function'
195		};
196	}
197
198	function legacyContactHomeSelected() {
199		for (var i = 0; i < legacyContactForms.length; i++) {
200			var select = legacyContactForms[i].querySelector('select[name="home-name"]');
201			if (select) {
202				return String(select.value || '').trim() !== '';
203			}
204		}
205		return false;
206	}
207
208	function namedElements(form, name) {
209		return Array.prototype.filter.call(form.querySelectorAll('[name]'), function (element) {
210			return element.getAttribute('name') === name;
211		});
212	}
213
214	function fieldValue(form, name) {
215		if (!form || typeof form.querySelectorAll !== 'function') {
216			return '';
217		}
218		var fields = namedElements(form, name);
219		if (!fields.length) {
220			return '';
221		}
222		for (var i = 0; i < fields.length; i++) {
223			var field = fields[i];
224			if ((field.type === 'checkbox' || field.type === 'radio') && !field.checked) {
225				continue;
226			}
227			if (String(field.value || '').trim() !== '') {
228				return String(field.value).trim().slice(0, 2048);
229			}
230		}
231		return '';
232	}
233
234	function setHiddenField(form, name, value) {
235		value = value === null || typeof value === 'undefined' ? '' : String(value);
236		var fields = namedElements(form, name);
237		var field = fields.length ? fields[0] : null;
238		if (!field) {
239			field = document.createElement('input');
240			field.type = 'hidden';
241			field.name = name;
242			field.setAttribute('data-cinnamon-analytics-field', '1');
243			form.appendChild(field);
244		}
245		field.value = value;
246		field.defaultValue = value;
247		field.setAttribute('value', value);
248	}
249
250	function removeManagedField(form, name) {
251		namedElements(form, name).forEach(function (field) {
252			if (field.getAttribute('data-cinnamon-analytics-field') === '1' && field.parentNode) {
253				field.parentNode.removeChild(field);
254			}
255		});
256	}
257
258	function normaliseStoredValue(value) {
259		if (typeof value !== 'string') {
260			return '';
261		}
262		value = value.trim();
263		if (!value) {
264			return '';
265		}
266		if (value.charAt(0) === '{') {
267			try {
268				var parsed = JSON.parse(value);
269				value = parsed && typeof parsed.value === 'string' ? parsed.value : '';
270			} catch (error) {
271				return '';
272			}
273		}
274		return String(value).trim().slice(0, 500);
275	}
276
277	function legacyStorageValue(key) {
278		var normalise = ['gclid', 'gbraid', 'wbraid'].indexOf(key) !== -1 ? normaliseClickId : normaliseStoredValue;
279		return normalise(storageGet(sessionStore, key)) || normalise(storageGet(localStore, key));
280	}
281
282	function normaliseClickId(value) {
283		if (typeof value !== 'string' || value.length > 4096) {
284			return '';
285		}
286		value = value.trim();
287		if (value.charAt(0) === '{') {
288			try {
289				var record = JSON.parse(value);
290				if (!record || typeof record.value !== 'string' || typeof record.expiryDate !== 'number' || !Number.isFinite(record.expiryDate) || record.expiryDate <= Date.now()) {
291					return '';
292				}
293				value = record.value.trim();
294			} catch (error) {
295				return '';
296			}
297		}
298		// Opaque URL-safe IDs only; never submit an object or a truncated token.
299		return /^[A-Za-z0-9._~-]{1,255}$/.test(value) ? value : '';
300	}
301
302	function normaliseGaClientId(value) {
303		value = typeof value === 'string' || typeof value === 'number' ? String(value).trim() : '';
304		return /^[A-Za-z0-9._-]{3,255}$/.test(value) ? value.slice(0, 255) : '';
305	}
306
307	function gaCookieClientId() {
308		try {
309			var cookies = String(document.cookie || '').split(';');
310			for (var i = 0; i < cookies.length; i++) {
311				var parts = cookies[i].trim().split('=');
312				if (parts.shift() !== '_ga') {
313					continue;
314				}
315				var value = decodeURIComponent(parts.join('=') || '');
316				var segments = value.split('.');
317				if (segments.length >= 4) {
318					return normaliseGaClientId(segments.slice(-2).join('.'));
319				}
320			}
321		} catch (error) {
322			return '';
323		}
324		return '';
325	}
326
327	function refreshManagedForms() {
328		Array.prototype.forEach.call(document.querySelectorAll('.wpcf7 > form, form.wpcf7-form'), function (form) {
329			var state = formStates.get(form);
330			if (state) {
331				refreshHiddenFields(form, state);
332			}
333		});
334	}
335
336	function requestGaClientId() {
337		if (!consentSnapshot().statistics) {
338			return;
339		}
340		gaClientId = gaClientId || gaCookieClientId();
341		if (gaClientId || gaClientIdRequested) {
342			return;
343		}
344		if (typeof window.gtag !== 'function' || !config.gaMeasurementId) {
345			return;
346		}
347		gaClientIdRequested = true;
348		try {
349			window.gtag('get', String(config.gaMeasurementId), 'client_id', function (value) {
350				var resolved = normaliseGaClientId(value);
351				if (!resolved || !consentSnapshot().statistics) {
352					gaClientIdRequested = false;
353					return;
354				}
355				gaClientId = resolved;
356				refreshManagedForms();
357			});
358		} catch (error) {
359			gaClientIdRequested = false;
360			// GA may be unavailable or consent-blocked; the cookie fallback remains optional.
361		}
362	}
363
364	function savedAttribution(storageKey) {
365		var value = storageGet(sessionStore, storageKey);
366		if (!value) {
367			return {};
368		}
369		try {
370			var parsed = JSON.parse(value);
371			return parsed && typeof parsed === 'object' ? parsed : {};
372		} catch (error) {
373			return {};
374		}
375	}
376
377	function legacyPpcTracking() {
378		var raw = storageGet(sessionStore, 'ppcTracking') || storageGet(localStore, 'ppcTracking');
379		if (!raw) {
380			return {};
381		}
382		try {
383			var parsed = JSON.parse(raw);
384			return parsed && typeof parsed === 'object' ? parsed : {};
385		} catch (error) {
386			return {};
387		}
388	}
389
390	function pathAttribution() {
391		var match = String(window.location.href).match(/~cmp~(.*?)~ag~(.*?)~ar~.*?~kw~(.*?)(?:[/?#]|$)/i);
392		if (!match) {
393			return {};
394		}
395		var decode = function (value) {
396			try {
397				return decodeURIComponent(value || '').slice(0, 500);
398			} catch (error) {
399				return String(value || '').slice(0, 500);
400			}
401		};
402		return { campaign: decode(match[1]), adgroup: decode(match[2]), keyword: decode(match[3]) };
403	}
404
405	function organicSearchSource() {
406		// Use the consented, original referrer, never the current internal page.
407		// These are source-side inferences, not GA4's computed session dimensions.
408		if (!initialiseStatisticsContext() || !initialReferrer) {
409			return '';
410		}
411		try {
412			var referrer = new URL(initialReferrer);
413			if (referrer.protocol !== 'https:' && referrer.protocol !== 'http:') {
414				return '';
415			}
416			var host = referrer.hostname.toLowerCase().replace(/\.$/, '');
417			var googleDomains = [
418				'google.com', 'google.co.uk', 'google.ie', 'google.fr', 'google.de',
419				'google.es', 'google.it', 'google.nl', 'google.be', 'google.pt',
420				'google.ch', 'google.at', 'google.dk', 'google.se', 'google.no',
421				'google.fi', 'google.pl', 'google.cz', 'google.co.in', 'google.com.au',
422				'google.co.nz', 'google.ca', 'google.co.za', 'google.co.jp', 'google.com.br'
423			];
424			var isGoogle = googleDomains.some(function (domain) {
425				return host === domain || host === 'www.' + domain || host === 'search.' + domain || host === 'encrypted.' + domain;
426			});
427			if (isGoogle && /^\/(?:search|url|webhp)?\/?$/.test(referrer.pathname)) {
428				return 'google';
429			}
430			if ((host === 'bing.com' || host === 'www.bing.com' || host === 'cn.bing.com') && /^\/(?:search)?\/?$/.test(referrer.pathname)) {
431				return 'bing';
432			}
433			if (host === 'duckduckgo.com' || host === 'www.duckduckgo.com' || host === 'html.duckduckgo.com') {
434				return 'duckduckgo';
435			}
436			if ((host === 'ecosia.org' || host === 'www.ecosia.org') && /^\/(?:search)?\/?$/.test(referrer.pathname)) {
437				return 'ecosia';
438			}
439			if (host === 'search.yahoo.com' || /^[a-z]{2}\.search\.yahoo\.com$/.test(host)) {
440				return 'yahoo';
441			}
442			if (host === 'search.brave.com') {
443				return 'brave';
444			}
445		} catch (error) {
446			// Missing, malformed or unrecognised referrers stay unknown.
447		}
448		return '';
449	}
450
451	function collectAttribution(form) {
452		var consent = consentSnapshot();
453		if (!consent.telemetry) {
454			return {};
455		}
456		var statisticsStored = consent.statistics ? savedAttribution('cinnamonAnalyticsStatisticsAttribution') : {};
457		var marketingStored = consent.marketing ? savedAttribution('cinnamonAnalyticsMarketingAttribution') : {};
458		var ppc = consent.marketing ? legacyPpcTracking() : {};
459		var pathValues = consent.marketing ? pathAttribution() : {};
460		var query = null;
461		try {
462			query = new URL(window.location.href).searchParams;
463		} catch (error) {
464			query = new URLSearchParams();
465		}
466		var output = {};
467		var statisticsOutput = {};
468		var marketingOutput = {};
469		if (consent.statistics) {
470			requestGaClientId();
471		}
472
473		ATTRIBUTION_KEYS.forEach(function (key) {
474			var isMarketing = MARKETING_ATTRIBUTION_KEYS.indexOf(key) !== -1;
475			var isStatistics = STATISTICS_ATTRIBUTION_KEYS.indexOf(key) !== -1;
476			if ((!isMarketing && !isStatistics) || (isMarketing && !consent.marketing) || (isStatistics && !consent.statistics)) {
477				return;
478			}
479			var stored = isMarketing ? marketingStored : statisticsStored;
480			var current = key === 'ga_client_id' ? gaClientId : query.get(key);
481			if (isMarketing && (key === 'campaign' || key === 'adgroup' || key === 'keyword') && pathValues[key]) {
482				current = pathValues[key];
483			}
484			var existing = fieldValue(form, key);
485			var fromPpc = isMarketing && ppc[key] ? String(ppc[key]).slice(0, 500) : '';
486			var normalise = ['gclid', 'gbraid', 'wbraid'].indexOf(key) !== -1 ? normaliseClickId : normaliseStoredValue;
487			var value = normalise(current || '') || normalise(existing) || normalise(stored[
487key] || '') || normalise(fromPpc) || legacyStorageValue(key);
488			if (value) {
489				output[key] = value;
490				if (isMarketing) {
491					marketingOutput[key] = value;
492				} else {
493					statisticsOutput[key] = value;
494				}
495			}
496		});
497
498		var hasPaidEvidence = !!(output.gclid || output.gbraid || output.wbraid || output.campaign || output.adgroup || output.keyword) ||
499			['gclid', 'gbraid', 'wbraid', 'msclkid', 'dclid'].some(function (key) { return query.has(key); });
500		var campaignKeys = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'utm_id', 'utm_source_platform'];
501		var hasExplicitCampaign = campaignKeys.some(function (key) {
502			return query.has(key) || !!fieldValue(form, key) || (key !== 'utm_source' && key !== 'utm_medium' && !!output[key]);
503		});
504		var organicInferred = statisticsStored._organic_inferred === true;
505		// Unlike supplied campaign values, a previous organic fallback must yield
506		// if a subsequent page supplies stronger paid/campaign evidence.
507		if (organicInferred && (hasPaidEvidence || hasExplicitCampaign)) {
508			['utm_source', 'utm_medium'].forEach(function (key) {
509				if (!normaliseStoredValue(query.get(key) || '') && !fieldValue(form, key) && !legacyStorageValue(key) && output[key] === statisticsStored[key]) {
510					delete output[key];
511					delete statisticsOutput[key];
512				}
513			});
514			organicInferred = false;
515		}
516
517		// Google auto-tagging supplies an opaque click ID, not readable campaign
518		// metadata. Where both purposes are consented, make the source/medium
519		// explicit and reuse Cinnamon's legacy campaign value when it exists.
520		// Never invent a campaign name from GCLID/GBRAID/WBRAID.
521		if (consent.statistics && consent.marketing && (output.gclid || output.gbraid || output.wbraid)) {
522			if (!output.utm_source) {
523				output.utm_source = 'google';
524				statisticsOutput.utm_source = 'google';
525			}
526			if (!output.utm_medium) {
527				output.utm_medium = 'cpc';
528				statisticsOutput.utm_medium = 'cpc';
529			}
530			if (!output.utm_campaign && output.campaign) {
531				output.utm_campaign = output.campaign;
532				statisticsOutput.utm_campaign = output.campaign;
533			}
534		}
535
536		// Organic visits normally have no UTMs. Populate CareHQ's existing source
537		// and medium columns only when there is no explicit/session campaign or
538		// paid evidence. Do not mix a partial UTM campaign with inferred values.
539		// Checking URL key presence does not read or retain a denied click ID.
540		var hasCampaignEvidence = campaignKeys.some(function (key) {
541			return !!output[key] || query.has(key);
542		});
543		if (consent.statistics && !hasPaidEvidence && !hasCampaignEvidence) {
544			var organicSource = organicSearchSource();
545			if (organicSource) {
546				output.utm_source = organicSource;
547				output.utm_medium = 'organic';
548				statisticsOutput.utm_source = organicSource;
549				statisticsOutput.utm_medium = 'organic';
550				organicInferred = true;
551			}
552		}
553
554		if (consent.statistics) {
555			if (organicInferred && output.utm_medium === 'organic') {
556				// Private plugin-session provenance, not a submitted/CRM field.
557				statisticsOutput._organic_inferred = true;
558			}
559			storageSet(sessionStore, 'cinnamonAnalyticsStatisticsAttribution', JSON.stringify(statisticsOutput));
560		}
561		if (consent.marketing) {
562			storageSet(sessionStore, 'cinnamonAnalyticsMarketingAttribution', JSON.stringify(marketingOutput));
563		}
564		return output;
565	}
566
567	function capturePageAttribution() {
568		initialiseStatisticsContext();
569		collectAttribution(null);
570	}
571
572	function fireCarehqCreatedGa4(form, detail) {
573		var apiResponse = detail && detail.apiResponse && typeof detail.apiResponse === 'object' ? detail.apiResponse : {};
574		var marker = apiResponse.cinnamon_carehq;
575		if (!marker || marker.version !== 1 || marker.event !== 'carehq_enquiry_created' || marker.accepted !== true || marker.created !== true || !validUuid(marker.event_id)) {
576			return;
577		}
578		if (!consentSnapshot().statistics || typeof window.gtag !== 'function' || !config.gaMeasurementId || c
578arehqGaEventIds.has(marker.event_id)) {
579			return;
580		}
581
582		var formId = Number(detail.contactFormId) || Number(fieldValue(form, '_wpcf7')) || 0;
583		var classification = successClassificationInputs(formId, detail.inputs || []);
584		var attribution = collectAttribution(form);
585		var homeId = fieldValue(form, 'home-id') || String(Number(config.homeId) || '');
586		var parameters = {
587			send_to: String(config.gaMeasurementId),
588			transport_type: 'beacon',
589			event_id: marker.event_id,
590			carehq_result: 'created',
591			form_id: String(formId)
592		};
593		if (homeId) {
594			parameters.home_id = String(homeId).slice(0, 100);
595		}
596		if (classification.formType) {
597			parameters.enquiry_type = String(classification.formType).slice(0, 100);
598		}
599		['utm_source', 'utm_medium', 'utm_campaign'].forEach(function (key) {
600			if (attribution[key]) {
601				parameters['cinnamon_' + key] = String(attribution[key]).slice(0, 100);
602			}
603		});
604
605		carehqGaEventIds.add(marker.event_id);
606		try {
607			window.gtag('event', 'carehq_enquiry_created', parameters);
608		} catch (error) {
609			// GA4 availability must never affect the completed enquiry journey.
610		}
611	}
612
613	function formIdentity(form, index) {
614		var unitTag = fieldValue(form, '_wpcf7_unit_tag');
615		var formId = fieldValue(form, '_wpcf7');
616		return String(config.pageId || 0) + ':' + (unitTag || formId || String(index || 0));
617	}
618
619	function findFormIndex(form) {
620		return Array.prototype.indexOf.call(document.querySelectorAll('.wpcf7 > form, form.wpcf7-form'), form);
621	}
622
623	function ensureFormState(form) {
624		if (!form || form.nodeType !== 1) {
625			return null;
626		}
627		var state = formStates.get(form);
628		if (!state) {
629			var key = 'cinnamonAnalyticsLogical:' + formIdentity(form, findFormIndex(form));
630			var existing = fieldValue(form, '_cinnamon_analytics_submission_id');
631			var logical = validUuid(existing) ? existing : (consentSnapshot().statistics ? storageGet(sessionStore, key) : null);
632			if (!validUuid(logical)) {
633				logical = uuid();
634			}
635			state = {
636				logicalId: logical,
637				storageKey: key,
638				interactionLogged: false,
639				pendingRotate: false,
640				rotationTimer: 0
641			};
642			formStates.set(form, state);
643			if (consentSnapshot().statistics) {
644				storageSet(sessionStore, key, logical);
645			}
646		}
647		refreshHiddenFields(form, state);
648		return state;
649	}
650
651	function refreshHiddenFields(form, state) {
652		var consent = consentSnapshot();
653		// Explicit client-reported purpose flags let the server suppress legacy
654		// fallbacks when a category is denied or the CMP has no response yet.
655		setHiddenField(form, '_cinnamon_analytics_consent_statistics', consent.statistics ? '1' : '0');
656		setHiddenField(form, '_cinnamon_analytics_consent_marketing', consent.marketing ? '1' : '0');
657		setHiddenField(form, '_cinnamon_analytics_submission_id', state.logicalId);
658		setHiddenField(form, '_cinnamon_analytics_session_id', sessionId);
659		setHiddenField(form, '_cinnamon_analytics_page_id', String(Number(config.pageId) || 0));
660		setHiddenField(form, '_cinnamon_analytics_submission_page', pageUrl());
661		if (initialiseStatisticsContext()) {
662			setHiddenField(form, '_cinnamon_analytics_landing_page', String(landingPage || '').slice(0, 2048));
663			setHiddenField(form, '_cinnamon_analytics_referrer', String(initialReferrer || '').slice(0, 2048));
664		} else {
665			removeManagedField(form, '_cinnamon_analytics_landing_page');
666			removeManagedField(form, '_cinnamon_analytics_referrer');
667		}
668		var attribution = collectAttribution(form);
669		ATTRIBUTION_KEYS.forEach(function (key) {
670			var categoryAllowed = MARKETING_ATTRIBUTION_KEYS.indexOf(key) !== -1 ? consent.marketing : consent.statistics;
671			if (categoryAllowed) {
672				// Never modify a theme/CF7 field: doing so could change email,
673				// CareHQ or CF7 spam behavior. Evidence stays underscore-prefixed.
674				setHiddenField(form, '_cinnamon_analytics_attr_' + key, attribution[key] || '');
675			} else {
676				removeManagedField(form, '_cinnamon_analytics_attr_' + key);
677			}
678		});
679	}
680
681	function rotateLogicalSubmission(form, state) {
682		if (!state || !state.pendingRotate) {
683			return;
684		}
685		state.logicalId = uuid();
686		state.interactionLogged = false;
687		state.pendingRotate = false;
688		if (consentSnapshot().statistics) {
689			storageSet(sessionStore, state.storageKey, state.logicalId);
690		}
691		refreshHiddenFields(form, state);
692	}
693
694	function formContext(form, state) {
695		var home = fieldValue(form, 'home-id') || fieldValue(form, 'home-name') || fieldValue(form, 'home-recruitment') || fieldValue(form, 'home-survey');
696		var numericHome = /^\d+$/.test(home) ? Number(home) : Number(config.homeId) || 0;
697		return {
698			session_uuid: sessionId,
699			submission_uuid: state.logicalId,
700			form_id: Number(fieldValue(form, '_wpcf7')) || 0,
701			page_id: Number(config.pageId) || 0,
702			page_url: pageUrl(),
703			home_id: numericHome
704		};
705	}
706
707	function inputMap(inputs) {
708		var mapped = {};
709		(Array.isArray(inputs) ? inputs : []).forEach(function (item) {
710			if (item && typeof item.name === 'string' && typeof item.value !== 'object' && !Object.prototype.hasOwnProperty.call(mapped, item.name)) {
711				mapped[item.name] = String(item.value || '').slice(0, 500);
712			}
713		});
714		return mapped;
715	}
716
717	function legacyInputValue(inputs, name, useLast) {
718		var found = '';
719		(Array.isArray(inputs) ? inputs : []).some(function (item) {
720			if (!item || item.name !== name || typeof item.value === 'object') {
721				return false;
722			}
723			found = String(item.value || '').slice(0, 500);
724			return !useLast;
725		});
726		return found;
727	}
728
729	function successClassificationInputs(formId, inputs) {
730		var values = inputMap(inputs);
731		var formTypeRaw = values['form-type'] || '';
732		var formTypeValue = values['form-type-value'] || '';
733		var enquiryTypeRaw = values['enquiry-type'] || '';
734		var enquiryTypeValue = values['enquiry-type-value'] || '';
735		var formType = formTypeValue || formTypeRaw;
736		var enquiryType = enquiryTypeValue || enquiryTypeRaw;
737		var homeSelected = String(values['home-name'] || '').trim() !== '';
738		// The audited 34703 branch walks the full array (last matching value
739		// wins); the other legacy branches stop at the first form-type value.
740		var legacyFormType = legacyInputValue(inputs, 'form-type', formId === 34703);
741		var legacyEnquiryType = legacyInputValue(inputs, 'enquiry-type', true);
742		var legacyHomeSelected = legacyContactHomeSelected();
743
744		return {
745			formType: formType,
746			formTypeRaw: formTypeRaw,
747			formTypeValue: formTypeValue,
748			enquiryType: enquiryType,
749			enquiryTypeRaw: enquiryTypeRaw,
750			enquiryTypeValue: enquiryTypeValue,
751			homeSelected: homeSelected,
752			legacyFormType: legacyFormType,
753			legacyEnquiryType: legacyEnquiryType,
754			legacyHomeSelected: legacyHomeSelected
755		};
756	}
757
758	function buildEvent(form, state, kind, name, metadata) {
759		var context = formContext(form, state);
760		return {
761			id: uuid(),
762			kind: kind,
763			name: name,
764			occurred_at: Date.now(),
765			session_uuid: context.session_uuid,
766			submission_uuid: context.submission_uuid,
767			form_id: context.form_id,
768			page_id: context.page_id,
769			page_url: context.page_url,
770			home_id: context.home_id,
771			metadata: Object.assign({
772				sequence: ++sequence,
773				unit_tag: fieldValue(form, '_wpcf7_unit_tag'),
774				container_post_id: Number(fieldValue(form, '_wpcf7_container_post')) || 0,
775				plugin_version: fieldValue(form, '_wpcf7_version') || String(config.pluginVersion || ''),
776				consent: consentSnapshot()
777			}, metadata || {})
778		};
779	}
780
781	function record(form, kind, name, metadata, consentRequirement) {
782		var consent = consentSnapshot();
783		var consentDenied = consentRequirement === 'statistics' && !consent.statistics;
784		consentDenied = consentDenied || (consentRequirement === 'conversion' && !consent.statistics && !consent.marketing);
785		if (!form || consentDenied) {
786			return;
787		}
788		var state = ensureFormState(form);
789		if (!state) {
790			return;
791		}
792		queue.push({ payload: buildEvent(form, state, kind, name, metadata), retries: 0 });
793		if (queue.length >= MAX_BATCH) {
794			flush(false);
795		} else {
796			scheduleFlush();
797		}
798	}
799
800	function scheduleFlush() {
801		if (flushTimer) {
802			return;
803		}
804		flushTimer = window.setTimeout(function () {
805			flushTimer = 0;
806			flush(false);
807		}, 500);
808	}
809
810	function ensureToken(force) {
811		if (!force && token && tokenExpires > Math.floor(Date.now() / 1000) + 30) {
812			return Promise.resolve(token);
813		}
814		if (tokenPromise) {
815			return tokenPromise;
816		}
817		tokenPromise = window.fetch(config.tokenUrl, {
818			method: 'POST',
819			credentials: 'same-origin',
820			// Token requests are deliberately tiny and keepalive-safe so an
821			// unload-time drain is not lost while obtaining browser credentials.
822			keepalive: true,
823			headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
824			body: JSON.stringify({ page_id: Number(config.pageId) || 0 })
825		}).then(function (response) {
826			if (!response.ok) {
827				throw new Error('token');
828			}
829			return response.json();
830		}).then(function (data) {
831			if (!data || typeof data.token !== 'string') {
832				throw new Error('token');
833			}
834			token = data.token;
835			tokenExpires = Number(data.expires) || 0;
836			return token;
837		}).finally(function () {
838			tokenPromise = null;
839		});
840		return tokenPromise;
841	}
842
843	function sendBatch(batch, keepalive, refreshed) {
844		return ensureToken(!!refreshed).then(function (activeToken) {
845			return window.fetch(config.eventsUrl, {
846				method: 'POST',
847				credentials: 'same-origin',
848				// Every bounded collector request is keepalive-capable. This also
849				// protects an ordinary in-flight flush when pagehide races it.
850				keepalive: true,
851				headers: {
852					'Accept': 'application/json',
853					'Content-Type': 'application/json',
854					'X-Cinnamon-Analytics-Token': activeToken
855				},
856				body: JSON.stringify({ token: activeToken, events: batch.map(function (item) { return item.payload; }) })
857			});
858		}).then(function (response) {
859			if ((response.status === 401 || response.status === 403) && !refreshed) {
860				token = '';
861				tokenExpires = 0;
862				return sendBatch(batch, keepalive, true);
863			}
864			if (!response.ok) {
865				throw new Error('events');
866			}
867			return response;
868		});
869	}
870
871	function flush(keepalive) {
872		if (!queue.length) {
873			return;
874		}
875		if (flushing) {
876			pendingKeepalive = pendingKeepalive || !!keepalive;
877			return;
878		}
879		if (flushTimer) {
880			window.clearTimeout(flushTimer);
881			flushTimer = 0;
882		}
883		flushing = true;
884		var batch = queue.splice(0, MAX_BATCH);
885		sendBatch(batch, keepalive, false).catch(function () {
886			batch.reverse().forEach(function (item) {
887				if (item.retries < 2) {
888					item.retries++;
889					queue.unshift(item);
890				}
891			});
892		}).finally(function () {
893			flushing = false;
894			var drainImmediately = pendingKeepalive || !!keepalive || document.visibilityState === 'hidden';
895			pendingKeepalive = false;
896			if (queue.length) {
897				if (drainImmediately) {
898					flush(true);
899				} else {
900					scheduleFlush();
901				}
902			}
903		});
904	}
905
906	function initForms(root) {
907		var forms = [];
908		if (root && root.matches && root.matches('.wpcf7 > form, form.wpcf7-form')) {
909			forms.push(root);
910		}
911		if (root && root.querySelectorAll) {
912			forms = forms.concat(Array.prototype.slice.call(root.querySelectorAll('.wpcf7 > form, form.wpcf7-form')));
913		}
914		forms.forEach(function (form) {
915			ensureFormState(form);
916		});
917	}
918
919	function refreshFormsForConsent() {
920		var consent = consentSnapshot();
921		// Remove the pre-split development key without importing it into a
922		// category whose current consent cannot be proven.
923		storageRemove(sessionStore, 'cinnamonAnalyticsAttribution');
924		if (!consent.statistics) {
925			storageRemove(sessionStore, 'cinnamonAnalyticsSessionId');
926			storageRemove(sessionStore, 'cinnamonAnalyticsLandingPage');
927			storageRemove(sessionStore, 'cinnamonAnalyticsInitialReferrer');
928			storageRemove(sessionStore, 'cinnamonAnalyticsStatisticsAttribution');
929			landingPage = '';
930			initialReferrer = null;
931			gaClientId = '';
932			gaClientIdRequested = false;
933		}
934		if (!consent.marketing) {
935			storageRemove(sessionStore, 'cinnamonAnalyticsMarketingAttribution');
936		}
937		capturePageAttribution();
938		Array.prototype.forEach.call(document.querySelectorAll('.wpcf7 > form, form.wpcf7-form'), function (form) {
939			var state = ensureFormState(form);
940			if (!state) {
941				return;
942			}
943			if (!consent.statistics) {
944				storageRemove(sessionStore, state.storageKey);
945			} else {
946				storageSet(sessionStore, state.storageKey, state.logicalId);
947			}
948			refreshHiddenFields(form, state);
949		});
950	}
951
952	// Capture phase runs before CF7's submit handler constructs FormData. This
953	// keeps correlation fields current for Enter-key and requestSubmit() paths.
954	document.addEventListener('submit', function (event) {
955		var form = event.target && event.target.matches && event.target.matches('.wpcf7 > form, form.wpcf7-form') ? event.target : null;
956		if (!form) {
957			return;
958		}
959		var state = ensureFormState(form);
960		if (state) {
961			refreshHiddenFields(form, state);
962		}
963	}, true);
964
965	document.addEventListener('focusin', function (event) {
966		var form = event.target && event.target.closest ? event.target.closest('.wpcf7 > form, form.wpcf7-form') : null;
967		if (!form) {
968			return;
969		}
970		var state = ensureFormState(form);
971		if (!state || state.interactionLogged || !consentSnapshot().statistics) {
972			return;
973		}
974		state.interactionLogged = true;
975		record(form, 'form_interaction', 'form_interaction_started', {
976			interaction: 'focusin',
977			element_tag: String(event.target.tagName || '').toLowerCase(),
978			element_type: String(event.target.type || '').toLowerCase(),
979			field_name: String(event.target.name || '').slice(0, 64)
980		}, 'statistics');
981	}, true);
982
983	document.addEventListener('click', function (event) {
984		var element = event.target && event.target.closest ? event.target.closest('input, button, a') : null;
985		if (!element) {
986			return;
987		}
988		var matched = [];
989		conversionClasses.forEach(function (className) {
990			if (element.classList.contains(className)) {
991				matched.push(className);
992			}
993		});
994		if (!matched.length) {
995			return;
996		}
997		var form = element.closest('.wpcf7 > form, form.wpcf7-form');
998		if (!form) {
999			return;
1000		}
1001		matched.forEach(function (className) {
1002			record(form, 'conversion_click', className, {
1003				conversion_class: className,
1004				element_tag: String(element.tagName || '').toLowerCase(),
1005				element_type: String(element.type || '').toLowerCase()
1006			}, 'conversion');
1007		});
1008	}, true);
1009
1010	browserEvents.forEach(function (eventName) {
1011		document.addEventListener(eventName, function (event) {
1012			var form = event.target && event.target.matches && event.target.matches('form') ? event.target : (event.target && event.target.closest ? event.target.closest('form') : null);
1013			if (!form) {
1014				return;
1015			}
1016			var state = ensureFormState(form);
1017			if (!state) {
1018				return;
1019			}
1020			if (eventName === 'wpcf7beforesubmit') {
1021				refreshHiddenFields(form, state);
1022			}
1023			var detail = event.detail || {};
1024			var metadata = { cf7_status: String(detail.status || '').toLowerCase().replace(/[^a-z0-9_]+/g, '_') };
1025
1026			if (eventName === 'wpcf7mailsent') {
1027				var formId = Number(detail.contactFormId) || Number(fieldValue(form, '_wpcf7')) || 0;
1028				var classification = successClassificationInputs(formId, detail.inputs || []);
1029				var availability = integrationSnapshot();
1030				metadata.bing_queue_available = availability.bingPush;
1031				metadata.bing_queue_truthy = availability.bingQueueTruthy;
1032				metadata.bing_push_available = availability.bingPush;
1033				metadata.meta_function_available = availability.meta;
1034				metadata.infinity_function_available = availability.infinity;
1035				metadata.form_type = classification.formType;
1036				metadata.form_type_raw = classification.formTypeRaw;
1037				metadata.form_type_value = classification.formTypeValue;
1038				metadata.enquiry_type = classification.enquiryType;
1039				metadata.enquiry_type_raw = classification.enquiryTypeRaw;
1040				metadata.enquiry_type_value = classification.enquiryTypeValue;
1041				metadata.home_selected = classification.homeSelected;
1042				metadata.legacy_form_type = classification.legacyFormType;
1043				metadata.legacy_enquiry_type = classification.legacyEnquiryType;
1044				metadata.legacy_home_selected = classification.legacyHomeSelected;
1045				fireCarehqCreatedGa4(form, detail);
1046				state.pendingRotate = true;
1047				window.clearTimeout(state.rotationTimer);
1048				state.rotationTimer = window.setTimeout(function () {
1049					rotateLogicalSubmission(form, state);
1050				}, 0);
1051			}
1052
1053			record(form, 'cf7_browser', eventName, metadata, eventName === 'wpcf7beforesubmit' ? 'statistics' : '');
1054
1055			if (eventName === 'wpcf7submit' && state.pendingRotate) {
1056				window.clearTimeout(state.rotationTimer);
1057				state.rotationTimer = window.setTimeout(function () {
1058					rotateLogicalSubmission(form, state);
1059				}, 0);
1060			}
1061		}, true);
1062	});
1063
1064	if (document.readyState === 'loading') {
1065		document.addEventListener('DOMContentLoaded', function () {
1066			capturePageAttribution();
1067			initForms(document);
1068		});
1069	} else {
1070		capturePageAttribution();
1071		initForms(document);
1072	}
1073
1074	if (window.MutationObserver) {
1075		new MutationObserver(function (mutations) {
1076			mutations.forEach(function (mutation) {
1077				Array.prototype.forEach.call(mutation.addedNodes || [], function (node) {
1078					if (node && node.nodeType === 1) {
1079						initForms(node);
1080					}
1081				});
1082			});
1083		}).observe(document.documentElement, { childList: true, subtree: true });
1084	}
1085
1086	['CookiebotOnConsentReady', 'CookiebotOnAccept', 'CookiebotOnDecline'].forEach(function (eventName) {
1087		window.addEventListener(eventName, refreshFormsForConsent);
1088	});
1089
1090	document.addEventListener('visibilitychange', function () {
1091		if (document.visibilityState === 'hidden') {
1092			flush(true);
1093		}
1094	});
1095	window.addEventListener('pagehide', function () { flush(true); });
1096})();

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.