1/** 2 * A/B-test runtime for landing sections (Spec G, PP-1609). 3 * 4 * The template-part renders every variant as a hidden sibling carrying 5 * data-da-exp / data-da-variant (+ optional data-da-goal). This module asks 6 * Statsig (via the theme's same-origin config proxy â no US call pre-consent) 7 * which variant to reveal, removes the losers, and wires up event logging: 8 * 9 * - exposure: logged by the SDK on getExperiment() (= reveal time), mirrored 10 * to the dataLayer as `da_ab_exposure` for GA reporting. 11 * - click: `<exp_key>_click` to Statsig + `da_ab_click` to the dataLayer. 12 * - goal: dataLayer events named in data-da-goal are forwarded to Statsig. 13 * 14 * Consent: SDK starts with logging DISABLED â Events werden nicht verworfen, 15 * sondern vom SDK gepuffert, damit der erste Seitenaufruf eines neuen Besuchers 16 * messbar bleibt, wenn er danach einwilligt. Erteilt das Gate den Consent, wird 17 * Logging eingeschaltet und der Puffer geht raus; bei Ablehnung oder Widerruf 18 * wird er samt allen statsig.*-Schluesseln verworfen. Nichts wird neu geloggt. 19 * 20 * Failsafe: the inline head script (template-functions/ab-testing.php) reveals 21 * the control after 2500 ms for every key this module hasn't decided. 22 * Coordination happens via window.__daAb.{done,failed}. 23 * 24 * Preview: ?da-exp=<key>:<variant>[,<key>:<variant>] reveals directly â 25 * no SDK init, no exposure, no proxy request (when all keys are overridden). 26 */ 27(function () { 28 'use strict'; 29 30 var cfg = window.daAb || {}; 31 var state = (window.__daAb = window.__daAb || { done: {}, failed: {} }); 32 33 var nodes = document.querySelectorAll('[data-da-exp]'); 34 if (!nodes.length) { 35 return; 36 } 37 38 var groups = {}; 39 var goals = {}; // exp key -> dataLayer event name to forward 40 Array.prototype.forEach.call(nodes, function (node) { 41 var key = node.getAttribute('data-da-exp'); 42 (groups[key] = groups[key] || []).push(node); 43 var goal = node.getAttribute('data-da-goal'); 44 if (goal) { 45 goals[key] = goal; 46 } 47 }); 48 49 window.dataLayer = window.dataLayer || []; 50 51 // CookieYes-Kategorie, an der das Statsig-Logging haengt. MUSS hier oben stehen: 52 // hasCookieConsent() laeuft schon vor der SDK-Initialisierung, und eine weiter 53 // unten zugewiesene var waere dann noch undefined â der Regex hiesse 54 // "undefined:yes" und eine erteilte Einwilligung galte faelschlich als fehlend. 55 var CONSENT_CATEGORY = 'analytics'; 56 57 // Consent state (PP-1808). Events logged before consent are buffered by 58 // the SDK in localStorage and only flushed once logging is enabled. 59 var consentApplied = false; 60 // Tri-State: solange niemand entschieden hat, puffert das SDK â die Exposure 61 // dieses Seitenaufrufs geht dann bei einer Einwilligung noch raus. Nach einer 62 // ABLEHNUNG darf nichts mehr entstehen, auch nicht im Puffer. 63 var consentWithdrawn = false; 64 65 // Manueller Hook fuer andere Banner (docs/ab-testing-betrieb.md). Als Stub, damit 66 // ein Aufruf auf den Early-Return-Pfaden (Preview, kein SDK, Ablehnung) nicht 67 // wirft; die echte Implementierung ueberschreibt ihn weiter unten. 68 window.daAbSetConsent = function () { 69 return; 70 }; 71 72 /** 73 * Reveal one variant of an experiment, drop the rest. Falls back to the 74 * control when the assigned variant has no node (e.g. variant removed in 75 * WP while the experiment still runs). 76 */ 77 function reveal(key, variant, silent) { 78 if (state.done[key] || state.failed[key]) { 79 return; 80 } 81 var list = groups[key]; 82 var hasVariant = list.some(function (n) { 83 return n.getAttribute('data-da-variant') === variant; 84 }); 85 if (!hasVariant) { 86 variant = 'control'; 87 } 88 list.forEach(function (n) { 89 if (n.getAttribute('data-da-variant') === variant) { 90 n.removeAttribute('hidden'); 91 bindClick(key, variant, n); 92 } else if (n.parentNode) { 93 n.parentNode.removeChild(n); 94 } 95 }); 96 state.done[key] = variant; 97 if (!silent) { 98 window.dataLayer.push({ event: 'da_ab_exposure', exp: key, variant: variant }); 99 } 100 } 101 102 function revealAllControl(silent) {
103 Object.keys(groups).forEach(function (key) { 104 reveal(key, 'control', silent); 105 }); 106 } 107 108 function bindClick(key, variant, node) { 109 var target = node.closest ? (node.closest('a,button') || node) : node; 110 if (target.__daAbBound) { 111 return; 112 } 113 target.__daAbBound = true; 114 target.addEventListener('click', function () { 115 // Nach einem Widerruf nicht einmal puffern: bei disableLogging verwirft 116 // das SDK das Event nicht, es schreibt es in den localStorage. 117 if (client && !consentWithdrawn) { 118 client.logEvent(key + '_click', variant); 119 // Click often precedes navigation â flush immediately so the 120 // event doesn't die in the queue when the page unloads. 121 try { client.flush(); } catch (e) { /* noop */ } 122 } 123 window.dataLayer.push({ event: 'da_ab_click', exp: key, variant: variant }); 124 }); 125 } 126 127 // ---- Preview override: ?da-exp=key:b,other_key:c -------------------------- 128 var pending = Object.keys(groups); 129 var override = null; 130 try { 131 override = new URLSearchParams(window.location.search).get('da-exp'); 132 } catch (e) { /* very old browsers: no preview support */ } 133 if (override) { 134 override.split(',').forEach(function (pair) { 135 var parts = pair.split(':'); 136 if (parts.length === 2 && groups[parts[0]]) { 137 reveal(parts[0], parts[1], true); 138 pending = pending.filter(function (k) { return k !== parts[0]; }); 139 } 140 }); 141 } 142 if (!pending.length) { 143 return; // fully previewed â no SDK, no requests 144 } 145 // Partial override (multi-experiment page, only some keys in ?da-exp=â¦): 146 // the overridden keys stay silent, but the SDK/proxy still runs for the 147 // remaining ones â QA sessions on such pages DO produce exposures there. 148 149 // ---- Statsig client (bootstrap via same-origin proxy) ---------------------- 150 var Statsig = window.__STATSIG__; 151 if (!cfg.clientKey || !cfg.initUrl || !Statsig || !Statsig.StatsigClient) { 152 revealAllControl(true); // degrade silently: everyone sees the control 153 return; 154 } 155 156 // Explicit rejection on record (the visitor answered the banner and said no to 157 // analytics): do not start the SDK at all. Otherwise it would write its five 158 // localStorage keys again on EVERY pageview and keep assigning a variant, both 159 // against a stated "no" â § 25 Abs. 1 TDDDG attaches to the storage, not to the 160 // transmission. Everything an earlier pageview left behind goes now, and 161 // everyone in this state sees the control (PP-1808). 162 if (consentDeclined()) { 163 dropStatsigStorage(); 164 revealAllControl(true); 165 // Stimmt der Besucher hier doch noch zu, bleibt es bei der Kontrolle â 166 // ohne SDK und mit absichtlich leerem Speicher gibt es auf diesem Pageview 167 // keine Zuweisung mehr. Ab dem naechsten Aufruf laeuft alles normal. Ein 168 // location.reload() waere der groebere Hebel und haette einen Fehlpfad: 169 // feuert das Event, bevor CookieYes das Cookie geschrieben hat, greift 170 // dieser Guard nach dem Reload erneut und der Besucher haengt trotz 171 // Einwilligung in der Kontrolle (PP-1808). 172 return; 173 } 174 175 // The SDK retries events that a previous session could not flush at shutdown: 176 // EventLogger.start() â loadAndRetryShutdownFailedEvents() puts them straight 177 // into the batch queue, bypassing the disableLogging gate. Without consent that 178 // would be a US call before any consent exists â drop them first (verified 179 // against the vendored SDK 3.33.3, PP-1808). 180 // 181 // `hasCookieConsent()` allein reicht als Bedingung NICHT: hier laeuft der Code 182 // synchron, das Gate hat in aller Regel noch nichts gesagt, und dann wuerde der 183 // Puffer bei JEDEM Aufruf sterben â auch beim Eingewilligten. Darin stehen meist 184 // `<key>_click`-Events, deren Seitenaufruf mit dem Wechsel auf weiterbildung. 185 // endete; sie still zu loeschen senkt die Sekundaer-Metrik. 186 // 187 // Deshalb positive Evidenz verlangen statt blosser Abwesenheit einer Ablehnung: 188 // entweder das Gate hat bereits erteilt, oder im Cookie steht eine Entscheidung, 189 // die Analytics erteilt. Bleibt beides aus â Erstbesuch ohne Entscheidung, oder 190 // ein Re-Prompt nach abgelaufenem Consent, bei dem noch ein Puffer aus der 191 // Consent-Zeit liegt â wird geraeumt. 192 if (!hasCookieConsent() && !consentGrantedInCookie()) { 193 dropShutdownFailedEvents(); 194 } 195 196 var client = new Statsig.StatsigClient( 197 cfg.clientKey, 198 {}, 199 { 200 // Environment-Tier (PP-1808 A13). Ein Projekt, ein Client-Key fuer alle 201 // Umgebungen: ohne diese Angabe zaehlen Staging- und Abnahmelaeufe als 202 // Produktionsdaten und verschieben die Scorecards laufender Experimente. 203 // Statsig wertet in Pulse nur `production` aus, der Rest bleibt in den 204 // Diagnostics sichtbar. 205 // 206 // Fehlt die Angabe, wird sie NICHT geraten: ein Default 'development' 207 // wuerde bei einem Ausfall auf Produktion saemtliche echten Daten 208 // stillschweigend aus Pulse nehmen. Ohne Angabe bleibt es beim 209 // SDK-Standard, also beim Verhalten von vor A13. 210 environment: cfg.tier ? { tier: cfg.tier } : undefined, 211 disableLogging: true, // vor dem Consent verlaesst kein Event den Browser 212 // Plain-JSON body (no statsig encoding) so the theme proxy can read 213 // the stableID for its transient cache and forward verbatim. 214 disableStatsigEncoding: true, 215 networkConfig: { initializeUrl: cfg.initUrl } 216 } 217 ); 218 219 client.initializeAsync() 220 .then(function () { 221 // The real stableID of this client â utm-params.js would otherwise have 222 // to guess it from the localStorage key suffix (PP-1808). 223 try { 224 // Nicht nach einem Widerruf, der in dieses Init-Fenster gefallen ist: 225 // der Widerrufs-Zweig hat die Kennung geloescht, und sie hier wieder 226 // zu veroeffentlichen haengt sie bei einer erneuten Zustimmung im 227 // selben Pageview an die Links â fuer Exposures, die es nie gab 228 // (PP-1808, festgenagelt von T1.8b).
229 if (!consentWithdrawn) { 230 state.stableId = client.getContext().stableID; 231 publishLinkParams(); 232 } 233 } catch (e) { /* older SDK build without getContext */ } 234 pending.forEach(function (key) { 235 // Race guard: if the 2500 ms failsafe already revealed the 236 // control (slow proxy), getExperiment() would still log an 237 // exposure for a variant the user never saw and silently 238 // bias the experiment. Skip entirely. 239 // 240 // `consentWithdrawn` aus demselben Grund wie in bindClick() und 241 // forwardGoal(): nach einer Ablehnung darf nicht einmal gepuffert 242 // werden. Dass heute auch ohne diese Zeile nichts entsteht, haengt 243 // allein an `disableStorage` im Widerrufs-Zweig â die Invariante 244 // gehoert in den eigenen Code, nicht in die Annahme ueber das SDK 245 // (PP-1808). 246 if (state.failed[key] || consentWithdrawn) { 247 return; 248 } 249 var variant = 'control'; 250 try { 251 // Loggt die Exposure. Vor dem Consent landet sie im 252 // localStorage-Puffer des SDK und geht erst raus, wenn der 253 // Consent-Hook das Logging einschaltet. Kein 254 // disableExposureLog: das SDK zaehlt dann einen "non exposed 255 // check" und flusht das Aggregat schon vor dem Consent. 256 variant = client.getExperiment(key).get('variant', 'control'); 257 } catch (e) { /* unknown experiment -> control */ } 258 reveal(key, variant); 259 }); 260 }) 261 .catch(function () { 262 revealAllControl(true); 263 }); 264 265 // ---- Goal events: forward configured dataLayer events to Statsig ----------- 266 // Ordering caveat: GTM replaces dataLayer.push when its container loads. 267 // If that happens AFTER this deferred module, a single wrap would be 268 // silently discarded (goal events stop forwarding). Hence: re-wrap after 269 // the page settles (load + 3 s), and dedupe via WeakSet in case an event 270 // object travels through two of our wrappers. 271 if (Object.keys(goals).length) { 272 var forwarded = typeof WeakSet === 'function' ? new WeakSet() : null; 273 var forwardGoal = function (entry) { 274 if (!entry || typeof entry !== 'object' || !entry.event) { 275 return; 276 } 277 if (forwarded) { 278 if (forwarded.has(entry)) { 279 return; 280 } 281 forwarded.add(entry); 282 } 283 if (consentWithdrawn) { 284 return; // s. bindClick(): nach einer Ablehnung nicht einmal puffern 285 } 286 Object.keys(goals).forEach(function (key) { 287 if (goals[key] === entry.event && state.done[key]) { 288 client.logEvent(goals[key], state.done[key], { exp: key }); 289 try { client.flush(); } catch (e) { /* noop */ } 290 } 291 }); 292 }; 293 var wrapPush = function () { 294 var dl = window.dataLayer; 295 if (!dl || typeof dl.push !== 'function' || dl.push.__daAbWrapped) { 296 return; 297 } 298 var orig = dl.push.bind(dl); 299 var wrapper = function () { 300 for (var i = 0; i < arguments.length; i++) { 301 forwardGoal(arguments[i]); 302 } 303 return orig.apply(null, arguments); 304 }; 305 wrapper.__daAbWrapped = true; 306 dl.push = wrapper; 307 }; 308 wrapPush(); 309 window.addEventListener('load', wrapPush); 310 setTimeout(wrapPush, 3000); 311 } 312 313 // ---- Consent hook ----------------------------------------------------------- 314 /** 315 * Removes everything Statsig keeps on the device (`statsig.*` â stableID, 316 * session, cached evaluations, buffers). Used on a rejection or withdrawal, 317 * where none of it may survive, and before skipping the SDK on a later pageview. 318 * 319 * utm-params.js hat eine gleichlautende Kopie, und das bleibt so: die dortige 320 * haengt am Gate-Ereignis und raeumt auf JEDER Seite auf (die Cookie- 321 * Einstellungen stehen im Footer, auch wo kein Test laeuft). Diese hier muss 322 * SYNCHRON vor dem SDK-Start laufen â ein Moment, in dem noch kein Ereignis 323 * gefallen ist: liegt eine Ablehnung vor, darf das SDK die Reste eines 324 * frueheren Besuchs nicht erst einlesen und weiterbenutzen. Die Kopie ins 325 * Gate zu ziehen scheidet aus einem dritten Grund aus: dort liefe sie bei 326 * jedem update() ohne Analytics-Consent, also auch beim Erstbesuch mit 327 * offenem Banner, wo der Puffer erhalten bleiben muss. 328 */ 329 function dropStatsigStorage() { 330 dropStorageKeys(function (key) { 331 return key.indexOf('statsig.') === 0; 332 }); 333 } 334 335 /** 336 * Removes every localStorage key the predicate accepts. 337 * 338 * @param {function(string): boolean} matches Predicate over the key name. 339 */ 340 function dropStorageKeys(matches) { 341 try { 342 var stale = []; 343 for (var i = 0; i < window.localStorage.length; i++) { 344 var key = window.localStorage.key(i); 345 if (key && matches(key)) { 346 stale.push(key); 347 } 348 } 349 stale.forEach(function (key) { 350 window.localStorage.removeItem(key); 351 }); 352 } catch (e) { /* storage disabled â nothing stored either */ } 353 } 354 355 /** 356 * Removes events a previous session could not flush at shutdown. The SDK sends 357 * these on the next start() past the logging gate, so they must not survive a 358 * withdrawal and must not exist before the first consent. 359 */ 360 function dropShutdownFailedEvents() { 361 dropStorageKeys(function (key) { 362 return key.indexOf('statsig.failed_shutdown_events.') === 0; 363 }); 364 } 365 366 /** 367 * Sagt utm-params.js, dass sich die Grundlage der Link-Parameter geaendert hat: 368 * die Kennung steht (nach dem Init) oder der Consent-Stand ist ein anderer. 369 * 370 * Den Consent-Stand erfaehrt `utm-params.js` seit PP-1785 selbst, ueber 371 * `da_consent_change` aus dem Gate. Was es NICHT selbst erfaehrt, ist die 372 * Kennung: die steht erst, wenn das SDK geantwortet hat. Ohne dieses Signal 373 * ginge die Exposure raus, der Link bliebe ohne `da_sid`, der Lead waere nicht 374 * zuordenbar. Stille Unter-Attribution, genau der Fehlermodus, gegen den T1.7k die 375 * Gegenrichtung absichert (PP-1808). 376 * 377 * `appendQueryParametersToLinks()` drueben ist idempotent und setzt oder 378 * entfernt die Parameter je nach Consent-Stand selbst â deshalb ist dasselbe 379 * Signal fuer Erteilung und Widerruf richtig. 380 */ 381 function publishLinkParams() { 382 document.dispatchEvent(new CustomEvent('da_ab_link_params')); 383 } 384 385 window.daAbSetConsent = function (granted) { 386 if (!client) { 387 return; 388 }
389 // Widerruf: Logging aus, Speicher weg, Zuweisungs-Store nicht mehr fuellen. 390 // Ohne das verlieren die Links ihre Parameter (utm-params.js), waehrend das 391 // SDK weiter Klicks sendet â die beiden Haelften widersprechen sich dann. 392 if (!granted) { 393 consentWithdrawn = true; 394 consentApplied = false; 395 try { 396 // disableStorage mit: der laufende Client wuerde Session und 397 // Evaluations sonst nach dem Aufraeumen gleich neu schreiben. Ab dem 398 // naechsten Seitenaufruf haelt der Guard oben das SDK ganz an. 399 client.updateRuntimeOptions({ disableLogging: true, disableStorage: true }); 400 } catch (e) { /* SDK gone â nothing to disable */ } 401 // Auch die veroeffentlichte Kennung zuruecknehmen: utm-params.js liest 402 // sie primaer von dort, und das Consent-Gate soll nicht das Einzige 403 // sein, was sie von den Links fernhaelt (PP-1808). 404 delete state.stableId; 405 dropStatsigStorage(); 406 publishLinkParams(); 407 return; 408 } 409 if (consentApplied) { 410 return; 411 } 412 consentApplied = true; 413 consentWithdrawn = false; 414 try { 415 // Logging einschalten reicht: das SDK laedt seinen localStorage-Puffer 416 // (die Exposures dieses Seitenaufrufs) und flusht ihn. Es gibt kein 417 // Custom-Feld am User mehr, das vorher zu korrigieren waere â dass bei 418 // Statsig nur Eingewilligtes ankommt, garantiert der Ablauf selbst 419 // (PP-1808). 420 client.updateRuntimeOptions({ disableLogging: false, disableStorage: false }); 421 } catch (e) { 422 return; // SDK gone â nothing to enable 423 } 424 // Auf die Einwilligung folgt oft der Klick auf den CTA â die Events nicht 425 // auf den geplanten Flush warten lassen. 426 try { client.flush(); } catch (e) { /* noop */ } 427 publishLinkParams(); 428 }; 429 430 // ---- Consent-Anbindung --------------------------------------------------- 431 // Die Consent-Hoheit liegt seit PP-1785 beim zentralen Gate in 432 // template-parts/tracking-head.php. Es kennt die drei Zustaende (erteilt / 433 // abgelehnt / noch nicht entschieden), prueft `action:yes`, haelt einen 434 // Bestaetigungs-Poll fuer Events, deren Cookie noch nicht geschrieben ist, und 435 // feuert `da_consent_change` bei jedem Zustandswechsel. 436 // 437 // Die lokale CookieYes-Auswertung aus PP-1808 (ckyDecision, eigener Cookie-Poll, 438 // Abonnement von consent_update/banner_load) ist damit **ersatzlos entfallen**: 439 // sie war der Nachbau genau dieser Regeln fuer die Zeit, in der das Gate noch 440 // nicht gemerged war. Alle dort teuer erkauften Faelle deckt das Gate ab â auch 441 // der Erstbesucher, dessen `consent_update` mit voller `rejected`-Liste kommt, 442 // bevor er geklickt hat (PP-1808): das Gate startet dafuer den 443 // Bestaetigungs-Poll, statt eine Ablehnung anzuwenden. 444 // 445 // Andere Banner koennen weiterhin direkt window.daAbSetConsent(true|false) rufen. 446 447 /** 448 * Steht im hinterlegten Cookie eine Entscheidung, die diese Kategorie ERTEILT? 449 * 450 * Synchron beantwortbar, anders als daHasConsent() vor der ersten Antwort des 451 * Gates. "Entschieden und nicht abgelehnt" ist genau eine Erteilung, weil 452 * daConsentDeclined() als "entschieden UND nicht erteilt" definiert ist. 453 * 454 * Nur fuer den Retry-Puffer gedacht (s. Aufrufstelle) â nichts, was Dritt-Code 455 * freischaltet, darf daran haengen. 456 * 457 * @returns {boolean} True nur bei hinterlegter Erteilung; ohne Gate false. 458 */ 459 function consentGrantedInCookie() { 460 return typeof window.daConsentDecided === 'function' 461 && window.daConsentDecided() 462 && !consentDeclined(); 463 } 464 465 /** 466 * Hat der Besucher die Analytics-Kategorie ausdruecklich ABGELEHNT? 467 * 468 * Beantwortet das Gate (`window.daConsentDeclined`), nicht dieses Modul. Hier stand 469 * frueher ein eigener Cookie-Regex â der nahm das ERSTE `cookieyes-consent=` aus 470 * document.cookie, waehrend auf Produktion zwei davon existieren koennen (host-only 471 * neben `.developerakademie.com`, aeltere zuerst gelistet). Das Gate loest den Fall 472 * in parseCookie() auf und beruecksichtigt zusaetzlich staleRaw beim Re-Prompt; zwei 473 * Parser nebeneinander konnten bei denselben Cookies gegensaetzlich entscheiden 474 * (PP-1808). 475 * 476 * Warum ueberhaupt eine zweite Frage neben hasCookieConsent(): das Gate antwortet 477 * asynchron und meldet bis zur ersten CookieYes-Antwort "kein Consent" â das ist von 478 * einer Ablehnung nicht zu unterscheiden. Der Guard unten muss aber SYNCHRON 479 * entscheiden, bevor der Client gebaut wird. 480 * 481 * @returns {boolean} True nur bei einer vorliegenden Ablehnung; ohne Gate false. 482 */ 483 function consentDeclined() { 484 return typeof window.daConsentDeclined === 'function' 485 && window.daConsentDeclined(CONSENT_CATEGORY); 486 } 487 488 /** 489 * Eine Definition von "Consent liegt vor" â die des Gates. 490 * Fehlt der Helper, wird nicht freigegeben (PP-1785: kein Fallback, der laedt). 491 */ 492 function hasCookieConsent() { 493 return typeof window.daHasConsent === 'function' && window.daHasConsent(CONSENT_CATEGORY); 494 } 495 496 /** 497 * Meldet dem Gate, dass wegen `analytics` etwas scharfgeschaltet wurde. Ohne die 498 * Meldung steht das Modul nicht in der live-Liste und ein Widerruf loest keinen 499 * Reload aus (PP-1785). 500 */ 501 function markConsentUsed() { 502 if (typeof window.daMarkConsentUsed === 'function') { 503 window.daMarkConsentUsed(CONSENT_CATEGORY); 504 } 505 } 506 507 /** 508 * @param {boolean} gateHasSpoken True, wenn der Aufruf aus `da_consent_change` kommt. 509 * 510 * Zwei verschiedene Fragen, die frueher eine waren: 511 * - Aus dem Gate-Event heraus HAT das Gate geantwortet. "Kein Consent" ist dann eine
512 * echte Aussage â auch wenn das Cookie noch das Alte sagt, weil CookieYes es erst 513 * nach dem Event schreibt: der ausgesprochene Stand gewinnt. 514 * - Beim synchronen Erstaufruf hat es das nicht. Dort zaehlt nur eine im Cookie 515 * hinterlegte Ablehnung, sonst wuerde jeder Eingewilligte abgeschaltet, bevor das 516 * Gate ueberhaupt dazu kommt. 517 */ 518 function applyConsentState(gateHasSpoken) { 519 if (hasCookieConsent()) { 520 markConsentUsed(); 521 window.daAbSetConsent(true); 522 return; 523 } 524 // Abschalten nur bei einer AUSDRUECKLICHEN Ablehnung im Cookie â nicht schon, 525 // wenn das Gate bloss noch nichts gesagt hat. 526 // 527 // `hasCookieConsent()` oben kommt vom Gate und ist asynchron (3 s Grace, danach 528 // Events oder Cookie-Poll). Beim Sofort-Check unten hat es also noch nicht 529 // geantwortet. Stand hier frueher ein `action:yes`-Check, feuerte bei jedem 530 // wiederkehrenden Besucher mit erteiltem Consent sofort ein daAbSetConsent(false): 531 // consentWithdrawn gesetzt, Speicher geloescht, Kennung verworfen â noch bevor 532 // initializeAsync() durch war. Ergebnis: keine Zuweisung, kein `da_sid`, und das 533 // ausgerechnet im haeufigsten Fall (PP-1808). 534 // 535 // `consentDeclined()` fragt das Gate synchron nach einer ausgesprochenen 536 // Ablehnung und ist damit eindeutig â dieselbe Quelle wie der Guard oben. 537 // `consentApplied` statt blossem `gateHasSpoken`: das Gate feuert auch beim ERSTEN 538 // Stand eines Erstbesuchers ("nichts erteilt", Banner offen). Das als Widerruf zu 539 // lesen wuerde den Puffer dieses Seitenaufrufs verwerfen. Ein Widerruf ist nur 540 // einer, wenn vorher Consent galt. 541 if ((gateHasSpoken && consentApplied) || consentDeclined()) { 542 window.daAbSetConsent(false); 543 } 544 } 545 546 document.addEventListener('da_consent_change', function () { 547 applyConsentState(true); 548 }); 549 550 // Sofort-Check fuer "Stand lag beim Laden schon vor"; alles spaetere per Event. 551 applyConsentState(false); 552})();
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.