/*! Atlas Language School - source capture v1.1
* Records where an enquiry came from (paid / organic / direct ...) and writes it into
* hidden fields on the Freshworks enquiry forms. Also tags WhatsApp links with a source code.
* No dependencies. Never throws into the page. */
(function (w, d) {
'use strict';
if (w.AtlasSource) return;
var CFG = {
version: '1.1',
key: 'atlas_src_v1',
ownDomain: 'atlaslanguageschool.com',
persistDays: 30, // longer-term storage ONLY if marketing consent is given
maxLen: 200,
waEnabled: true,
waPhone: '353832001037',
waText: 'Hello Atlas, I would like some information about your courses.'
};
var CHANNEL_CODE = {
'Paid search': 'PS', 'Paid social': 'PSO', 'Organic search': 'OSE', 'Organic social': 'OSO',
'Direct': 'DIR', 'Referral': 'REF', 'Email': 'EML'
};
var SEARCH_HOSTS = ['google', 'bing', 'duckduckgo', 'yahoo', 'ecosia', 'baidu', 'yandex', 'brave', 'qwant', 'startpage', 'naver', 'seznam'];
var SOCIAL_HOSTS = ['facebook.com', 'fb.com', 'fb.me', 'instagram.com', 'linkedin.com', 'lnkd.in', 'tiktok.com', 't.co', 'x.com',
'twitter.com', 'youtube.com', 'youtu.be', 'pinterest.com', 'reddit.com', 'snapchat.com', 'threads.net', 'whatsapp.com'];
var MAIL_HOSTS = ['mail.google.com', 'outlook.live.com', 'outlook.office.com', 'outlook.office365.com', 'mail.yahoo.com'];
var SOCIAL_SRC = ['facebook', 'fb', 'instagram', 'ig', 'meta', 'linkedin', 'tiktok', 'twitter', 'x', 'youtube', 'pinterest', 'reddit', 'snapchat', 'threads'];
function cut(v) { v = (v == null ? '' : String(v)); return v.length > CFG.maxLen ? v.slice(0, CFG.maxLen) : v; }
function qp(search) {
var o = {};
if (!search) return o;
String(search).replace(/^\?/, '').split('&').forEach(function (kv) {
if (!kv) return;
var i = kv.indexOf('='), k = i < 0 ? kv : kv.slice(0, i), v = i < 0 ? '' : kv.slice(i + 1);
try { k = decodeURIComponent(k.replace(/\+/g, ' ')); v = decodeURIComponent(v.replace(/\+/g, ' ')); } catch (e) {}
k = k.toLowerCase();
if (!(k in o)) o[k] = v;
});
return o;
}
function hostOf(url) {
try { return new URL(url).hostname.replace(/^www\./, '').toLowerCase(); } catch (e) { return ''; }
}
function suffixIn(h, list) {
for (var i = 0; i < list.length; i++) { var s = list[i]; if (h === s || h.slice(-(s.length + 1)) === '.' + s) return true; }
return false;
}
function isOwn(h) { return !!h && (h === CFG.ownDomain || h.slice(-(CFG.ownDomain.length + 1)) === '.' + CFG.ownDomain); }
function isSearchHost(h) {
var parts = h.split('.');
for (var i = 0; i < SEARCH_HOSTS.length; i++) { if (parts.indexOf(SEARCH_HOSTS[i]) > -1) return true; }
return false;
}
/* Channel from campaign parameters first, then from the referrer. Returns '' if the visit is internal. */
function classify(p, refHost) {
p = p || {};
var med = String(p.utm_medium || '').toLowerCase().trim();
var src = String(p.utm_source || '').toLowerCase().trim();
var paidMed = /^(cpc|ppc|paid|paidsearch|paid[-_ ]search|sem|cpm|cpv|paid[-_ ]?social|social[-_ ]?paid|display|banner|retargeting|remarketing)$/;
if (p.gclid || p.gbraid || p.wbraid || p.msclkid) return 'Paid search';
if (paidMed.test(med)) return (/social/.test(med) || SOCIAL_SRC.indexOf(src) > -1) ? 'Paid social' : 'Paid search';
if (/^(email|e-mail|newsletter|mail)$/.test(med)) return 'Email';
if (med === 'organic') return 'Organic search';
if (/^(social|social-network|social-media|sm)$/.test(med) || SOCIAL_SRC.indexOf(src) > -1) return 'Organic social';
if (p.fbclid) return 'Organic social'; // fbclid is added to ALL outbound Facebook clicks; paid Meta ads need utm_medium=paid_social
if (src || med) return 'Referral';
if (!refHost) return 'Direct';
if (isOwn(refHost)) return '';
if (suffixIn(refHost, MAIL_HOSTS)) return 'Email';
if (isSearchHost(refHost)) return 'Organic search';
if (suffixIn(refHost, SOCIAL_HOSTS)) return 'Organic social';
return 'Referral';
}
/* ---------- storage: sessionStorage + session cookie; longer cookie only with marketing consent ---------- */
function cget(n) {
var m = d.cookie.match(new RegExp('(?:^|; )' + n + '=([^;]*)'));
return m ? decodeURIComponent(m[1]) : null;
}
function cset(n, v, days) {
var s = n + '=' + encodeURIComponent(v) + '; path=/; SameSite=Lax' + (w.location.protocol === 'https:' ? '; Secure' : '');
if (days) s += '; max-age=' + (days * 86400);
d.cookie = s;
}
function hasMarketingConsent() {
var m = d.cookie.match(/(?:^|; )_iub_cs-[^=]*=([^;]*)/);
if (!m) return false;
try { var c = JSON.parse(decodeURIComponent(m[1])); return !!(c && c.purposes && c.purposes['5']); } catch (e) { return false; }
}
function load() {
var raw = null;
try { raw = w.sessionStorage.getItem(CFG.key); } catch (e) {}
if (!raw) raw = cget(CFG.key);
if (!raw) return null;
try { var r = JSON.parse(raw); if (r && r.exp && r.exp < Date.now()) return null; return r; } catch (e) { return null; }
}
function save(r) {
var consent = hasMarketingConsent();
r.exp = consent ? Date.now() + CFG.persistDays * 86400000 : 0;
var raw = JSON.stringify(r);
try { w.sessionStorage.setItem(CFG.key, raw); } catch (e) {}
try { cset(CFG.key, raw, consent ? CFG.persistDays : 0); } catch (e) {}
}
function build(p, refHost, channel) {
var ref = '';
if (refHost && !isOwn(refHost)) {
try { var u = new URL(d.referrer); ref = u.hostname + u.pathname; } catch (e) { ref = refHost; } // never keep the query string
}
return {
g: cut(p.gclid || (p.gbraid ? 'gbraid:' + p.gbraid : '') || (p.wbraid ? 'wbraid:' + p.wbraid : '')),
f: cut(p.fbclid), s: cut(p.utm_source), m: cut(p.utm_medium), c: cut(p.utm_campaign), t: cut(p.utm_term),
lp: cut(w.location.origin + w.location.pathname), rf: cut(ref),
ch: channel, ts: Date.now(), exp: 0
};
}
function capture() {
var p = qp(w.location.search);
var refHost = hostOf(d.referrer);
var hasCampaign = !!(p.gclid || p.gbraid || p.wbraid || p.msclkid || p.fbclid || p.utm_source || p.utm_medium || p.utm_campaign);
var stored = load();
var external = refHost && !isOwn(refHost);
if (hasCampaign || external) {
save(build(p, refHost, classify(p, refHost) || 'Direct')); // a new campaign or external visit replaces the old touch
} else if (!stored) {
save(build(p, '', 'Direct'));
}
return load();
}
/* ---------- hidden fields on the Freshworks forms ---------- */
function fieldMap(r) {
return {
cf_gclid: r.g, cf_fbclid: r.f, cf_utm_source: r.s, cf_utm_medium: r.m, cf_utm_campaign: r.c,
cf_utm_term: r.t, cf_landing_page: r.lp, cf_referrer: r.rf, cf_traffic_channel: r.ch
};
}
function setValue(el, v) {
if (el.tagName === 'SELECT') {
for (var i = 0; i < el.options.length; i++) {
if (el.options[i].text === v || el.options[i].value === v) { el.selectedIndex = i; break; }
}
} else { el.value = v; }
try { el.dispatchEvent(new Event('input', { bubbles: true })); el.dispatchEvent(new Event('change', { bubbles: true })); } catch (e) {}
}
function fillForm(form) {
var r = load() || capture();
if (!r) return;
var map = fieldMap(r);
Object.keys(map).forEach(function (k) {
var name = 'contact[custom_field][' + k + ']';
var el = form.querySelector('[name="' + name + '"]');
if (!el) { // fallback when the form builder did not add the field
el = d.createElement('input'); el.type = 'hidden'; el.name = name; el.setAttribute('data-atlas', '1');
form.appendChild(el);
}
setValue(el, map[k] || 'none'); // Freshworks hidden fields are always required, so never send an empty value
});
}
function isEnquiryForm(f) {
var a = String(f.getAttribute('action') || '');
return /myfreshworks\.com|smart_form|freshsales/i.test(a) || !!f.querySelector('[name^="contact["]');
}
function scan() {
var forms = d.forms;
for (var i = 0; i < forms.length; i++) {
if (isEnquiryForm(forms[i])) { try { fillForm(forms[i]); } catch (e) {} }
}
}
/* ---------- WhatsApp links: add a source code to the prefilled message ---------- */
function waCode(r) {
var ch = CHANNEL_CODE[(r || {}).ch] || 'UNK';
var seg = (w.location.pathname.replace(/^\/+|\/+$/g, '').split('/')[0] || 'home').toUpperCase().replace(/[^A-Z0-9-]/g, '').slice(0, 16);
return ch + '-' + seg;
}
function isWaLink(a) {
var h = a && a.getAttribute && a.getAttribute('href');
return !!h && h.indexOf(CFG.waPhone) > -1 && /whatsapp\.com\/send|api\.whatsapp\.com|wa\.me\//i.test(h);
}
function patchWa(a) {
if (!CFG.waEnabled || !isWaLink(a)) return;
var r = load() || capture();
var existing = '';
try { existing = new URL(a.href).searchParams.get('text') || ''; } catch (e) {}
existing = existing.replace(/\s*\[ref:[A-Z0-9-]+\]\s*$/, '');
var text = (existing || CFG.waText) + ' [ref:' + waCode(r) + ']';
a.href = 'https://wa.me/' + CFG.waPhone + '?text=' + encodeURIComponent(text);
}
function init() {
try { capture(); } catch (e) {}
try { scan(); } catch (e) {}
try {
new MutationObserver(function () { scan(); }).observe(d.documentElement, { childList: true, subtree: true });
} catch (e) {}
// refill just before any submit, in case the form was rebuilt
d.addEventListener('submit', function (ev) {
try { if (ev.target && ev.target.tagName === 'FORM' && isEnquiryForm(ev.target)) fillForm(ev.target); } catch (e) {}
}, true);
// patch WhatsApp links at click time (covers links added later by plugins)
d.addEventListener('click', function (ev) {
try { var a = ev.target && ev.target.closest ? ev.target.closest('a[href]') : null; if (a) patchWa(a); } catch (e) {}
}, true);
if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', function () { try { scan(); } catch (e) {} });
}
w.AtlasSource = { version: CFG.version, get: function () { return load(); }, _classify: classify, _qp: qp, _waCode: waCode };
init();
})(window, document);
Skip to content
The History Of Portobello House
atlas 2022-02-18T16:48:49+01:00