PageSourceSearch

https://monbachtal.de/wp-content/plugins/metform-pro/core/analytics/js/tracking.js?ver=4.3.0

js monbachtal.de collected 2026-09-26 12:34:39 UTC 12,093 bytes, 340 lines download raw bytes

1(function () {
2    'use strict';
3
4    // Early exit if no MetForm forms on the page (check immediately if DOM is ready)
5    if (!document.querySelector('.mf-form-wrapper')) {
6        return;
7    }
8
9    // --- Configuration ---
10    const activeSessions = {}; // Track active form sessions for metadata
11
12    /**
13     * Calculate session duration for a form
14     * @param {string} formId - Form ID
15     * @returns {number} Duration in seconds
16     */
17    function getSessionDuration(formId) {
18        const key = 'mf_session_' + formId;
19        const stored = sessionStorage.getItem(key);
20
21        if (!stored) {
22            return 0;
23        }
24
25        try {
26            const session = JSON.parse(stored);
27            return Math.floor((Date.now() - session.startTime) / 1000);
28        } catch (e) {
29            return 0;
30        }
31    }
32
33    // --- 2. Client-side detection functions ---
34    function detectDevice() {
35        const ua = navigator.userAgent.toLowerCase();
36        if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) {
37            return 'tablet';
38        }
39        if (/Mobile|iP(hone|od)|Android|BlackBerry|IEMobile|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/i.test(ua)) {
40            return 'mobile';
41        }
42        return 'desktop';
43    }
44    function detectBrowser() {
45        const ua = navigator.userAgent.toLowerCase();
46        if (ua.indexOf('opera') > -1 || ua.indexOf('opr') > -1) return 'opera';
47        if (ua.indexOf('edg') > -1) return 'edge';
48        if (ua.indexOf('firefox') > -1) return 'firefox';
49        if (ua.indexOf('trident') > -1 || ua.indexOf('msie') > -1) return 'ie';
50        if (ua.indexOf('chrome') > -1) return 'chrome';
51        if (ua.indexOf('safari') > -1) return 'safari';
52        return 'other';
53    }
54
55    function detectTrafficSource() {
56        const referrer = document.referrer;
57        if (!referrer) {
58            return 'direct';
59        }
60
61        const currentDomain = window.location.hostname;
62        const referrerUrl = new URL(referrer);
63        const referrerDomain = referrerUrl.hostname;
64
65        if (referrerDomain === currentDomain) {
66            return 'direct';
67        }
68
69        const searchEngines = ['google', 'bing', 'yahoo', 'duckduckgo', 'baidu', 'yandex'];
70        for (const engine of searchEngines) {
71            if (referrerDomain.includes(engine)) {
72                return 'organic';
73            }
74        }
75
76        return 'referral';
77    }
78
79    /**
80     * Get referrer URL with same-domain filtering
81     * Returns empty string if referrer is same domain or not available
82     * @returns {string} Referrer URL or empty string if same domain or not available
83     */
84    function getReferrer() {
85        const referrer = document.referrer;
86        if (!referrer) {
87            return '';
88        }
89
90        try {
91            const currentDomain = window.location.hostname;
92            const referrerUrl = new URL(referrer);
93            const referrerDomain = referrerUrl.hostname;
94
95            // Return empty if same domain
96            if (referrerDomain === currentDomain) {
97                return '';
98            }
99        } catch (e) {
100            // If URL parsing fails, return the referrer as-is
101        }
102
103        return referrer;
104    }
105
106    /**
107     * Set cookie
108     * @param {string} name - Cookie name
109     * @param {string} value - Cookie value
110     * @param {number} days - Days until expiry
111     */
112    function setCookie(name, value, days) {
113        const date = new Date();
114        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
115        const expires = "expires=" + date.toUTCString();
116        const secure = window.location.protocol === 'https:' ? ';Secure' : '';
117        document.cookie = name + "=" + value + ";" + expires + ";path=/;SameSite=Lax" + secure;
118    }
119
120    // --- Country detection start ---
121    let countryData = null;
122    let countryFetched = false;
123
124    /**
125     * Fetch country name from ipinfo.io (client-side)
126     * Caches result in memory to avoid multiple requests
127     * @returns {Promise<string>} Country name or empty string if not available
128     */
129    async function getCountry() {
130        // Return cached country if already fetched
131        if (countryFetched) {
132            return countryData;
133        }
134
135        // Respect the server-side privacy gate: skip the third-party (ipinfo.io)
136        // lookup unless country detection is explicitly enabled.
137        if (typeof metformAnalyticsTracking === 'undefined' || !metformAnalyticsTracking.countryDetection) {
138            countryData = '';
139            countryFetched = true;
140            return countryData;
141        }
142
143        try {
144            const response = await fetch('https://ipinfo.io/json');
145            if (response.ok) {
146                const data = await response.json();
147                countryData = data.country || '';
148            }
149        } catch (e) {
150            // Silently fail if country detection doesn't work
151            countryData = '';
152        }
153
154        countryFetched = true;
155        return countryData;
156    }
157
158    /**
159     * Track form view with analytics data
160     * async function to allow country detection before sending data
161     * @param {string} formId - The ID of the form being viewed
162     */
163    async function trackFormView(formId) {
164        if (typeof metformAnalyticsTracking === 'undefined' || !metformAnalyticsTracking.nonce) {
165            return;
166        }
167
168        const duration = getSessionDuration(formId);
169        const device = detectDevice();
170        const browser = detectBrowser();
171        const source = detectTrafficSource();
172        const referrer = getReferrer();
173        const country = await getCountry();
174
175        // Persist country/referrer for the server-side conversion tracker
176        // (base.php::track_submission reads this cookie on form submission).
177        const handoff = JSON.stringify({ country: country || '', referrer: referrer || '' });
178        setCookie('mf_analytics_' + formId, encodeURIComponent(handoff), 1);
179
180        fetch(`${metformAnalyticsTracking.restUrl}/track_view/${formId}`, {
181            method: 'POST',
182            headers: {
183                'X-WP-Nonce': metformAnalyticsTracking.nonce,
184                'Content-Type': 'application/json',
185            },
186            body: JSON.stringify({
187                session_duration: duration,
188                device: device,
189                browser: browser,
190                source: source,
191                referrer: referrer,
192                country: country || ''
193            })
194        }).catch(() => {});
195    }
196
197    /**
198     * Send incremental duration update (Burst-style delta)
199     * This allows multiple updates without double counting
200     * @param {string} formId - Form ID
201     * @param {boolean} isFinal - Whether this is a final update
202     */
203    function sendDurationUpdate(formId) {
204        if (typeof metformAnalyticsTracking === 'undefined') return;
205
206        const sessionKey = 'mf_session_' + formId;
207        const stored = sessionStorage.getItem(sessionKey);
208        if (!stored) return;
209
210        const session = JSON.parse(stored);
211        const totalDuration = Math.floor((Date.now() - session.startTime) / 1000);
212        const delta = totalDuration - (session.sentDuration || 0);
213
214        // Only send if there's new time (at least 1 second)
215        if (delta <= 0) return;
216
217        // Update sent duration in storage BEFORE sending to handle race conditions
218        session.sentDuration = (session.sentDuration || 0) + delta;
219        sessionStorage.setItem(sessionKey, JSON.stringify(session));
220
221        const data = JSON.stringify({
222            session_duration: delta, // Send only the increment
223            is_update: true // Crucial: This ensures views are NOT incremented on update
224        });
225
226        const url = `${metformAnalyticsTracking.restUrl}/track_view/${formId}`;
227
228        // send duration update to server
229        if (navigator.sendBeacon) { // sendBeacon is used for sending data to the server when the page is being unloaded
230            const urlWithNonce = `${url}?_wpnonce=${metformAnalyticsTracking.nonce}`;
231            const blob = new Blob([data], { type: 'application/json' }); // blob is used for sending data to the server when the page is being unloaded
232            navigator.sendBeacon(urlWithNonce, blob);
233        } else {
234            fetch(url, {
235                method: 'POST',
236                headers: {
237                    'X-WP-Nonce': metformAnalyticsTracking.nonce,
238                    'Content-Type': 'application/json',
239                },
240                body: data,
241                keepalive: true // keepalive is used for sending data to the server when the page is being unloaded
242            }).catch(() => { });
243        }
244    }
245
246    /**
247     * Extract form ID from .mf-form-wrapper element
248     * @param {Element} formWrapper - The .mf-form-wrapper element
249     * @returns {string|null} The form ID or null if not found
250     */
251    function getFormId(formWrapper) {
252        let formId = formWrapper.getAttribute('data-form-id');
253        if (formId) return formId;
254
255        const wrapperId = formWrapper.id;
256        if (wrapperId) {
257            const m = wrapperId.match(/metform-wrap-(\d+)/);
258            if (m) return m[1];
259        }
260        return null;
261    }
262
263    /**
264     * Setup optimized tracking events
265     * Uses visibilitychange and pagehide for incremental updates
266     */
267    function setupEventListeners() {
268        // Track when user switches tabs or minimizes window
269        document.addEventListener('visibilitychange', () => {
270            if (document.hidden) {
271                Object.keys(activeSessions).forEach(formId => {
272                    sendDurationUpdate(formId);
273                });
274            }
275        });
276
277        // Track when page is being hidden (closing tab, navigating away)
278        window.addEventListener('pagehide', () => {
279            Object.keys(activeSessions).forEach(formId => {
280                sendDurationUpdate(formId);
281            });
282        });
283    }
284
285    /**
286     * Initialize IntersectionObserver to track when forms come into view
287     * Observes all .mf-form-wrapper elements and tracks view when they are at least 50% visible
288     * Uses trackFormView to send analytics data when a form is viewed
289     * Ensures each form is only tracked once per page load
290     * @returns {void}
291     */
292    function initIntersectionObserver() {
293        const formWrappers = document.querySelectorAll('.mf-form-wrapper');
294        if (!formWrappers.length) return;
295
296        const observer = new IntersectionObserver((entries) => {
297            entries.forEach((entry) => {
298                if (entry.isIntersecting) {
299                    const formWrapper = entry.target; // .mf-form-wrapper
300                    const formId = getFormId(formWrapper);
301                    if (formId && !activeSessions[formId]) {
302                        // Initialize session for this page load/refresh
303                        const now = Date.now();
304                        const session = {
305                            startTime: now,
306                            sentDuration: 0
307                        };
308                        sessionStorage.setItem('mf_session_' + formId, JSON.stringify(session));
309                        activeSessions[formId] = true;
310
311                        trackFormView(formId);
312                    }
313                }
314            });
315        }, {
316            root: null,
317            rootMargin: '0px',
318            threshold: 0.5, // Consider form "viewed" when 50% visible
319        });
320
321        formWrappers.forEach((wrapper) => observer.observe(wrapper)); // Observe all metform form wrapper in loading page
322    }
323
324    // Auto init 
325    function init() {
326        initIntersectionObserver();
327        setupEventListeners();
328    }
329
330    if (document.readyState === 'loading') {
331        document.addEventListener('DOMContentLoaded', init);
332    } else {
333        init();
334    }
335
336    window.MetFormAnalytics = {
337        trackView: trackFormView,
338        getSessionDuration: (formId) => getSessionDuration(formId)
339    };
340})();

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.