PageSourceSearch

https://lkk.fi/wp-content/plugins/kansleri-cookie-consent/assets/consent.js?ver=1790156110

js lkk.fi collected 2026-10-10 01:04:40 UTC 21,098 bytes, 657 lines download raw bytes

1(function () {
2  'use strict';
3
4  var COOKIE_NAME = 'kncc_consent';
5  var COOKIE_DAYS = 365;
6  var config = window.knccConfig || {};
7
8  function getConsent() {
9    var match = document.cookie.match(new RegExp('(?:^|; )' + COOKIE_NAME + '=([^;]*)'));
10    if (!match) return null;
11    try {
12      return JSON.parse(decodeURIComponent(match[1]));
13    } catch (e) {
14      return null;
15    }
16  }
17
18  function setConsent(consent) {
19    consent.timestamp = new Date().toISOString();
20    var val = encodeURIComponent(JSON.stringify(consent));
21    var expires = new Date(Date.now() + COOKIE_DAYS * 864e5).toUTCString();
22    var cookie = COOKIE_NAME + '=' + val + ';expires=' + expires + ';path=/;SameSite=Lax';
23    if (location.protocol === 'https:') {
24      cookie += ';Secure';
25    }
26    document.cookie = cookie;
27  }
28
29  function dispatchConsentCommand(payload) {
30    if (typeof window.gtag === 'function') {
31      window.gtag('consent', 'update', payload);
32      return;
33    }
34    window.dataLayer = window.dataLayer || [];
35    window.dataLayer.push(['consent', 'update', payload]);
36  }
37
38  function updateConsentMode(consent) {
39    if (!config.consentMode) return;
40
41    dispatchConsentCommand({
42      analytics_storage:        consent.analytics ? 'granted' : 'denied',
43      ad_storage:               consent.marketing ? 'granted' : 'denied',
44      ad_user_data:             consent.marketing ? 'granted' : 'denied',
45      ad_personalization:       consent.marketing ? 'granted' : 'denied',
46      functionality_storage:    consent.preferences ? 'granted' : 'denied',
47      personalization_storage:  consent.preferences ? 'granted' : 'denied',
48      security_storage:         'granted',
49    });
50  }
51
52  function pushDataLayer(consent) {
53    window.dataLayer = window.dataLayer || [];
54    window.dataLayer.push({
55      event: 'consent_update',
56      consent_necessary: true,
57      consent_analytics: !!consent.analytics,
58      consent_marketing: !!consent.marketing,
59      consent_preferences: !!consent.preferences,
60    });
61  }
62
63  function isCategoryGranted(consent, key) {
64    if (key === 'necessary') {
65      return true;
66    }
67    if (consent.choice === 'accept') {
68      return true;
69    }
70    if (consent.choice === 'reject') {
71      return false;
72    }
73    var value = consent[key];
74    return value === true || value === 1 || value === '1' || value === 'true';
75  }
76
77  function getWpConsentPrefix() {
78    if (typeof window.consent_api !== 'undefined' && consent_api.cookie_prefix) {
79      return consent_api.cookie_prefix;
80    }
81    return 'wp_consent';
82  }
83
84  function dispatchWpConsentChange(category, value) {
85    try {
86      var detail = {};
87      detail[category] = value;
88      document.dispatchEvent(new CustomEvent('wp_listen_for_consent_change', { detail: detail }));
89    } catch (e) {}
90  }
91
92  function setWpConsentCookie(category, value) {
93    if (value !== 'allow' && value !== 'deny') {
94      return;
95    }
96
97    if (typeof wp_set_consent === 'function') {
98      wp_set_consent(category, value);
99      return;
100    }
101
102    // Fallback: WP Consent API JS not ready (e.g. delayed by a caching plugin).
103    // Write the cookie directly so the value is never left stale, then notify
104    // integrators like Google Site Kit that consent changed.
105    var prefix = getWpConsentPrefix();
106    var days = (typeof window.consent_api !== 'undefined' && consent_api.cookie_expiration)
107      ? consent_api.cookie_expiration
108      : 30;
109    var date = new Date();
110    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
111    var secure = location.protocol === 'https:' ? ';secure' : '';
112    document.cookie = prefix + '_' + category + '=' + value + secure + ';expires=' + date.toUTCString() + ';path=/';
113    dispatchWpConsentChange(category, value);
114  }
115
116  function clearStaleWpConsentCookie(category) {
117    var prefix = getWpConsentPrefix();
118    var name = prefix + '_' + category;
119    getDeletionDomains().forEach(function (domain) {
120      deleteCookie(name, domain || null);
121    });
122  }
123
124  function syncWpConsentApi(consent) {
125    if (!consent) return;
126
127    var analytics = isCategoryGranted(consent, 'analytics');
128
129    // Remove any stale statistics cookies (host + parent domains) before writing,
130    // so a lingering deny on a parent domain cannot shadow the fresh value.
131    if (analytics) {
132      clearStaleWpConsentCookie('statistics');
133      clearStaleWpConsentCookie('statistics-anonymous');
134    }
135
136    setWpConsentCookie('functional', 'allow');
137    setWpConsentCookie('statistics', analytics ? 'allow' : 'deny');
138    setWpConsentCookie('statistics-anonymous', analytics ? 'allow' : 'deny');
139    setWpConsentCookie('marketing', isCategoryGranted(consent, 'marketing') ? 'allow' : 'deny');
140    setWpConsentCookie('preferences', isCategoryGranted(consent, 'preferences') ? 'allow' : 'deny');
141  }
142
143  function scheduleWpConsentSync(consent) {
144    if (!consent) return;
145    syncWpConsentApi(consent);
146    if (typeof wp_set_consent !== 'function') {
147      setTimeout(function () { syncWpConsentApi(consent); }, 50);
148      setTimeout(function () { syncWpConsentApi(consent); }, 250);
149    }
150  }
151
152  function renderPolicyConsentStatus() {
153    var el = document.getElementById('kncc-policy-consent-status');
154    if (!el) return;
155
156    var labels = {};
157    try {
158      labels = JSON.parse(el.getAttribute('data-labels') || '{}');
159    } catch (e) {
160      labels = {};
161    }
162
163    var cats = (el.getAttribute('data-categories') || '').split(',').filter(function (cat) {
164      return cat !== '';
165    });
166    if (!cats.length) return;
167
168    var strings = config.policyStatus || {};
169    var consent = getConsent();
170
171    el.textContent = '';
172
173    if (!consent) {
174      var note = document.createElement('p');
175      note.className = 'kncc-policy__consent-status-note';
176      note.textContent = strings.noChoice || 'You have not made a choice yet.';
177      el.appendChild(note);
178      return;
179    }
180
181    var list = document.createElement('ul');
182    list.className = 'kncc-policy__consent-status-list';
183
184    cats.forEach(function (cat) {
185      var item = document.createElement('li');
186      var statusText;
187      if (cat === 'necessary') {
188        statusText = strings.alwaysActive || 'Always active';
189      } else if (isCategoryGranted(consent, cat)) {
190        statusText = strings.allowed || 'Allowed';
191      } else {
192        statusText = strings.denied || 'Denied';
193      }
194      item.textContent = (labels[cat] || cat) + ': ' + statusText;
195      list.appendChild(item);
196    });
197
198    el.appendChild(list);
199  }
200
201  function deleteCookie(name, domain) {
202    var expired = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/';
203    document.cookie = expired;
204    if (domain) {
205      document.cookie = expired + ';domain=' + domain;
206    }
207  }
208
209  function getDeletionDomains() {
210    var hostname = location.hostname;
211    var domains = [''];
212    if (hostname.indexOf('www.') === 0) {
213      domains.push('.' + hostname.substring(4));
214    }
215    domains.push('.' + hostname);
216    var parts = hostname.split('.');
217    if (parts.length > 2) {
218      domains.push('.' + parts.slice(-2).join('.'));
219    }
220    return domains;
221  }
222
223  function matchesClearPattern(name, pattern) {
224    if (!pattern) return false;
225    if (pattern.charAt(pattern.length - 1) === '*') {
226      return name.indexOf(pattern.slice(0, -1)) === 0;
227    }
228    return name === pattern;
229  }
230
231  function clearRejectedCategoryCookies(consent) {
232    var patterns = config.cookiePatterns || {};
233    var domains = getDeletionDomains();
234    var optional = ['analytics', 'marketing', 'preferences'];
235
236    document.cookie.split(';').forEach(function (entry) {
237      var name = entry.split('=')[0].trim();
238      if (!name || name === COOKIE_NAME) return;
239      if (name.indexOf('wp_consent_') === 0) return;
240
241      optional.forEach(function (cat) {
242        if (consent[cat]) return;
243        (patterns[cat] || []).forEach(function (pattern) {
244          if (matchesClearPattern(name, pattern)) {
245            domains.forEach(function (domain) {
246              deleteCookie(name, domain || null);
247            });
248          }
249        });
250      });
251    });
252  }
253
254  function recordStat(choice, consent) {
255    if (!config.ajaxUrl) return;
256    var body = new FormData();
257    body.append('action', 'kncc_record_consent');
258    body.append('choice', choice);
259    body.append('nonce', config.statsNonce || '');
260    body.append('categories', JSON.stringify({
261      necessary: true,
262      analytics: !!consent.analytics,
263      marketing: !!consent.marketing,
264      preferences: !!consent.preferences,
265    }));
266    // sendBeacon reports failure by returning false rather than throwing, so the
267    // return value has to be checked or a dropped beacon never reaches the fallback.
268    var queued = false;
269    try {
270      queued = !!(navigator.sendBeacon && navigator.sendBeacon(config.ajaxUrl, body));
271    } catch (e) {
272      queued = false;
273    }
274
275    if (!queued) {
276      fetch(config.ajaxUrl, { method: 'POST', body: body, keepalive: true }).catch(function () {});
277    }
278  }
279
280  /* ── Script / iframe activation after consent ── */
281
282  function activateBlockedScripts(consent) {
283    // Keep the text/plain source in the DOM and mark it loaded. Removing it made
284    // Reject → Accept impossible without a full page reload (nothing left to run).
285    var blocked = document.querySelectorAll('script[type="text/plain"][data-kncc-category]');
286    blocked.forEach(function (el) {
287      var cat = el.getAttribute('data-kncc-category');
288      if (!consent[cat]) return;
289      if (el.getAttribute('data-kncc-loaded') === '1') return;
290
291      var newScript = document.createElement('script');
292      newScript.type = 'text/javascript';
293      newScript.setAttribute('data-kncc-injected', '1');
294      newScript.setAttribute('data-kncc-category', cat);
295
296      if (el.src) {
297        newScript.src = el.src;
298        if (el.hasAttribute('async')) newScript.async = true;
299        if (el.hasAttribute('defer')) newScript.defer = true;
300      } else {
301        newScript.textContent = el.textContent;
302      }
303
304      el.parentNode.insertBefore(newScript, el.nextSibling);
305      el.setAttribute('data-kncc-loaded', '1');
306    });
307
308    var iframes = document.querySelectorAll('iframe[data-kncc-src][data-kncc-category]');
309    iframes.forEach(function (el) {
310      var cat = el.getAttribute('data-kncc-category');
311      if (!consent[cat]) return;
312
313      el.src = el.getAttribute('data-kncc-src');
314      el.removeAttribute('data-kncc-src');
315      el.classList.remove('kncc-blocked-iframe');
316      var placeholder = el.previousElementSibling;
317      if (placeholder && placeholder.classList.contains('kncc-iframe-placeholder')) {
318        placeholder.remove();
319      }
320    });
321  }
322
323  /**
324   * After Reject / custom deny: allow the same scripts to run again on the next Accept
325   * without reloading the page.
326   */
327  function resetBlockedScriptsForRejected(consent) {
328    var optional = ['analytics', 'marketing', 'preferences'];
329    optional.forEach(function (cat) {
330      if (consent[cat]) return;
331
332      document.querySelectorAll('script[type="text/plain"][data-kncc-category="' + cat + '"]').forEach(function (el) {
333        el.removeAttribute('data-kncc-loaded');
334      });
335
336      document.querySelectorAll('script[data-kncc-injected="1"][data-kncc-category="' + cat + '"]').forEach(function (el) {
337        el.remove();
338      });
339
340      document.querySelectorAll('iframe[data-kncc-category="' + cat + '"]').forEach(function (el) {
341        if (!el.getAttribute('data-kncc-src') && el.getAttribute('src')) {
342          el.setAttribute('data-kncc-src', el.getAttribute('src'));
343          el.removeAttribute('src');
344          el.classList.add('kncc-blocked-iframe');
345        }
346      });
347    });
348  }
349
350  function applyConsent(consent, choice) {
351    setConsent(consent);
352    scheduleWpConsentSync(consent);
353    updateConsentMode(consent);
354    pushDataLayer(consent);
355    activateBlockedScripts(consent);
356    if (choice) recordStat(choice, consent);
357    showFloatingBtn();
358    hideBanner();
359    renderPolicyConsentStatus();
360  }
361
362  function acceptAll() {
363    var consent = { necessary: true, analytics: true, marketing: true, preferences: true, choice: 'accept' };
364    applyConsent(consent, 'accept');
365  }
366
367  function rejectAll() {
368    var consent = { necessary: true, analytics: false, marketing: false, preferences: false, choice: 'reject' };
369    clearRejectedCategoryCookies(consent);
370    resetBlockedScriptsForRejected(consent);
371    applyConsent(consent, 'reject');
372  }
373
374  function saveCustom() {
375    var consent = { necessary: true, choice: 'custom' };
376    var optional = ['analytics', 'marketing', 'preferences'];
377    var checks = document.querySelectorAll('.kncc-category__check');
378    var present = {};
379
380    checks.forEach(function (el) {
381      var cat = el.getAttribute('data-category');
382      if (cat !== 'necessary') {
383        consent[cat] = el.checked;
384        present[cat] = true;
385      }
386    });
387
388    optional.forEach(function (cat) {
389      if (!present[cat]) {
390        consent[cat] = false;
391      }
392    });
393
394    clearRejectedCategoryCookies(consent);
395    resetBlockedScriptsForRejected(consent);
396    applyConsent(consent, 'custom');
397  }
398
399  function closeBanner() {
400    // Reveal the floating button first: hideBanner() restores focus to whatever
401    // opened the banner, which is often this button, and a hidden element cannot
402    // receive focus.
403    showFloatingBtn();
404    hideBanner();
405  }
406
407  var banner = null;
408  var details = null;
409  var floatingBtn = null;
410  var lastFocusedBeforeBanner = null;
411
412  var FOCUSABLE = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
413
414  function isBannerVisible() {
415    return !!banner && banner.style.display !== 'none';
416  }
417
418  function isModalBanner() {
419    return !!banner && banner.getAttribute('data-kncc-modal') === '1';
420  }
421
422  function focusableInBanner() {
423    if (!banner) return [];
424    return Array.prototype.filter.call(banner.querySelectorAll(FOCUSABLE), function (el) {
425      return el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement;
426    });
427  }
428
429  /**
430   * Keep Tab inside the modal banner. Bar and corner banners do not block the page,
431   * so tabbing out of them is the expected behaviour.
432   */
433  function trapTab(e) {
434    if (e.key !== 'Tab' || !isModalBanner() || !isBannerVisible()) return;
435
436    var items = focusableInBanner();
437    if (!items.length) return;
438
439    var first = items[0];
440    var last = items[items.length - 1];
441
442    if (e.shiftKey && document.activeElement === first) {
443      e.preventDefault();
444      last.focus();
445    } else if (!e.shiftKey && document.activeElement === last) {
446      e.preventDefault();
447      first.focus();
448    }
449  }
450
451  function showBanner(trigger) {
452    if (banner) {
453      lastFocusedBeforeBanner = trigger || document.activeElement;
454
455      banner.style.display = '';
456      banner.removeAttribute('aria-hidden');
457
458      var existing = getConsent();
459      if (existing) {
460        var checks = banner.querySelectorAll('.kncc-category__check');
461        checks.forEach(function (el) {
462          var cat = el.getAttribute('data-category');
463          if (cat !== 'necessary' && existing[cat] !== undefined) {
464            el.checked = existing[cat];
465          }
466        });
467      }
468
469      // A modal banner traps focus, so it must receive it. A bar or corner banner
470      // shown automatically on page load must not steal focus from the document;
471      // it only takes focus when the visitor deliberately reopened it.
472      if (trigger || isModalBanner()) {
473        var items = focusableInBanner();
474        if (items.length) {
475          items[0].focus();
476        }
477      }
478    }
479  }
480
481  function hideBanner() {
482    if (banner) {
483      banner.style.display = 'none';
484      banner.setAttribute('aria-hidden', 'true');
485    }
486    if (details) {
487      details.style.display = 'none';
488    }
489
490    // Return focus to whatever opened the banner so keyboard users are not dropped
491    // back at the top of the document.
492    if (lastFocusedBeforeBanner && document.contains(lastFocusedBeforeBanner)) {
493      try { lastFocusedBeforeBanner.focus(); } catch (e) {}
494    }
495    lastFocusedBeforeBanner = null;
496  }
497
498  function showFloatingBtn() {
499    if (floatingBtn) {
500      floatingBtn.style.display = '';
501    }
502  }
503
504  function hideFloatingBtn() {
505    if (floatingBtn) {
506      floatingBtn.style.display = 'none';
507    }
508  }
509
510  function toggleDetails() {
511    if (!details) return;
512    var hidden = details.style.display === 'none';
513    details.style.display = hidden ? '' : 'none';
514  }
515
516  /* ── Inline iframe accept buttons ── */
517
518  function initIframeAcceptButtons() {
519    document.addEventListener('click', function (e) {
520      var btn = e.target.closest('[data-kncc-iframe-accept]');
521      if (!btn) return;
522
523      var cat = btn.getAttribute('data-kncc-iframe-accept');
524      var consent = getConsent() || { necessary: true };
525      consent[cat] = true;
526      setConsent(consent);
527      scheduleWpConsentSync(consent);
528      updateConsentMode(consent);
529      pushDataLayer(consent);
530
531      // Activate all iframes for this category
532      var iframes = document.querySelectorAll('iframe[data-kncc-src][data-kncc-category="' + cat + '"]');
533      iframes.forEach(function (el) {
534        el.src = el.getAttribute('data-kncc-src');
535        el.removeAttribute('data-kncc-src');
536        el.classList.remove('kncc-blocked-iframe');
537        var placeholder = el.previousElementSibling;
538        if (placeholder && placeholder.classList.contains('kncc-iframe-placeholder')) {
539          placeholder.remove();
540        }
541      });
542
543      // Remove remaining placeholders for this category
544      var placeholders = document.querySelectorAll('.kncc-iframe-placeholder[data-kncc-category="' + cat + '"]');
545      placeholders.forEach(function (el) { el.remove(); });
546      renderPolicyConsentStatus();
547    });
548  }
549
550  /* ── Global API for reopening consent from policy page or external links ── */
551
552  window.knccEditConsent = function (trigger) {
553    hideFloatingBtn();
554    showBanner(trigger || document.activeElement);
555  };
556
557  /**
558   * Delegated handler for the [kncc_cookie_settings] shortcode link and the policy
559   * page's "Edit cookie preferences" button, so neither needs an inline onclick.
560   */
561  function initOpenSettingsControls() {
562    document.addEventListener('click', function (e) {
563      var trigger = e.target.closest('.kncc-open-settings, .kncc-edit-consent-btn');
564      if (!trigger) return;
565
566      e.preventDefault();
567      window.knccEditConsent(trigger);
568    });
569  }
570
571  function init() {
572    renderPolicyConsentStatus();
573
574    var consent = getConsent();
575    if (consent) {
576      scheduleWpConsentSync(consent);
577      clearRejectedCategoryCookies(consent);
578      updateConsentMode(consent);
579      activateBlockedScripts(consent);
580    }
581
582    banner = document.getElementById('kncc-banner');
583    floatingBtn = document.getElementById('kncc-floating-btn');
584
585    initOpenSettingsControls();
586
587    if (!banner) {
588      if (consent && floatingBtn) {
589        showFloatingBtn();
590      }
591      return;
592    }
593
594    details = banner.querySelector('.kncc-details');
595
596    banner.addEventListener('click', function (e) {
597      var target = e.target.closest('[data-kncc]');
598      if (!target) return;
599
600      var action = target.getAttribute('data-kncc');
601      switch (action) {
602        case 'accept':
603          acceptAll();
604          break;
605        case 'reject':
606          rejectAll();
607          break;
608        case 'toggle-details':
609          toggleDetails();
610          break;
611        case 'close':
612          closeBanner();
613          break;
614        case 'save':
615          saveCustom();
616          break;
617      }
618    });
619
620    if (floatingBtn) {
621      floatingBtn.addEventListener('click', function () {
622        hideFloatingBtn();
623        showBanner(floatingBtn);
624      });
625    }
626
627    document.addEventListener('keydown', function (e) {
628      if (!isBannerVisible()) return;
629
630      if (e.key === 'Escape') {
631        closeBanner();
632        return;
633      }
634
635      trapTab(e);
636    });
637
638    initIframeAcceptButtons();
639
640    if (consent) {
641      showFloatingBtn();
642    } else {
643      showBanner();
644    }
645  }
646
647  var existingConsent = getConsent();
648  if (existingConsent) {
649    scheduleWpConsentSync(existingConsent);
650  }
651
652  if (document.readyState === 'loading') {
653    document.addEventListener('DOMContentLoaded', init);
654  } else {
655    init();
656  }
657})();

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.