PageSourceSearch

https://loveteethdp.co.uk/wp-content/plugins/pontifex-attribution/assets/submit.js?ver=2.6.4

js loveteethdp.co.uk collected 2026-10-02 00:13:32 UTC 13,482 bytes, 294 lines download raw bytes

1/*!
2 * Pontifex Attribution — Submit Forwarder v2.5.6
3 *
4 * Forwards form submissions to the microservice /lead endpoint via three
5 * complementary hooks (submit event, HTMLFormElement.submit/requestSubmit
6 * patch, and submit-button click fallback). Calendly clicks → /calendly-context.
7 *
8 * The triple-hook approach is needed because modern form builders submit via
9 * different mechanisms: native form submit (event-driven), jQuery .submit()
10 * (event-driven), form.requestSubmit() (event-driven), form.submit() (no event),
11 * and Gravity Forms 2.10+ XHR (no event, no DOM submit call). Each hook is
12 * idempotent via a WeakSet — /lead is sent at most once per form per session.
13 *
14 * Sends a canonical {email, phone} sniffed from HTML5 input types alongside
15 * the raw fields, so the Worker can identify the contact even when the form
16 * builder uses opaque field names (Gravity Forms `input_3`, WPForms, etc.).
17 *
18 * Refuses to send anything if no site_token is configured.
19 */
20(function () {
21    var CFG = window.PFX_CONFIG || {};
22    var DBG = !!CFG.debug;
23    function log() { if (DBG && window.console) { try { console.log.apply(console, ['[pfx-submit]'].concat([].slice.call(arguments))); } catch(e){} } }
24
25    if (!CFG.siteToken) {
26        log('disabled — no site_token configured (plugin not authorized)');
27        return;
28    }
29    if (!CFG.microserviceUrl) {
30        log('disabled — no microservice URL configured');
31        return;
32    }
33
34    function tracking() {
35        try { return JSON.parse(localStorage.getItem('tracking_params') || '{}'); } catch(e) { return {}; }
36    }
37
38    function post(path, body) {
39        var url = CFG.microserviceUrl + path;
40        var payload = JSON.stringify(body);
41        try {
42            // Use fetch with keepalive instead of sendBeacon to avoid CORS
43            // preflight issues with JSON content-type on cross-origin requests.
44            fetch(url, {
45                method: 'POST',
46                headers: { 'Content-Type': 'application/json' },
47                body: payload,
48                keepalive: true,
49                mode: 'cors'
50            }).then(function() { log('sent', path); })
51              .catch(function(e) { log('send failed', path, e); });
52        } catch(e) { log('post failed', path, e); }
53    }
54
55    function isExcluded(form) {
56        var sels = (CFG.excludedSelectors || []);
57        for (var i = 0; i < sels.length; i++) {
58            try { if (sels[i] && form.matches(sels[i])) return true; } catch(e) {}
59        }
60        return false;
61    }
62
63    // Marketing/analytics endpoints that some WP plugins (PixelYourSite, Meta
64    // CAPI, GA-conversion proxies) hit by submitting a hidden <form action="…">.
65    // Those aren't real user submissions, just conversion pings — but our
66    // submit hook can't tell the difference at the DOM level, so we filter on
67    // form.action here. Worker has a matching defensive denylist for clients
68    // still running an older tracker.
69    var TRACKING_HOSTS = [
70        'facebook.com/tr',
71        'google-analytics.com/collect',
72        'google-analytics.com/g/collect',
73        'analytics.google.com/g/collect',
74        'analytics.tiktok.com',
75        'bat.bing.com',
76        'linkedin.com/li.lms-analytics',
77        'analytics.pinterest.com',
78        'ct.pinterest.com',
79        'googleadservices.com/pagead/conversion',
80        'doubleclick.net',
81        'analytics.snapchat.com'
82    ];
83
84    function isTrackingPixelForm(form) {
85        var a = (form && form.action) || '';
86        if (!a) return false;
87        a = String(a).toLowerCase();
88        for (var i = 0; i < TRACKING_HOSTS.length; i++) {
89            if (a.indexOf(TRACKING_HOSTS[i]) !== -1) return true;
90        }
91        return false;
92    }
93
94    // Divi's Visual Builder POSTs a synthetic <form id="preview-data-submission">
95    // (carrying is_fb_preview / et_pb_preview_nonce, action ?et_pb_preview=true)
96    // every time it re-renders shortcode previews while a page is being edited.
97    // Those aren't real submissions, but our submit hook can't tell at the DOM
98    // level, so one editing session floods /lead with no-email pings. Filter
99    // them here; the Worker has a matching defensive check for older trackers.
100    function isBuilderPreviewForm(form) {
101        try {
102            if (form.id === 'preview-data-submission') return true;
103            if (form.querySelector('[name="is_fb_preview"], [name="et_pb_preview_nonce"]')) return true;
104            var a = String((form && form.action) || '').toLowerCase();
105            if (a.indexOf('et_pb_preview=true') !== -1) return true;
106        } catch(e) {}
107        return false;
108    }
109
110    function detectFormSource(form) {
111        if (form.id && /gform_/.test(form.id)) return 'gravity_forms';
112        if (form.className && /wpcf7-form/.test(form.className)) return 'contact_form_7';
113        if (form.id && /wpforms-form/.test(form.id)) return 'wpforms';
114        if (form.className && /frm-fluent-form|fluentform/.test(form.className)) return 'fluent_forms';
115        if (form.action && /aweber\.com/.test(form.action)) return 'aweber';
116        if (form.action && /list-manage\.com/.test(form.action)) return 'mailchimp';
117        return 'generic';
118    }
119
120    function serializeForm(form) {
121        var out = {};
122        try {
123            var fd = new FormData(form);
124            fd.forEach(function (val, key) {
125                if (typeof File !== 'undefined' && val instanceof File) return;
126                if (out[key] !== undefined) {
127                    if (!Array.isArray(out[key])) out[key] = [out[key]];
128                    out[key].push(val);
129                } else {
130                    out[key] = val;
131                }
132            });
133        } catch(e) {}
134        return out;
135    }
136
137    // Identify canonical contact fields by HTML5 input type, not by name.
138    // Form builders use opaque names (Gravity Forms 'input_3', WPForms
139    // 'wpforms[fields][N]') that the Worker cannot decode. Sniffing types here
140    // gives the Worker reliable email/phone regardless of which builder is used.
141    function extractCanonical(form) {
142        var out = {};
143        try {
144            var emails = form.querySelectorAll('input[type="email"]');
145            for (var i = 0; i < emails.length; i++) {
146                var ev = (emails[i].value || '').trim();
147                if (ev) { out.email = ev; break; }
148            }
149            var tels = form.querySelectorAll('input[type="tel"]');
150            for (var j = 0; j < tels.length; j++) {
151                var pv = (tels[j].value || '').trim();
152                if (pv) { out.phone = pv; break; }
153            }
154        } catch(e) {}
155        return out;
156    }
157
158    // ── FORM SUBMIT FORWARDER ────────────────────────────────────────────────
159    // Modern form builders submit through several different mechanisms.
160    // Hook 1 — submit event capture: catches jQuery .submit(), form.requestSubmit(),
161    //   and standard browser-initiated form submission.
162    // Hook 2 — HTMLFormElement.prototype.submit/requestSubmit patch: catches direct
163    //   DOM API calls. form.submit() does NOT fire a submit event.
164    // Hook 3 — submit-button click + checkValidity: catches Gravity Forms 2.10's
165    //   XHR submission path which bypasses BOTH the submit event and form.submit().
166    // All hooks dedupe via __pfxSentForms so /lead is sent at most once per form.
167    var __pfxSentForms = (typeof WeakSet !== 'undefined')
168        ? new WeakSet()
169        : { has: function(){ return false; }, add: function(){} };
170
171    function sendLead(form) {
172        if (!form || form.tagName !== 'FORM') return;
173        if (isExcluded(form)) return;
174        if (isTrackingPixelForm(form)) {
175            // Email-bypass exception: if the form has a real email value, send
176            // anyway. Meta CAPI and similar conversion wrappers can ride on
177            // real form submissions; a misconfigured plugin might also point a
178            // genuine form's action at a tracking endpoint by accident. Don't
179            // drop a real lead just because the action looks pixel-shaped.
180            var probe = extractCanonical(form);
181            if (!probe.email || !/.+@.+\..+/.test(probe.email)) {
182                log('skipped tracking-pixel form:', form.action);
183                return;
184            }
185            log('tracking-pixel action with valid email - processing:', form.action);
186        }
187        if (isBuilderPreviewForm(form)) {
188            log('skipped builder preview submission:', form.id || form.action);
189            return;
190        }
191        if (__pfxSentForms.has(form)) return;
192        __pfxSentForms.add(form);
193
194        var fields = serializeForm(form);
195        var t = tracking();
196        var cleaned = {};
197        Object.keys(fields).forEach(function (k) { if (!(k in t)) cleaned[k] = fields[k]; });
198
199        post('/lead', {
200            site_token:      CFG.siteToken,
201            site_url:        CFG.siteUrl,
202            form_source:     detectFormSource(form),
203            form_id:         form.id || '',
204            form_action:     form.action || '',
205            page_url:        window.location.href,
206            fields:          cleaned,
207            canonical:       extractCanonical(form),
208            tracking_params: t,
209            ts:              Date.now()
210        });
211    }
212
213    if (CFG.enableForms) {
214        // Hook 1: submit event in capture phase.
215        document.addEventListener('submit', function (e) {
216            if (e.target && e.target.tagName === 'FORM') sendLead(e.target);
217        }, true);
218
219        // Hook 2: monkey-patch DOM submission methods.
220        try {
221            var __realSubmit = HTMLFormElement.prototype.submit;
222            HTMLFormElement.prototype.submit = function () {
223                sendLead(this);
224                return __realSubmit.apply(this, arguments);
225            };
226            if (typeof HTMLFormElement.prototype.requestSubmit === 'function') {
227                var __realRequestSubmit = HTMLFormElement.prototype.requestSubmit;
228                HTMLFormElement.prototype.requestSubmit = function () {
229                    sendLead(this);
230                    return __realRequestSubmit.apply(this, arguments);
231                };
232            }
233        } catch (e) { log('hook2 patch failed', e); }
234
235        // Hook 3: click on submit button → wait briefly for hooks 1/2, then fire
236        // /lead if neither did. checkValidity gates out invalid submissions.
237        document.addEventListener('click', function (e) {
238            var t = e.target;
239            var btn = (t && (t.tagName === 'BUTTON' || t.tagName === 'INPUT'))
240                ? t
241                : (t && t.closest && t.closest('button[type=submit], input[type=submit]'));
242            if (!btn || btn.type !== 'submit') return;
243            var form = btn.closest && btn.closest('form');
244            if (!form) return;
245            setTimeout(function () {
246                if (__pfxSentForms.has(form)) return;
247                if (typeof form.checkValidity === 'function' && !form.checkValidity()) return;
248                sendLead(form);
249            }, 250);
250        }, true);
251    }
252
253    // ── CALENDLY CONTEXT FORWARDER ───────────────────────────────────────────
254    if (CFG.enableCalendly) {
255        var lastSent = 0;
256        function sendContext(reason) {
257            var t = tracking();
258            if (!t.visitor_id) return;
259            var now = Date.now();
260            if (now - lastSent < 5000) return;
261            lastSent = now;
262            post('/calendly-context', {
263                site_token:      CFG.siteToken,
264                site_url:        CFG.siteUrl,
265                visitor_id:      t.visitor_id,
266                tracking_params: t,
267                page_url:        window.location.href,
268                reason:          reason || 'click',
269                ts:              now
270            });
271        }
272
273        document.addEventListener('click', function (e) {
274            var el = e.target;
275            while (el && el !== document.body) {
276                if (el.tagName === 'A' && el.href && /calendly\.com/.test(el.href)) { sendContext('link'); return; }
277                if (el.getAttribute && el.getAttribute('onclick') && /Calendly\.initPopupWidget/.test(el.getAttribute('onclick'))) { sendContext('popup'); return; }
278                if (el.classList && (el.classList.contains('calendly-inline-widget') || el.classList.contains('calendly-badge-widget'))) { sendContext('inline'); return; }
279                el = el.parentNode;
280            }
281        }, true);
282
283        function scanForEmbeds() {
284            if (document.querySelector('.calendly-inline-widget, .calendly-badge-widget, a[href*="c
284alendly.com"], [onclick*="Calendly.initPopupWidget"]')) {
285                sendContext('embed_present');
286            }
287        }
288        if (document.readyState === 'loading') {
289            document.addEventListener('DOMContentLoaded', scanForEmbeds);
290        } else {
291            scanForEmbeds();
292        }
293    }
294})();

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.