PageSourceSearch

https://proinvestor.com/forum/assets/plugins/nodebb-theme-proinvestor/js/consent-init.js?v=a8ad02f4ce9

js proinvestor.com collected 2026-09-26 04:23:58 UTC 13,546 bytes, 383 lines download raw bytes

1/* global CookieConsent */
2
3'use strict';
4
5(function () {
6  var COOKIE_NAME = 'cc_cookie';
7
8  // Delivered by the theme's filter:config.get hook. window.__piConsentDomain
9  // is the pre-54bd4e6 fallback, when the (now removed) header.tpl inlined it.
10  function settings() {
11    var cfg = (window.config && window.config.piConsent) || {};
12    return {
13      cookieDomain: cfg.cookieDomain || window.__piConsentDomain || '',
14      gtmId: cfg.gtmId || '',
15    };
16  }
17
18  // Signed-in summary for the analytics user properties. lib/theme.js
19  // (injectConsentScripts) writes it as a JSON data block next to these script
20  // tags — a data block never executes, so CSP is not a concern.
21  function readUser() {
22    var el = document.getElementById('pi-user');
23    if (!el) return { uid: 0, tier: 'guest' };
24    try {
25      var parsed = JSON.parse(el.textContent || '{}');
26      return { uid: Number(parsed.uid) || 0, tier: parsed.tier || 'guest' };
27    } catch (_err) {
28      return { uid: 0, tier: 'guest' };
29    }
30  }
31
32  // Category / page type for the forum pageview push, from ajaxify's page data.
33  function forumCategoryName(page) {
34    if (!page) return '';
35    if (page.template && page.template.name === 'category' && page.name) return page.name;
36    if (page.category && page.category.name) return page.category.name;
37    return '';
38  }
39
40  function hasConsent(category) {
41    var needle = COOKIE_NAME + '=';
42    var parts = document.cookie.split(';');
43
44    for (var i = 0; i < parts.length; i += 1) {
45      var value = parts[i].trim();
46      if (!value.startsWith(needle)) continue;
47
48      var raw = value.slice(needle.length);
49      var parsed;
50
51      try {
52        parsed = JSON.parse(decodeURIComponent(raw));
53      } catch (_decodeErr) {
54        try {
55          parsed = JSON.parse(raw);
56        } catch (_jsonErr) {
57          parsed = null;
58        }
59      }
60
61      return !!(parsed && Array.isArray(parsed.categories) && parsed.categories.includes(category));
62    }
63
64    return false;
65  }
66
67  // Run `callback` as soon as `category` is granted — now, or whenever the
68  // visitor accepts later. Never runs if they decline.
69  function whenConsented(category, callback) {
70    if (hasConsent(category)) {
71      callback();
72      return;
73    }
74
75    var onConsent = function () {
76      if (!hasConsent(category)) return;
77      window.removeEventListener('cc:onConsent', onConsent);
78      window.removeEventListener('cc:onChange', onConsent);
79      window.removeEventListener('pi:consent:changed', onConsent);
80      callback();
81    };
82
83    window.addEventListener('cc:onConsent', onConsent);
84    window.addEventListener('cc:onChange', onConsent);
85    window.addEventListener('pi:consent:changed', onConsent);
86  }
87
88  function ensureGptScript(onReady) {
89    if (window.__piGptScriptLoaded) {
90      onReady();
91      return;
92    }
93
94    if (window.__piGptScriptLoading) {
95      window.__piGptQueue = window.__piGptQueue || [];
96      window.__piGptQueue.push(onReady);
97      return;
98    }
99
100    window.__piGptScriptLoading = true;
101    window.__piGptQueue = [onReady];
102
103    var script = document.createElement('script');
104    script.async = true;
105    script.src = 'https://securepubads.g.doubleclick.net/tag/js/gpt.js';
106
107    script.onload = function () {
108      window.__piGptScriptLoaded = true;
109      window.__piGptScriptLoading = false;
110      var queue = window.__piGptQueue || [];
111      window.__piGptQueue = [];
112      queue.forEach(function (fn) {
113        try {
114          fn();
115        } catch (_err) {
116          // swallow callback errors
117        }
118      });
119    };
120
121    script.onerror = function () {
122      window.__piGptScriptLoading = false;
123      window.__piGptQueue = [];
124    };
125
126    document.head.appendChild(script);
127  }
128
129  function createConsentConfig() {
130    var domain = settings().cookieDomain;
131
132    return {
133      disablePageInteraction: true,
134      cookie: {
135        name: COOKIE_NAME,
136        domain: domain,
137        path: '/',
138        sameSite: 'Lax',
139        expiresAfterDays: 182,
140      },
141      guiOptions: {
142        consentModal: {
143          layout: 'box',
144          position: 'middle center',
145          equalWeightButtons: true,
146        },
147        preferencesModal: {
148          layout: 'box',
149          equalWeightButtons: true,
150        },
151      },
152      categories: {
153        necessary: {
154          enabled: true,
155          readOnly: true,
156        },
157        ads: {
158          enabled: false,
159        },
160        analytics: {
161          enabled: false,
162        },
163        functional: {
164          enabled: false,
165        },
166      },
167      language: {
168        default: 'da',
169        autoDetect: 'document',
170        translations: {
171          da: {
172            consentModal: {
173              title: 'Vi bruger cookies',
174              description: 'Vi bruger nødvendige cookies til drift og valgfri co
174okies til funktioner og annoncer. Du kan altid ændre dit valg via cookieknappen nederst til venstre.',
175              acceptAllBtn: 'Accepter alle',
176              acceptNecessaryBtn: 'Kun nødvendige',
177              showPreferencesBtn: 'Tilpas valg',
178            },
179            preferencesModal: {
180              title: 'Cookieindstillinger',
181              acceptAllBtn: 'Accepter alle',
182              acceptNecessaryBtn: 'Kun nødvendige',
183              savePreferencesBtn: 'Gem valg',
184              closeIconLabel: 'Luk',
185              sections: [
186                {
187                  title: 'SÃ¥dan bruger vi cookies',
188                  description: 'Du kan vælge, hvilke valgfri cookies vi må bruge. Nødvendige cookies kan ikke fravælges.',
189                },
190                {
191                  title: 'Nødvendige cookies',
192                  description: 'Påkrævet for login, sikkerhed og grundlæggende drift.',
193                  linkedCategory: 'necessary',
194                },
195                {
196                  title: 'Funktionelle cookies',
197                  description: 'Forbedrer funktioner og brugeroplevelse.',
198                  linkedCategory: 'functional',
199                },
200                {
201                  title: 'Statistikcookies',
202                  description:
203                    'Bruges til at måle trafik og forstå, hvordan siden bruges, så vi kan forbedre den.',
204                  linkedCategory: 'analytics',
205                },
206                {
207                  title: 'Annoncecookies',
208                  description: 'Bruges til annoncering og måling af annoncelevering.',
209                  linkedCategory: 'ads',
210                },
211                {
212                  title: 'Mere information',
213                  description: 'Læs mere i vores <a href="/privatlivspolitik">privatlivspolitik</a>.',
214                },
215              ],
216            },
217          },
218          en: {
219            consentModal: {
220              title: 'We use cookies',
221              description: 'We use necessary cookies for core functionality and optional cookies for features and ads. You can always update your choice with the cookie button in the bottom-left corner.',
222              acceptAllBtn: 'Accept all',
223              acceptNecessaryBtn: 'Necessary only',
224              showPreferencesBtn: 'Manage choices',
225            },
226            preferencesModal: {
227              title: 'Cookie preferences',
228              acceptAllBtn: 'Accept all',
229              acceptNecessaryBtn: 'Necessary only',
230              savePreferencesBtn: 'Save choices',
231              closeIconLabel: 'Close',
232              sections: [
233                {
234                  title: 'How we use cookies',
235                  description: 'Choose which optional cookie categories you allow. Necessary cookies cannot be disabled.',
236                },
237                {
238                  title: 'Necessary cookies',
239                  description: 'Required for login, security and core functionality.',
240                  linkedCategory: 'necessary',
241                },
242                {
243                  title: 'Functional cookies',
244                  description: 'Improve features and user experience.',
245                  linkedCategory: 'functional',
246                },
247                {
248                  title: 'Analytics cookies',
249                  description:
250                    'Used to measure traffic and understand how the site is used so we can improve it.',
251                  linkedCategory: 'analytics',
252                },
253                {
254                  title: 'Advertising cookies',
255                  description: 'Used for ad delivery and ad measurement.',
256                  linkedCategory: 'ads',
257                },
258                {
259                  title: 'More information',
260                  description: 'See our <a href="/privatlivspolitik">privacy policy</a>.',
261                },
262              ],
263            },
264          },
265        },
266      },
267      onFirstConsent: function () {
268        window.dispatchEvent(new Event('pi:consent:changed'));
269      },
270      onConsent: function () {
271        window.dispatchEvent(new Event('pi:consent:changed'));
272      },
273      onChange: function () {
274        window.dispatchEvent(new Event('pi:consent:changed'));
275      },
276    };
277  }
278
279  function initConsent() {
280    if (!window.CookieConsent || window.__piConsentInitialized) {
281      window.dispatchEvent(new Event('pi:consent:ready'));
282      return;
283    }
284
285    window.CookieConsent.run(createConsentConfig());
286    window.__piConsentInitialized = true;
287    window.dispatchEvent(new Event('pi:consent:ready'));
288  }
289
290  window.piShowConsentPreferences = function () {
291    if (window.CookieConsent && typeof window.CookieConsent.showPreferences === 'function') {
292      window.CookieConsent.showPreferences();
293    }
294  };
295
296  window.piLoadGptIfConsented = function (callback) {
297    whenConsented('ads', function () {
298      ensureGptScript(callback);
299    });
300  };
301
302  // ── Google Tag Manager ──────────────────────────────────────────────────────
303  // Same container as the Next.js side (frontend/src/components/
303analytics/
304  // GtmLoader.tsx), so forum and site sessions land in one GA property. Loads
305  // only once the 'analytics' category is granted — the CMP above writes the
306  // same first-party cc_cookie the frontend reads, so a choice made on either
307  // end of the site is honoured on the other.
308
309  // ── Event tracking ────────────────────────────────────────────────────────
310  // Forum-side twin of frontend/src/lib/analytics/track.ts: same event names,
311  // same `pi` parameter namespace, same consent rule. Catalogue and GTM setup
312  // in docs/analytics.md. Nothing is pushed without the analytics grant —
313  // pre-consent pushes would be flushed to GA the moment GTM boots. The `pi`
314  // key is reset before every push because GTM's data layer model merges
315  // pushes; without it a `channel` from one event leaks onto the next.
316  window.piTrack = function (eventName, params) {
317    if (!eventName || !hasConsent('analytics')) return;
318    var clean = {};
319    Object.keys(params || {}).forEach(function (key) {
320      var value = params[key];
321      if (value === undefined || value === null || value === '') return;
322      clean[key] = value;
323    });
324    window.dataLayer = window.dataLayer || [];
325    window.dataLayer.push({ pi: null });
326    window.dataLayer.push({ event: eventName, pi: clean });
327  };
328
329  function bootGtm(gtmId) {
330    if (window.__piGtmLoaded) return;
331    window.__piGtmLoaded = true;
332
333    window.dataLayer = window.dataLayer || [];
334
335    // User state goes in BEFORE gtm.start so the Google tag reads user_id /
336    // member_tier on the first page_view. Same on the Next.js side (GtmLoader).
337    var user = readUser();
338    var userState = { member_tier: user.tier };
339    if (user.uid > 0) userState.user_id = String(user.uid);
340    window.dataLayer.push(userState);
341
342    window.dataLayer.push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
343
344    var script = document.createElement('script');
345    script.async = true;
346    script.src = 'https://www.googletagmanager.com/gtm.js?id=' + encodeURIComponent(gtmId);
347    document.head.appendChild(script);
348
349    // NodeBB is a SPA: after the first load every navigation is ajaxify, which
350    // fires no browser pageview. Push one per route change so the forum does
351    // not report a single pageview per session.
352    if (window.$ && typeof window.$(window).on === 'function') {
353      window.$(window).on('action:ajaxify.end', function (ev, data) {
354        var page = (window.ajaxify && window.ajaxify.data) || {};
355        window.dataLayer.push({ pi: null });
356        window.dataLayer.push({
357          event: 'pi.pageview',
358          page_path: (window.config && window.config.relative_path ? window.config.relative_path : '') +
359            '/' + ((data && data.url) || ''),
360          page_title: document.title,
361          // Page type (topic / category / recent / …) and category name, so
362          // "which categories are read" is a report. Mapped by the pi.pageview
363          // tag in GTM — see docs/analytics.md.
364          pi: {
365            forum_template: (page.template && page.template.name) || '',
366            forum_category: forumCategoryName(page),
367          },
368        });
369      });
370    }
371  }
372
373  function initGtm() {
374    var gtmId = settings().gtmId;
375    if (!gtmId) return;
376    whenConsented('analytics', function () {
377      bootGtm(gtmId);
378    });
379  }
380
381  initConsent();
382  initGtm();
383}());

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.