1if (location.hash === '#cmpDebug') { 2 console.log('cmp is loaded', window.jppolCmpConfig); 3} 4 5/** 6 * If ATT-modal (Apple Tracking Transparency) has been rejected, we auto decline cookies. If rejected, then this value 7 * is stored in a separate cookie, "cookiesHasBeenAutoDeclined". In the case that the user changes ATT-settings, we 8 * utilize this cookie's existence to check if we want to show the CMP-modal. 9 * 10 * Since this cookie always gets deleted if it exists, and it only gets set if ATT is rejected, we can make sure 11 * the CMP-modal only shows once, if the user switched ATT-settings. 12 * 13 * This will only run if device === app, due to "autoDeclineCookies" only gets send from the native app client. 14 */ 15 16const doesAutoDeclineCookieExist = document.cookie.indexOf('cookiesHasBeenAutoDeclined=true') !== -1 17 18if (doesAutoDeclineCookieExist && window.hasOwnProperty("Cookiebot")) { 19 document.cookie = "cookiesHasBeenAutoDeclined= ; expires = Thu, 01 Jan 1970 00:00:00 GMT" // Delete cookie 20 window.Cookiebot.show() 21} 22 23if (jppolCmpConfig.autoDeclineCookies) { 24 document.cookie = "cookiesHasBeenAutoDeclined=true"; 25 if (window.hasOwnProperty("Cookiebot") && window.Cookiebot.hasOwnProperty("submitCustomConsent")) { 26 const optinPreferences = false 27 const optinStatistics = false 28 const optinMarketing = false 29 window.Cookiebot.submitCustomConsent(optinPreferences, optinStatistics, optinMarketing) 30 } 31} 32 33if (window.jppolPerf && window.jppolPerf.browserMeasureSupported) { 34 window.performance.measure('jppolCMP:start'); 35} 36 37window.jppolCMPVendorCategories = { 38 necessary: 'necessary', 39 preferences: 'preferences', 40 statistics: 'statistics', 41 marketing: 'marketing', 42 translateCategories: function (cat) { 43 switch (cat) { 44 case jppolCMPVendorCategories.necessary: 45 return 'nødvendige'; 46 case jppolCMPVendorCategories.preferences: 47 return 'præferencer'; 48 case jppolCMPVendorCategories.statistics: 49 return 'statistik'; 50 case jppolCMPVendorCategories.marketing: 51 return 'marketing'; 52 default: 53 return cat; 54 } 55 } 56}; 57window.jppolCMP = { 58 relevanceId: null, 59 externalId: null, 60 debug: false || location.hash === '#cmpDebug', 61 html: { 62 backgroundImage: '<div style="height:210px;background-image: url(/static/assets/images/gdpr/exo_cmp_embed_background_desktop.png);background-position: center; background-repeat: no-repeat;background-size: 100% 100%"></div>', 63 backgroundImageRed: '<div style="width:100%;height:147px;
63background: center url(/static/assets/images/gdpr/embed-background-red.png) no-repeat;"></div>', 64 consentButton: '<button onclick="window.Cookiebot.show()" role="button" style="background-color: #439e5c; display: flex; padding: 0.5rem; border-radius: 3px; align-items: center; border:0; color:white" class="js-gdpr-button-inline" style="fill: white">' + 65 'Administrer samtykke' + 66 '</button>', 67 ATTModalConsentButton: '<button role="button" data-auto-decline-cookies="true" style="background-color: #439e5c; display: flex; padding: 0.5rem; border-radius: 0.125rem; align-items: center; fill: white; border:0">' + 68 'Administrér samtykke <svg style="height: 0.75rem; margin-left: 0.5rem;" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 162.1 169.3" xml:space="preserve"> <g> <path d="M159.9,105.1c0.9,0.5,1.5,1.1,1.9,2c0.3,0.9,0.4,1.8,0.2,2.7c-3.9,12.1-10.1,22.8-18.8,32.1c-0.7,0.7-1.5,1.1-2.4,1.4 c-0.9,0.2-1.8,0.1-2.7-0.3l-14.3-8.5c-6.1,5.2-13.1,9.2-20.8,11.9v16.7c0,0.9-0.3,1.8-0.9,2.6s-1.3,1.3-2.2,1.5 c-12.5,2.7-25,2.7-37.6,0c-0.9-0.2-1.6-0.7-2.2-1.5s-0.9-1.6-0.9-2.6v-16.7c-7.7-2.7-14.7-6.7-20.8-11.9L24,143 c-0.9,0.5-1.8,0.6-2.7,0.3s-1.7-0.7-2.4-1.4c-8.6-9.3-14.9-20-18.8-32.1c-0.2-0.9-0.2-1.8,0.2-2.7s1-1.6,1.9-2l14.3-8.5 c-1.4-8-1.4-15.9,0-23.9L2.2,64.2c-0.9-0.5-1.5-1.1-1.9-2c-0.3-0.9-0.4-1.8-0.2-2.7C4,47.3,10.3,36.6,18.9,27.3 c0.7-0.7,1.5-1.1,2.4-1.4c0.9-0.2,1.8-0.1,2.7,0.3l14.3,8.5c6.1-5.2,13.1-9.2,20.8-11.9V6.1c0-0.9,0.3-1.8,0.9-2.6s1.3-1.3,2.2-1.5 c12.5-2.7,25-2.7,37.6,0c0.9,0.2,1.6,0.7,2.2,1.5s0.9,1.6,0.9,2.6v16.7c7.7,2.7,14.7,6.7,20.8,11.9l14.3-8.5 c0.9-0.5,1.8-0.6,2.7-0.3c0.9,0.2,1.7,0.7,2.4,1.4c8.6,9.3,14.9,20,18.8,32.1c0.2,0.9,0.2,1.8-0.2,2.7c-0.3,0.9-1,1.6-1.9,2 l-14.3,8.5c1.4,8,1.4,15.9,0,23.9L159.9,105.1z M100.3,65.4c-5.3-5.3-11.8-8-19.3-8s-13.9,2.7-19.3,8s-8,11.8-8,19.3 c0,7.5,2.7,13.9,8,19.3c5.3,5.3,11.8,8,19.3,8s13.9-2.7,19.3-8c5.3-5.3,8-11.8,8-19.3C108.4,77.1,105.7,70.7,100.3,65.4z"></path> </g> </svg>' + 69 '</button>', 70 }, 71 embedHtmlNoConsent: function (vendorName_message_Obj, embed_body_elements) { 72 const isDesktop = window?.polContext__?.page?.deviceType === "desktop"; 73 const textStyle = isDesktop 74 ? "margin:0 10px 8px;max-width:70%;text-align:center;" 75 : "margin:0 10px 10px;max-width:275px;text-align:center;"; 76 77 const displayATTSettingsInsteadOfCMP_consentButton = 78 window.polContext__?.page?.displayATTSettingsInsteadOfCMP 79 ? jppolCMP.html.ATTModalConsentButton 80 : jppolCMP.html.consentButton; 81 82 const consentButton = window.polContext__?.page?.isApp 83 ? displayATTSettingsInsteadOfCMP_consentButton 84 : jppolCMP.html.consentButton; 85 86 const vendorUrl = 87 vendorName_message_Obj?.url && vendorName_message_Obj.url.length > 0 88 ? (isDesktop 89 ? `<span style='margin-top: 0.5rem; display: flex; flex-direction: column; align-items: center; text-align: center; max-width: 70%; font-size: 14px;'> 90 Du kan også se indholdet ved gå direkte til <br> 91 <a style='text-decoration: underline' href='${vendorName_message_Obj.url}' target='_blank'> 92 ${vendorName_message_Obj.vendorName} (åbner i ny fane) 93 </a> 94 </span>` 95 : `<span style='margin-top: 0.5rem; display: flex; flex-direction: column; align-items: center; text-align: center; font-size: 14px;'> 96 Du kan også se indholdet ved gå direkte til <br> 97 <a style='text-decoration: underline' attribute='external' href='${vendorName_message_Obj.url}' target='_blank'> 98 ${vendorName_message_Obj.vendorName} (åbner i ny fane) 99 </a> 100 </span>`) 101 : "<div></div>"; 102 103 return ` 104 <div> 105 ${embed_body_elements} 106 </div> 107 <div style='position: relative;'> 108 ${jppolCMP.html.backgroundImage} 109 <div style='position: absolute; bottom: 0; top: 0; left: 0; right: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; font-family: flamabook; font-size: 16px; line-height:1.2; color: white;'>
110 <span style='${textStyle}' class='ui-font'> 111 Du kan kun se indhold fra vores<br> samarbejdspartner ${vendorName_message_Obj.vendorName}, hvis du giver samtykke til ${vendorName_message_Obj.message} 112 </span> 113 ${consentButton} 114 ${vendorUrl} 115 </div> 116 </div>`; 117 }, 118 embedHtmlVendorNotFound: function () { 119 let textStyle = "margin:0 10px 15px;max-width:250px;font-size:16px;text-align:center;"; 120 if (window.polContext__) { 121 if (window.polContext__.page) { 122 if (window.polContext__.page.deviceType === "mobile") { 123 textStyle = "margin:0 10px 15px;max-width:220px;font-size:14px;text-align:center;"; 124 } 125 } 126 } 127 return "" + 128 jppolCMP.html.backgroundImageRed + 129 "<div class='u-position-bg u-flex u-flex--col u-flex--align-items-center u-flex--justify-content-center' style='color:#FFFFFF;'>" + 130 "<span style='" + textStyle + "' class='ui-font'>Her er indlejret et element, der kan sætte ukendte cookies, og vi har derfor blokeret det</span>" + 131 "</div>"; 132 }, 133 fullConsent: false, /* used to determine if scripts from Google Tag Manager and other tracking-scripts can run */ 134 /* populated as HTML renders, if any embedded-code is found during paint. 135 object must contain: {id: String, html: String, loaded: Boolean} 136 */ 137 embedVendors: { 138 politiken: { 139 name: "Politiken.dk", 140 sources: ["politiken.dk", "navne.politiken", "misc-politiken-articles.s3"], 141 required: [] 142 }, 143 // used for pixel tracking only 144 doubleclick: { 145 name: "Google Doubleclick", 146 sources: ["pubads.g.doubleclick.net"], 147 required: [] 148 }, 149 // tool-vendors - internal/editorial tools 150 ritzau: { 151 name: "Ritzau", 152 sources: ["ritzau.dk"], 153 required: [] 154 }, 155 datawrapper: { 156 name: "Datawrapper", 157 sources: ["datawrapper"], 158 required: [] 159 }, 160 flourish: { 161 name: "Flourish", 162 sources: ["flourish", "flo.uri.sh"], 163 required: [] 164 }, 165 riddle: { 166 name: "Riddle Technologies AG", 167 sources: ["riddle"], 168 required: [] 169 }, 170 kennedk: { 171 name: "kenne.dk", 172 sources: ["vapor-farm-c1.com"], 173 required: [] 174 }, 175 rockContent: { 176 name: "Rock Content Technologies", 177 sources: ["rockcontent.com", "embed.scribblelive.com"], 178 required: [window.jppolCMPVendorCategories.statistics] 179 }, 180 dpaSportsLive: { 181 name: "dpa SportsLive", 182 sources: ["core.dpa-infocom.net", "dpa-sportslive.com", "dpa-infocom.s3", "d2c0cdjj8gf5hk.cloudfront.net"], 183 required: [] 184 }, 185 // others... 186 facebook: { 187 name: "Facebook", 188 sources: ["facebook"], 189 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 190 }, 191 instagram: { 192 name: "Instagram", 193 sources: ["instagram"], 194 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 195 }, 196 threads: { 197 name: "Threads", 198 sources: ["threads"], 199 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 200 }, 201 telegram: { 202 name: "Telegram", 203 sources: ["telegram"], 204 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 205 }, 206 linkedin: { 207 name: "LinkedIn", 208 sources: ["linkedin"], 209 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 210 }, 211 youtube: { 212 name: "YouTube", 213 sources: ["youtube", "youtu.be"],
214 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 215 }, 216 leadfamly: { 217 name: "LeadFamily ApS", 218 sources: ["leadfamly"], 219 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 220 }, 221 etrack: { 222 name: "etrack1", 223 sources: ["webservice/etrack", "etrack1.com"], 224 required: [] 225 }, 226 codespace: { 227 name: "Codespace", 228 sources: ["codespace.dk"], 229 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 230 }, 231 playbuzz: { 232 name: "Playbuzz UK Limited", 233 sources: ["playbuzz.com"], 234 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 235 }, 236 tableau: { 237 name: "Tableau Software, Inc.", 238 sources: ["tableau.com"], 239 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 240 }, 241 contenttools: { 242 name: "Content Tools", 243 sources: ["contenttools"], 244 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 245 }, 246 vimeo: { 247 name: "Vimeo", 248 sources: ["vimeo.com"], 249 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 250 }, 251 tiktok: { 252 name: "TikTok", 253 sources: ["tiktok.com"], 254 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 255 }, 256 spotify: { 257 name: "Spotify AB", 258 sources: ["spotify.com"], 259 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 260 }, 261 soundcloud: { 262 name: "soundcloud", 263 sources: ["soundcloud"], 264 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 265 }, 266 tidal: { 267 name: "Tidal", 268 sources: ["tidal"], 269 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 270 }, 271 appleMusic: { 272 name: "Apple Music", 273 sources: ["music.apple.com"], 274 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 275 }, 276 reddit: { 277 name: "Reddit, Inc.", 278 sources: ["reddit.com"], 279 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 280 }, 281 tumblr: { 282 name: "Tumblr, Inc.", 283 sources: ["tumblr.com"], 284 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 285 }, 286 truthSocial: { 287 name: "Truth Social", 288 sources: ["truthsocial.com"], 289 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 290 }, 291 giphy: { 292 name: "Giphy, Inc.", 293 sources: ["giphy.com"], 294 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 295 }, 296 imgur: { 297 name: "Imgur, Inc. og Imgur, LLC.", 298 sources: ["imgur.com"], 299 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 300 }, 301 googleMaps: { 302 name: "Google Maps", 303 sources: ["google.com/maps"], 304 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 305 }, 306 dropbox: { 307 name: "Dropbox, Inc.", 308 sources: ["dropbox"], 309 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 310 }, 311 airbnb: { 312 name: "Airbnb", 313 sources: ["airbnb"],
314 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 315 }, 316 flickr: { 317 name: "Flickr", 318 sources: ["flickr.com", "flic.kr"], 319 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 320 }, 321 myspace: { 322 name: "Myspace", 323 sources: ["myspace"], 324 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 325 }, 326 Bandcamp: { 327 name: "bandcamp", 328 sources: ["bandcamp"], 329 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 330 }, 331 audible: { 332 name: "Audible", 333 sources: ["audible"], 334 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 335 }, 336 ebay: { 337 name: "eBay, Inc.", 338 sources: ["ebay.com", "ebay.co.uk", "ebay.de"], 339 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 340 }, 341 yelp: { 342 name: "Yelp Inc.", 343 sources: ["yelp.com"], 344 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 345 }, 346 trustpilot: { 347 name: "Trustpilot", 348 sources: ["trustpilot"], 349 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 350 }, 351 podimo: { 352 name: "Podimo", 353 sources: ["podimo"], 354 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 355 }, 356 podbean: { 357 name: "Podbean", 358 sources: ["podbean"], 359 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 360 }, 361 figma: { 362 name: "Figma", 363 sources: ["figma"], 364 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 365 }, 366 momondo: { 367 name: "Momondo", 368 sources: ["momondo"], 369 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 370 }, 371 bbc: { 372 name: "BBC", 373 sources: ["bbc.com"], 374 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 375 }, 376 aws: { 377 name: "Amazon.com, Inc.", 378 sources: ["amazonaws.com"], 379 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 380 }, 381 audienceproject: { 382 name: "AudienceProject ApS", 383 sources: ["userreport.com"], 384 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 385 }, 386 adform: { 387 name: "Adform", 388 sources: ["adform.net"], 389 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 390 }, 391 googleAdManager: { 392 name: "Google Inc.", 393 sources: ["admanager.google.com"], 394 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 395 }, 396 hubspot: { 397 name: "HubSpot, Inc.", 398 sources: ["hsforms.com"], 399 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 400 }, 401 lexi: { 402 name: "Lexi ApS", 403 sources: ["lexi"], 404 required: [] 405 }, 406 // We keep Twitter in the CMP to handle legacy embeds 407 twitter: { 408 name: "X", // Twitters new name is 'X', and we are required to show this in the UI. 409 sources: ["twitter.com"], 410 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 411 }, 412 x: { 413 name: "X", 414 sources: ["x.com"],
415 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 416 }, 417 bluesky: { 418 name: "Bluesky", 419 sources: ["bsky.app"], 420 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 421 } 422 /* 423 disabled 424 heyrobot: { 425 name: "HeyRobot", 426 sources: ["heyrobot"], 427 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing] 428 }, 429 */ 430 }, 431 vendors: { 432 norkon: { 433 name: "Norkon Live blog", 434 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing], 435 loaded: false, 436 load: async function () { 437 const valid = jppolCMP.checkConsentsByCategory(this.required); 438 439 if (valid) { 440 const liveBlogConfigs = document.querySelectorAll('[data-live-blog-config]') 441 442 // Hack to make it work specifically for Norkon, due to last script needs prior scripts to download, parse and execute. 443 const loadScriptSequentially = async (scriptData) => { 444 await new Promise((resolve, reject) => { 445 let script = document.createElement('script') 446 447 script.onload = () => resolve(scriptData.src) 448 script.onerror = () => reject(new Error(`â Failed to load script: ${scriptData.src || 'inline'}`)) 449 450 if (scriptData.src) { 451 script.async = false 452 script.src = scriptData.src 453 } else { 454 script.innerHTML = scriptData.content 455 script.type = 'text/javascript' 456 resolve() // Inline scripts execute immediately 457 } 458 459 document.head.appendChild(script); 460 }) 461 } 462 463 for (const liveBlogConfig of liveBlogConfigs) { 464 const liveBlogScriptLinks = JSON.parse(liveBlogConfig.dataset.liveBlogConfig).scripts 465 466 // Load scripts sequentially 467 for (const script of liveBlogScriptLinks) { 468 await loadScriptSequentially(script) 469 } 470 } 471 472 this.loaded = true; 473 } else { 474 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + "' will not load") : null; 475 } 476 } 477 }, 478 adnami: { 479 name: "Adnami A/S", 480 required: [], 481 loaded: false, 482 load: function () { 483 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + '" requires consent from:', this.required) : null; 484 485 const valid = jppolCMP.checkConsentsByCategory(this.required); 486 487 if (valid) { 488 jppolCMP.createScript("https://macro.adnami.io/macro/spec/adsm.macro.749a71e5-eaa5-48b7-82a4-e9a28a048987.js", true, function () { 489 window.adsmtag = adsmtag || {}; 490 adsmtag.cmd = adsmtag.cmd || []; 491 }); 492 493 jppolCMP.createScript("https://macro.adnami.io/macro/gen/adnm.sonar-autotag.js", true, () => {}, null, [{ 494 name: "data-adnm-gid", 495 value: "teczefybndby3w" 496 }, { 497 name: "data-adnm-sample", 498 value: "70" 499 }]); 500 501 this.loaded = true; 502 } else { 503 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + "' will not load") : null; 504 } 505 } 506 }, 507 brandmetrics: { 508 name: "Brand Metrics Sweden AB", 509 required: [], 510 loaded: false, 511 load: function () { 512 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + '" requires consent from:', this.required) : null; 513 514 const valid = jppolCMP.checkConsentsByCategory(this.required); 515 516 if (valid) { 517 jppolCMP.createScript("https://cdn.brandmetrics.com/tag/17fdf9b7-dfbf-4c2a-8a7d-868b496fb116/politike
517n.dk.js", true); 518 this.loaded = true; 519 } else { 520 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + "' will not load") : null; 521 } 522 } 523 }, 524 chartbeat: { 525 name: "Chartbeat", 526 required: [window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing], 527 loaded: false, 528 load: function () { 529 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + '" requires consent from:', this.required) : null; 530 531 const valid = jppolCMP.checkConsentsByCategory(this.required); 532 533 if (valid) { 534 jppolCMP.onReady(function () { 535 jppolCMP.createScript("/static/js/chartbeat.js?d=" + jppolCmpConfig.cachekey, true, function () { 536 window._sf_startpt = (new Date()).getTime(); 537 }); 538 }); 539 this.loaded = true; 540 } else { 541 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + "' will not load") : null; 542 } 543 } 544 }, 545 echobox: { 546 name: "EchoBox", 547 required: [], 548 loaded: false, 549 load: function () { 550 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + '" requires consent from:', this.required) : null; 551 552 const valid = jppolCMP.checkConsentsByCategory(this.required); 553 554 if (valid) { 555 jppolCMP.createScript("https://applets.ebxcdn.com/ebx.js", true, () => {}, "ebx"); 556 this.loaded = true; 557 } else { 558 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + "' will not load") : null; 559 } 560 } 561 }, 562 SupWiz: { 563 name: "SupWiz", 564 required: [], 565 loaded: false, 566 load: function () { 567 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + '" requires consent from:', this.required) : null; 568 569 const valid = jppolCMP.checkConsentsByCategory(this.required); 570 571 if (valid && window.hasOwnProperty("polContext__") 572 && window.polContext__.hasOwnProperty("page") 573 && window.polContext__.page.hasOwnProperty("sectionId")) { 574 575 const sectionId = window.polContext__.page.sectionId; 576 577 jppolCMP.onReady(() => { 578 if (sectionId === 4528) { //Politiken kundecenter 579 jppolCMP.createScript("https://supchat.politiken.supwizapp.com/widget/main.js", true); 580 jppolCMP.createScript("https://supchat.politiken.supwizapp.com/static/widget-configs/1.js", true, function () { 581 try { 582 let ssoIdForSupChat = ''; 583 let userNameForSupChat = 'anonym'; 584 if (window.polContext__.hasOwnProperty("user") 585 && window.polContext__.user.hasOwnProperty("isAuthenticated") 586 && window.polContext__.user.hasOwnProperty("name") 587 && window.polContext__.user.hasOwnProperty("ssoId") 588 && window.polContext__.user.isAuthenticated) { 589 ssoIdForSupChat = window.polContext__.user.ssoId; 590 userNameForSupChat = window.polContext__.user.name; 591 } 592 if (window.config?.visitorInfo?.fields?.push) { 593 window.config.visitorInfo.fields.push({ 594 name: userNameForSupChat, 595 type: 'hidden', 596 defaultValue: ssoIdForSupChat 597 }); 598 } else { 599 console.warn('SupWiz widget: config.visitorInfo.fields ikke tilgængelig'); 600 } 601 602 if (typeof configureWidget === 'function' && typeof config !== 'undefined') { 603 window.widgetObj = configureWidget(config); 604 } else { 605 console.warn('SupWiz widget: configureWidget eller config ikke tilgængelig'); 606 } 607 } catch (e) {
608 console.error('Fejl ved initialisering af SupWiz (config 1):', e); 609 } 610 }); 611 } else if (sectionId === 4877) { //Borneavisen kundecenter 612 jppolCMP.createScript("https://supchat.politiken.supwizapp.com/widget/main.js", true); 613 jppolCMP.createScript("https://supchat.politiken.supwizapp.com/static/widget-configs/3.js", true, function () { 614 try { 615 if (typeof configureWidget === 'function' && typeof config !== 'undefined') { 616 window.widgetObj = configureWidget(config); 617 } else { 618 console.warn('SupWiz widget: configureWidget eller config ikke tilgængelig'); 619 } 620 } catch (e) { 621 console.error('Fejl ved initialisering af SupWiz widget:', e); 622 } 623 }); 624 } 625 }); 626 this.loaded = true; 627 } 628 } 629 }, 630 snowplow: { 631 name: "snowplow", 632 required: [], 633 loaded: false, 634 /** 635 * Snowplow consent settings will by default be set to noConsent. 636 * later depending on consent level, they can be changed and then applied. 637 */ 638 snowplowConsentSettings: { 639 anonymousTracking: { 640 withSessionTracking: true, 641 withServerAnonymisation: true, 642 }, 643 respectDoNotTrack: false, 644 sessionCookieTimeout: 0, 645 stateStorageStrategy: "none" 646 }, 647 determineUserAuthorization: function (context) { 648 if (!context.user.ipAccessFor) { 649 return "ip" 650 } else if (!context.page.sharedKey) { 651 return "gift" 652 } else { 653 return "login" 654 } 655 }, 656 load: function () { 657 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + '" requires consent from:', this.required) : null; 658 659 const snowplowConfig = window.jppolCmpConfig.snowplowConfig 660 if (!snowplowConfig.useSnowplow && !window.polSnowplowEventHandler) { 661 jppolCMP.debug ? console.log("jppolCMP - Snowplow was disabled from the backend or the polSnowplowHandler was not found...") : null; 662 return; 663 } 664 665 function mapUserTypeToSnowplowTypes(userType) { 666 switch (userType) { 667 case "subscriber": 668 case "registered": 669 return userType; 670 671 default: 672 return "anon"; 673 } 674 } 675 676 const customTrackerName = "snowplow-pol-escenic-tracker"; // this name is for internal use only 677 const collectorUrl = snowplowConfig.collectorUrl ?? null; 678 const appId = snowplowConfig.appId ?? null; 679 const snowplowPlatformType = "web" 680 681 const snowplowConsentState = this.getSnowplowConsentState() 682 if (snowplowConsentState === "hasConsent") { 683 // We set the snowplow config to be full consent compatible 684 this.updateSnowplowSettingsFullConsent(); 685 } 686 687 if (!collectorUrl && !appId) { 688 jppolCMP.debug ? console.error("SnowplowConfig did not include a collectorUrl") : null; 689 return; 690 } 691 692 let schemaData = {}; 693 if (window.polContext__) { 694 const context = window.polContext__ 695 696 let accessType = context.user.access.brands[0] && context.user.access.brands[0].brandAccessAgreements[0] && context.user.access.brands[0].brandAccessAgreements[0].accessAgreementName || ""; 697 let accessId = context.user.access.brands[0] && context.user.access.brands[0].brandAccessAgreements[0] && context.user.access.brands[0].brandAccessAgreements[0].accessAgreementId || ""; 698 let accountNumber = context.user.access.brands[0] && context.user.access.brands[0].brandAccessAgreements[0] && context.user.access.brands[0].brandAccessAgreements[0].accountNumber || ""; 699 let accessAuthorizationType = this.determineUserAuthorization(context) || ""; 700 701 schemaData.access = { 702 access_type: !accessType ? "ANON" : accessType, 703 access_id: accessId, 704 account_number: accountNumber, 705 access_authorization_type: accessAuthorizationType 706 } 707 schemaData.user = { 708 user_id: context.user.ssoId ?? "", 709 anon_id: "ANON", // We don't have any useful ANON id, so we send ANON, which is inline with data team 710 user_authenticated: window.polSnowplowEventHandler.convertBoolToYesOrNo(context.user.isAuthenticated) ?? "",
711 user_type: mapUserTypeToSnowplowTypes(context.user.userType) 712 } 713 schemaData.content = { 714 content_id: context.page?.articleId?.toString() ?? "null", // If there are no articleId, like on a authors page, then we return "null" 715 content_label: !context.page.articleTitle ? "null" : context.page.articleTitle, // If there are no articleTitle, like on a authors page, then we return "null" 716 content_section_id: context.page.sectionId.toString() ?? "", 717 content_type: context.page.pageType ?? "null" 718 } 719 schemaData.page = { 720 page_restricted: window.polSnowplowEventHandler.convertBoolToYesOrNo(context.page.isPremium) ?? "", 721 page_section_id: context.page.sectionId.toString() ?? "", 722 page_type: context.page.pageType ?? "" 723 } 724 } 725 726 // new tracker object 727 const snowplowTrackerConfig = { 728 useSnowplow: snowplowConfig.useSnowplow, 729 snowplowTrackers: [{ 730 appId: appId, 731 collectorUrl: collectorUrl, 732 customTrackerName: customTrackerName, 733 platformType: snowplowPlatformType, 734 trackerSettings: { 735 sessionCookieTimeout: this.snowplowConsentSettings.sessionCookieTimeout, 736 respectDoNotTrack: this.snowplowConsentSettings.respectDoNotTrack, 737 anonymousTracking: { 738 withSessionTracking: this.snowplowConsentSettings.anonymousTracking.withSessionTracking, 739 withServerAnonymisation: this.snowplowConsentSettings.anonymousTracking.withServerAnonymisation, 740 }, 741 stateStorageStrategy: this.snowplowConsentSettings.stateStorageStrategy, 742 }, 743 trackerEvents: { 744 usePageview: true, 745 useLinkClickTracking: true, 746 useButtonClickTracking: true, 747 useFormTracking: true, 748 }, 749 }], 750 schemaTypes: schemaData 751 } 752 753 // Start snowplow tracking 754 window.polSnowplowEventHandler.initSnowplow(snowplowTrackerConfig, window.Cookiebot); 755 756 this.loaded = true; 757 }, 758 updateSnowplowSettingsFullConsent: function () { 759 if (window.jppolCMP.fullConsent || (window.Cookiebot?.consent?.marketing && window.Cookiebot?.consent?.statistics)) { 760 this.snowplowConsentSettings.sessionCookieTimeout = 1800; 761 this.snowplowConsentSettings.anonymousTracking.withServerAnonymisation = false; 762 this.snowplowConsentSettings.stateStorageStrategy = "cookieAndLocalStorage"; 763 764 jppolCMP.debug ? console.log("SNOWPLOW: We updated snowplow to have a full consent session", this.snowplowConsentSettings) : null; 765 } 766 }, 767 setSnowplowConsentState: function (value) { 768 window.localStorage.setItem("snowplow_state", value.toString()); 769 }, 770 getSnowplowConsentState: function () { 771 return window.localStorage.getItem("snowplow_state") 772 } 773 }, 774 GTM: { 775 name: "Google Tag Manager", 776 required: [], 777 loaded: false, 778 load: function () { 779 if (window.jppolCmpConfig.tagManagerId.trim().length === 0){ 780 return; 781 } 782 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + '" requires consent from:', this.required) : null; 783 784 const valid = jppolCMP.checkConsentsByCategory(this.required); 785 786 if (valid) { 787 (function (w, d, s, l, i) { 788 w[l] = w[l] || []; 789 w[l].push({'gtm.start': new Date().getTime(), event: 'gtm.js'}); 790 const f = d.getElementsByTagName(s)[0], j = d.createElement(s), 791 dl = l !== 'dataLayer' ? '&l=' + l : ''; 792 j.async = true; 793 //j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl + window.jppolCmpConfig.tagManagerId; 794 j.src = '/proxy/pza?id=' + i + dl + window.jppolCmpConfig.tagManagerId; 795 f.parentNode.insertBefore(j, f); 796 797 // Set control pixel, as extra data gathering 798 jppolCMP.setCMPTrackingPixel(); 799 800 })(window, document, 'script', 'dataLayer', 'GTM-TPRSNTR'); 801 this.loaded = true; 802 } else { 803 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + "' will not load") : null; 804 } 805 } 806 }, 807 adModule: { 808 name: "Ad Module", 809 required: [], 810 loaded: false, 811 bannerData: [], 812 load: async function () { 813 if (!window.jppolCmpConfig.useAdModule) { 814 console.log("window.jppolCmpConfig.useAdModule was false") 815 return; 816 } 817 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + '" NOW STARTING UP') : null; 818 819 // We initialise the ad server scripts, which is needed before we initialise the rest of the ad module 820 try { 821 await window.polAdModuleHandler.setAndInitAdServerScripts() 822 } catch (e) { 823 console.error("Failed to set Ad Script...", e) 824 return; 825 } 826 827 const context = window?.polContext__ 828 if (!context) { 829 jppolCMP.debug ? console.error("polContext was not found...") : null; 830 return; 831 } 832 833 if (this.bannerData.length === 0) { 834 jppolCMP.debug ? console.error("bannerdata was 0, so we return ads...") : null; 835 return; 836 } 837 838 const bannerDataArray = this.bannerData.map((data) => { 839 return { 840 placementCode: data?.targetId, 841 adSize: data?.sizes 842 } 843 }); 844 845 const adModuleConfig = { 846 fullConsent: jppolCMP.fullConsent, 847 deviceType: context.page.deviceType, 848 currentSection: context.page.section, 849 targetingValues: { 850 bloomreachSegment: context?.user.bloomreachSegments, 851 activeFeatures: context?.user.access.activeFeatures, 852 articleId: context?.page.articleId, 853 isPremium: context?.page.isPremium, 854 publisherName: context?.page.subbrand, 855 currentSection: context?.page?.section, 856 subscriptionFeatures: context?.user?.access.subscriptionFeatures, 857 suggestedTags: context?.page.suggestedTags, 858 articleTags: context?.page.articleTags, 859 articleTitle: context?.page.articleTitle, 860 articleSubTitle: context?.page.articleSubtitle, 861 articleAuthors: context?.page.articleAuthors, 862 usertype: context?.user.userType, 863 newsletters: context?.user.newsletters 864 }, 865 bannerData: bannerDataArray, 866 } 867 window.polAdModuleHandler.initAdModule(adModuleConfig) 868 869 this.loaded = true; 870 } 871 }, 872 bloomreach: { 873 name: "Bloomreach",
874 required: [jppolCMPVendorCategories.preferences, window.jppolCMPVendorCategories.statistics, window.jppolCMPVendorCategories.marketing], 875 loaded: false, 876 load: function () { 877 if (!window.jppolCmpConfig.useBloomreach) { 878 return; 879 } 880 jppolCMP.debug = true; 881 jppolCMP.debug ? console.log('jppolCMP - "' + this.name + '" requires consent from:', this.required) : null; 882 const valid = jppolCMP.checkConsentsByCategory(this.required); 883 if (valid) { 884 jppolCMP.onReady(function () { 885 function startBloomreach() { 886 const Bloomreach = window.require("module/common/bloomreach"); 887 const initResult = Bloomreach.init(); 888 889 const customer = {}; 890 891 // temporary_id must always be defined for us to track all events. 892 // This check is here to provide backwards compatibility, for 893 // when clients for some reason get a new version of this, 894 // but a cached version of module/common/bloomreach, whose 895 // init function returns undefined. 896 if (initResult && initResult.temporaryId) { 897 customer.temporary_id = initResult.temporaryId; 898 } 899 900 // When not logged in, users get a temporary ssoId. Using this 901 // as composite_key (our bloomreach "hard id"), prevents 902 // bloomreach from merging customers based on their "soft ids" 903 // (__exponea_etc__ cookie). For this reason, we rely solely on 904 // the cookie id, until the user logs in. See 905 // https://documentation.bloomreach.com/engagement/docs/merging 906 // for details 907 if (window.polContext__.user.isAuthenticated) { 908 customer.composite_key = window.polContext__.page.cdpBrandName + "_" + window.polContext__.user.ssoId; 909 } 910 911 !function(e,n,t,i,o,r){function a(e){if("number"!=typeof e)return e;var n=new Date;return new Date(n.getTime()+1e3*e)}var c=4e3,s="xnpe_async_hide";function p(e){return e.reduce((function(e,n){return e[n]=function(){e._.push([n.toString(),arguments])},e}),{_:[]})}function m(e,n,t){var i=t.createElement(n);i.src=e;var o=t.getElementsByTagName(n)[0];return o.parentNode.insertBefore(i,o),i}function u(e){return"[object Date]"===Object.prototype.toString.call(e)}r.target=r.target||"https://api.exponea.com",r.file_path=r.file_path||r.target+"/js/exponea.min.js",o[n]=p(["anonymize","initialize","identify","getSegments","update","track","trackLink","trackEnhancedEcommerce","getHtml","showHtml","showBanner","showWebLayer","ping","getAbTest","loadDependency","getRecommendation","reloadWebLayers","_preInitialize"]),o[n].notifications=p(["isAvailable","isSubscribed","subscribe","unsubscribe"]),o[n]["snippetVersion"]="v2.5.0",function(e,n,t){e[n]["_"+t]={},e[n]["_"+t].nowFn=Date.now,e[n]["_"+t].snippetStartTime=e[n]["_"+t].nowFn()}(o,n,"performance"),function(e,n,t,i,o,r){e[o]={sdk:e[i],sdkObjectName:i,skipExperiments:!!t.new_experiments,sign:t.token+"/"+(r.exec(n.cookie)||["","new"])[1],path:t.target}}(o,e,r,n,i,RegExp("__exponea_etc__"+"=([\\w-]+)")),function(e,n,t){m(e.file_path,n,t)}(r,t,e),function(e,n,t,i,o,r,p){if(e.new_experiments){!0===e.new_experiments&&(e.new_experiments={});var l,f=e.new_experiments.hide_class||s,_=e.new_experiments.timeout||c,d=encodeURIComponent(r.location.href.split("#")[0]);e.cookies&&e.cookies.expires&&("number"==typeof e.cookies.expires||u(e.cookies.expires)?l=a(e.cookies.expires):e.cookies.expires.tracking&&("number"==typeof e.cookies.expires.tracking||u(e.cookies.expires.tracking))&&(l=a(e.cookies.expires.tracking))),l&&l<new Date&&(l=void 0);var x=e.target+"/webxp/"+n+"/"+r[t].sign+"/modifications.min.js?http-referer="+d+"&timeout="+_+"ms"+(l?"&cookie-expires="+Math.floor(l.getTime()/1e3):"");"sync"===e.new_experiments.mode&&r.localStorage.getItem("__exponea__sync_modifications__")?function(e,n,t,i,o){t[o][n]="<"+n+' src="'+e+'"></'+n+">",i.writeln(t[o][n]),i.writeln("<"+n+">!"+o+".init && document.writeln("+o+"."+n+'.replace("/'+n+'/", "/'+n+'-async/").replace("><", " async><"))</'+n+">")}(x,n,r,p,t):function(e,n,t,i,o,r,a,c){r.documentElement.classList.add(e);var s=m(t,i,r);function p(){o[c].init||m(t.replace("/"+i+"/","/"+i+"-async/"),i,r)}function u(){r.documentElement.classList.remove(e)}s.onload=p,s.onerror=p,o.setTimeout(u,n),o[a]._revealPage=u}(f,_,x,n,r,p,o,t)}}(r,t,i,0,n,o,e),function(e,n,t){var i;(null===(i=t.experimental)||void 0===i?void 0:i.non_personalized_weblayers)&&e[n]._preInitialize(t),e[n].start=function(i){i&&Object.keys(i).forEac
911h((function(e){return t[e]=i[e]})),e[n].initialize(t)}}(o,n,r)}(document,"exponea","script","webxpClient",window,{ 912 target: jppolCmpConfig.cdpProjectBaseUrl, 913 token: jppolCmpConfig.cdpProjectToken, 914 customer, 915 cookies: { 916 cross_subdomain: false, 917 }, 918 track: { 919 google_analytics: false, 920 }, 921 }); 922 window.exponea.start(); 923 }; 924 925 function startBloomreachIfReady() { 926 if (!(window.require && window.require.defined("module/common/bloomreach"))) { 927 return false; 928 } 929 930 if (jppolCMP.debug) { 931 console.log("jppolCMP - initializing bloomreach module"); 932 } 933 934 startBloomreach(); 935 return true; 936 }; 937 938 if(!startBloomreachIfReady()) { 939 if (jppolCMP.debug) { 940 console.log("jppolCMP - can't initialize bloomreach module yet. Will retry"); 941 } 942 const intervalId = setInterval(function () { 943 if (startBloomreachIfReady()) { 944 clearInterval(intervalId); 945 } else { 946 if (jppolCMP.debug) { 947 console.log("jppolCMP - can't initialize bloomreach module yet. Will retry"); 948 } 949 } 950 }, 200); 951 } 952 }); 953 this.loaded = true; 954 } 955 } 956 } 957 }, 958 init: function () { 959 if (window.hasOwnProperty("jppolPerf") && window.jppolPerf.browserMeasureSupported) { 960 window.performance.measure('jppolCmp:init:start'); 961 } 962 963 // set relevaceId, also used in "cookieless environments" 964 let id = ""; 965 if (window.hasOwnProperty("polContext__") 966 && window.polContext__.hasOwnProperty("user") 967 && window.polContext__.user.hasOwnProperty("relevanceId") 968 && window.polContext__.user.relevanceId !== "") { 969 id = window.polContext__.user.relevanceId; 970 } else { 971 // check localStorage for ID 972 id = window.localStorage.getItem("_ls_value"); 973 } 974 window.jppolCMP.relevanceId = id; 975 976 // load consented vendor-scripts 977 for (const p in jppolCMP.vendors) { 978 if (jppolCMP.vendors.hasOwnProperty(p)) { 979 const v = jppolCMP.vendors[p]; 980 if (v.hasOwnProperty('load') && v.hasOwnProperty('loaded')) { 981 if (v.loaded) { 982 jppolCMP.debug ? console.log('Vendor: ', p, 'has already been loaded') : null; 983 } else { 984 v.load(); 985 } 986 } 987 } 988 } 989 // load embeds 990 jppolCMP.onReady(function () { 991 for (let i = 0; i < window.jppolCmpConfig.embeds.length; i++) { 992 const embed = window.jppolCmpConfig.embeds[i]; 993 if (embed.loaded) { 994 jppolCMP.debug ? console.log('Embed with "id":', embed.id, 'has already been loaded') : null; 995 } else { 996 jppolCMP.createEmbed(embed.id, embed.html, embed.bodyElements); 997 } 998 } 999 1000 }); 1001 }, 1002 onReady: function (callback) { 1003 if (typeof (callback) !== "function") { 1004 jppolCMP.debug ? console.log("jppolCMP.onReady - 'callback' in not a function:", typeof (callback)) : null; 1005 return false 1006 } 1007 if (/complete|interactive/.test(document.readyState)) { 1008 callback(); 1009 } else { 1010 document.addEventListener('DOMContentLoaded', function () { 1011 callback(); 1012 }); 1013 } 1014 }, 1015 checkConsentsByCategory: function (array) { 1016 if (Array.isArray(array) && array.length === 0) { 1017 return true; 1018 } 1019 if (window.hasOwnProperty("Cookiebot") && window.Cookiebot.hasOwnProperty("consent")) { 1020 for (let i = 0; i < array.length; i++) { 1021 const category = array[i]; 1022 if (window.Cookiebot.consent.hasOwnProperty(category) && window.Cookiebot.consent[category]) { 1023 // continue 1024 } else { 1025 jppolCMP.debug ? console.log("jppolCMP - consent: '" + category + "' not found or false") : null; 1026 return false;
1027 } 1028 } 1029 return true; 1030 } 1031 return false; 1032 }, 1033 checkFullConsent: function () { 1034 if (window.hasOwnProperty("Cookiebot") && window.Cookiebot.hasOwnProperty("consent") 1035 && window.Cookiebot.consent.hasOwnProperty('necessary') && window.Cookiebot.consent.necessary 1036 && window.Cookiebot.consent.hasOwnProperty('preferences') && window.Cookiebot.consent.preferences 1037 && window.Cookiebot.consent.hasOwnProperty('statistics') && window.Cookiebot.consent.statistics 1038 && window.Cookiebot.consent.hasOwnProperty('marketing') && window.Cookiebot.consent.marketing) { 1039 return true 1040 } 1041 return false; 1042 }, 1043 createScript: function (src, async, callback = () => {}, id, attributes = []) { 1044 if (src !== null && typeof (src) !== "undefined") { 1045 const s = document.createElement("script"); 1046 s.src = src; 1047 (async !== null && typeof (async) !== "undefined") ? s.async = (async === true) : false; 1048 if (typeof (callback) === "function") { 1049 s.onload = function () { 1050 callback(); 1051 jppolCMP.debug ? console.log("jppolCMP - callback done:", src) : null; 1052 } 1053 } 1054 if (id !== null && typeof (id) !== "undefined") { 1055 s.id = id; 1056 } 1057 if (attributes != null && Array.isArray(attributes)) { 1058 for (let i = 0; i < attributes.length; i++) { 1059 const attribute = attributes[i]; 1060 if ((attribute != null) && (typeof attribute === "object")) { 1061 s.setAttribute(attribute.name, attribute.value); 1062 } 1063 } 1064 } 1065 const t = document.getElementsByTagName("script")[0]; 1066 t.parentNode.insertBefore(s, t); 1067 jppolCMP.debug ? console.log("jppolCMP - created script:", src) : null; 1068 } else { 1069 console.log("jppolCMP - 'src' is invalid"); 1070 } 1071 }, 1072 1073 /** 1074 * 1075 * Due to legislative initiatives, we need to offer the read a direct link to the embedded content 1076 * - if users have not accepted cookies. This logic extracts the direct url to the content, from the provided embed code, from CUE. 1077 * 1078 * Due to the quite diverse opportunities for content embedding, each embed provider handles embedding differently, hence the logic below. 1079 * The logic works by finding the correct vendor, which we need to handle a url from. Then we get the url from a querySelector 1080 * @see embedQuery 1081 * From this query, we can extract the correct url to make sure we pass this url to the component, that informs people about their missing consent. 1082 * 1083 * @return string - if nothing is found, this defaults to an empty string. 1084 * 1085 * @param doc - the document of the page, but parsed to an object. 1086 * @param vendor - an object, that contains information about the embed vendor. The value contains the vendor name (string) and the permission level required (array with strings). 1087 * 1088 */ 1089 getUrlFromEmbedCode: function (doc, vendor) { 1090 let contentUrl = ""; 1091 const embedQuery = doc.querySelector("iframe, blockquote, div, script") 1092 1093 if (embedQuery) { 1094 const vendorName = vendor.name; 1095 1096 // As embeds are different, we iterate through each case, to make sure we get the correct URL value. 1097 if (vendorName.toLowerCase().includes("reddit") || vendorName.indexOf("reddit") !== -1) { 1098 /** 1099 * Reddit offers multiple links, for the users, comments etc. 1100 * We take the firstChild, and it's href, as this links to the directly embedded post. 1101 */ 1102 contentUrl = embedQuery.firstChild.href; 1103 } 1104 else if (vendorName.toLowerCase().includes("twitter") || vendorName.indexOf("twitter") !== -1 ) { 1105 // This case is a fallback case, in case we have old twitter embeds.
1106 contentUrl = embedQuery.childNodes[2].href ? embedQuery.childNodes[2].href : embedQuery.querySelector("a").href ? embedQuery.querySelector("a").href : ""; 1107 } 1108 else if (vendorName.toLowerCase().includes("x") || vendorName.indexOf("x") !== -1 ) { 1109 contentUrl = embedQuery.childNodes[2].href ? embedQuery.childNodes[2].href : embedQuery.querySelector("a").href ? embedQuery.querySelector("a").href : ""; 1110 } 1111 else if (vendorName.toLowerCase().includes("bluesky") || vendorName.indexOf("bluesky") !== -1 ) { 1112 const getUrlValue = embedQuery.querySelector("a").href ? embedQuery.querySelector("a").href : embedQuery.children[3].href; 1113 if (getUrlValue) { 1114 contentUrl = getUrlValue; 1115 } 1116 } 1117 else if (vendorName.toLowerCase().includes("youtube") || vendorName.indexOf("youtube") !== -1) { 1118 contentUrl = embedQuery.src; 1119 } 1120 else if (vendorName.toLowerCase().includes("facebook") || vendorName.indexOf("facebook") !== -1) { 1121 contentUrl = embedQuery.src; 1122 } 1123 else if (vendorName.toLowerCase().includes("instagram") || vendorName.indexOf("instagram") !== -1) { 1124 contentUrl = embedQuery.dataset.instgrmPermalink; 1125 } 1126 else if (vendorName.toLowerCase().includes("tiktok") || vendorName.indexOf("tiktok") !== -1) { 1127 contentUrl = embedQuery.cite; 1128 } 1129 else if (vendorName.toLowerCase().includes("threads") || vendorName.indexOf("threads") !== -1) { 1130 contentUrl = embedQuery.dataset.textPostPermalink; 1131 } 1132 else if (vendorName.toLowerCase().includes("tumblr") || vendorName.indexOf("tumblr") !== -1) { 1133 contentUrl = embedQuery.dataset.href; 1134 } 1135 else if (vendorName.toLowerCase().includes("spotify") || vendorName.indexOf("spotify") !== -1) { 1136 contentUrl = embedQuery.src; 1137 } 1138 else if (vendorName.toLowerCase().includes("vimeo") || vendorName.indexOf("vimeo") !== -1) { 1139 contentUrl = embedQuery.src; 1140 } 1141 else if (vendorName.toLowerCase().includes("imgur") || vendorName.indexOf("imgur") !== -1) { 1142 contentUrl = embedQuery.firstChild.href; 1143 } 1144 else if (vendorName.toLowerCase().includes("giphy") || vendorName.indexOf("giphy") !== -1) { 1145 contentUrl = embedQuery.src; 1146 } 1147 else if (vendorName.toLowerCase().includes("google maps") || vendorName.indexOf("google maps") !== -1) { 1148 const googleMapsQuery = embedQuery.src; 1149 1150 /** 1151 * This regex query looks for the values in between the string values '2d' and '3d'. 1152 * The values in between a seperate by a question mark, which is the regex query also handles, by only taking values in between. 1153 * The collected values are our longitude and latitude values, which we need to create the correct link for Google Maps. 1154 * 1155 * The collected 2d value: this is related to the longitude coordinates 1156 * The collected 3d value: this is related to the latitude coordinates 1157 * @type {RegExp} 1158 */ 1159 const regex = /!2d([-+]?\d*\.?\d+)!3d([-+]?\d*\.?\d+)/; 1160 const googleMapsUrlRegexMatch = googleMapsQuery.match(regex); 1161 1162 if (googleMapsUrlRegexMatch) { 1163 const longitude = googleMapsUrlRegexMatch[1]; 1164 const latitude = googleMapsUrlRegexMatch[2]; 1165 const mapZoomLevel = 14; // the range goes from 0 to the 21 - 0 being the whole world and 21 extremely zoomed. 14 is picked as a sweet spot in between. 1166 1167 /** 1168 * This string is composed with inspiration from Googles api: https://developers.google.com/maps/documentation/urls/get-started#search-action 1169 * And is the final string, that we send the frontend, for users to view. 1170 * @type {string} 1171 */ 1172 const googleMapsUrlWithCoordinates = `https://www.google.com/maps/@?api=1&map_action=map¢er=${latitude}%2C${longitude}&zoom=${mapZoomLevel}`; 1173 contentUrl = googleMapsUrlWithCoordinates 1174 } 1175 } 1176 else if (vendorName.toLowerCase().includes("figma") || vendorName.indexOf("figma") !== -1) {
1177 contentUrl = embedQuery.src; 1178 } 1179 else if (vendorName.toLowerCase().includes("truth social") || vendorName.indexOf("truth social") !== -1) { 1180 contentUrl = embedQuery.src; 1181 } 1182 else if (vendorName.toLowerCase().includes("telegram") || vendorName.indexOf("telegram") !== -1) { 1183 const baseTelegramUrl = "https://t.me/"; 1184 const pathToContent = embedQuery.dataset.telegramPost; 1185 1186 if (pathToContent) { 1187 contentUrl = baseTelegramUrl + pathToContent 1188 } 1189 } 1190 else { 1191 jppolCMP.debug ? console.log("jppolCMP - could not locate a url from the provided embedded query: ", embedQuery) : null; 1192 } 1193 } 1194 1195 return contentUrl; 1196 }, 1197 1198 createEmbed: function (id, script, description) { 1199 let i; 1200 let valid; 1201 let vendor; 1202 const div = document.getElementById(id); 1203 if (div === null) { 1204 jppolCMP.debug ? console.log("jppolCMP - createEmbed() could not find element with 'id':", id) : null; 1205 return false; 1206 } 1207 1208 const doc = new window.DOMParser().parseFromString(script, "text/html"); 1209 1210 let vendorSrc = doc.head.innerHTML.match(/[\s.]src="(.*?)"/i); 1211 if (typeof (vendorSrc) === "undefined" || vendorSrc === null) { 1212 vendorSrc = doc.body.innerHTML.match(/[\s.]src="(.*?)"/i); 1213 } 1214 1215 if (vendorSrc && vendorSrc.length && vendorSrc.length > 1) { 1216 const str = vendorSrc[1].toLowerCase(); 1217 1218 // http or external src 1219 if (/https?:\/\//.test(str) || /\/\//.test(str)) { 1220 1221 vendor = jppolCMP.findEmbedVendorsFromString(str); 1222 1223 // don't show unknown vendors 1224 if (!vendor.hasOwnProperty('name')) { 1225 jppolCMP.debug ? console.log("jppolCMP - Could not find vendor using String 'str':", str) : null; 1226 div.innerHTML = jppolCMP.embedHtmlVendorNotFound(); 1227 return false; 1228 } 1229 1230 valid = true; 1231 if (vendor.hasOwnProperty("required")) { 1232 valid = jppolCMP.checkConsentsByCategory(vendor.required); 1233 } 1234 1235 // don't show vendors without consent 1236 if (!valid) { 1237 jppolCMP.debug ? console.log("jppolCMP - no consent for embed with 'id'", id, "found vendor:", vendor) : null; 1238 1239 let vendorName = "Indlejret indhold"; 1240 if (vendor.hasOwnProperty("name") && typeof (vendor.name) === "string") { 1241 vendorName = vendor.name; 1242 } 1243 1244 // no need to tell the user to accept already accepted categories 1245 let requiredString = ""; 1246 const noConsentCategories = []; 1247 1248 for (i = 0; i < vendor.required.length; i++) {
1249 const category = vendor.required[i]; 1250 if (!Cookiebot.consent[category]) { 1251 noConsentCategories.push(category); 1252 } 1253 } 1254 for (i = 0; i < noConsentCategories.length; i++) { 1255 // over 2 items and last loop? 1256 const cat = window.jppolCMPVendorCategories.translateCategories(noConsentCategories[i]); 1257 if (noConsentCategories.length >= 2 && (i + 1) === noConsentCategories.length) { 1258 requiredString += " og " + cat; 1259 } else { 1260 requiredString += cat; 1261 } 1262 } 1263 1264 // getting direct url to embed, from embed code 1265 const urlFromEmbed = this.getUrlFromEmbedCode(doc, vendor) 1266 1267 // Building vendor object for modal, and assigning url value to object 1268 const vendorObject = this.buildRequiredMessage(vendor) 1269 vendorObject["url"] = urlFromEmbed; 1270 1271 div.innerHTML = jppolCMP.embedHtmlNoConsent(vendorObject, description); 1272 return false; 1273 } 1274 } 1275 // eTrack specific logic 1276 else if (str.indexOf("/webservice/etrack")) { 1277 vendor = jppolCMP.findEmbedVendorsFromString(str); 1278 1279 valid = true; 1280 if (vendor.hasOwnProperty("required") && Array.isArray(vendor.required)) { 1281 valid = jppolCMP.checkConsentsByCategory(vendor.required); 1282 } 1283 1284 if (valid) { 1285 div.innerHTML = script; 1286 } else { 1287 div.innerHTML = jppolCMP.embedHtmlNoConsent(jppolCMP.buildRequiredMessage(vendor), description); 1288 } 1289 jppolCMP.debug ? console.log('jppolCMP - Embedded eTrack iframe') : null; 1290 return true; 1291 } 1292 } else { 1293 jppolCMP.debug ? console.log('jppolCMP - no "vendorSrc" found for "id":', id) : null; 1294 } 1295 1296 // find scripts inside embedded html 1297 const scripts = []; 1298 let x = 0; 1299 1300 const headScripts = doc.head.getElementsByTagName('script'); 1301 for (x = 0; x < headScripts.length; x++) { 1302 const s = headScripts[x]; 1303 scripts.push({ 1304 "src": s.src, 1305 "async": s.async, 1306 "code": (s.text || s.textContent || s.innerHTML || ""), 1307 "attributes" : s.attributes 1308 }); 1309 1310 // TODO: Is this correct? Scripts should be removed, but can we do this while looping? 1311 s.remove(); 1312 } 1313 1314 const bodyScripts = doc.body.getElementsByTagName('script'); 1315 for (x = 0; x < bodyScripts.length; x++) { 1316 const p = bodyScripts[x]; 1317 scripts.push({ 1318 "src": p.src, 1319 "async": p.async, 1320 "code": (p.text || p.textContent || p.innerHTML || ""), 1321 "attributes" : p.attributes 1322 }); 1323 1324 // TODO: Is this correct? Scripts should be removed, but can we do this while looping? 1325 p.remove(); 1326 } 1327 1328 // append html after scripts has been removed 1329 div.innerHTML = (description || "") + doc.body.innerHTML; 1330 jppolCMP.debug ? console.log("jppolCMP - creating scripts for id: ", id, scripts) : null; 1331 1332 // create and run scripts from "doc" in "this.document" 1333 for (i = 0; i < scripts.length; i++) { 1334 const r = scripts[i]; 1335 if (r.code !== "" || r.attributes.length) { 1336 var newScript = document.createElement("script"); 1337 1338 /* Since some embeds like Telegrams only contains a script with no content, but rather fetches it's content, 1339 we need to link the attributes and src and embed it inline, since said script creates its iframe next to the 1340 original scripts placement in the DOM */ 1341 if (r.src) { 1342 newScript.src = r.src 1343 } 1344 1345 const attributes = r.attributes; 1346 1347 for (let i = 0; i < attributes.length; i++) { 1348 const attribute = attributes[i]; 1349 if (attribute.name.startsWith('data-')) { 1350 newScript.setAttribute(attribute.name, attribute.value); 1351 } 1352 } 1353 1354 newScript.appendChild(document.createTextNode(r.code)); 1355 div.parentElement.insertBefore(newScript, div); 1356 jppolCMP.debug ? console.log("jppolCMP - created local-script for id: ", id) : null; 1357 } else { 1358 jppolCMP.createScript(r.src, r.async, function () { 1359 jppolCMP.debug ? console.log('jppolCMP - embedded script with "src":', r.src) : null; 1360 }); 1361 } 1362 } 1363 1364 // set embed as loaded, to prevent double loading the same embed 1365 for (i = 0; i < window.jppolCmpConfig.embeds.length; i++) { 1366 const embed = window.jppolCmpConfig.embeds[i]; 1367 if (embed.id === id) { 1368 embed.loaded = true; 1369 break; 1370 } 1371 } 1372 }, 1373 buildRequiredMessage: function (vendor) { 1374 let requiredString = ""; 1375 let i; 1376 const noConsentCategories = []; 1377 1378 let vendorName = "Indlejret indhold"; 1379 if (vendor.hasOwnProperty("name") && typeof (vendor.name) === "string") { 1380 vendorName = vendor.name; 1381 } 1382 1383 for (i = 0; i < vendor.required.length; i++) {
1384 const category = vendor.required[i]; 1385 if (!Cookiebot.consent[category]) { 1386 noConsentCategories.push(category); 1387 } 1388 } 1389 for (i = 0; i < noConsentCategories.length; i++) { 1390 // over 2 items and last loop? 1391 const cat = window.jppolCMPVendorCategories.translateCategories(noConsentCategories[i]); 1392 if (noConsentCategories.length >= 2 && (i + 1) === noConsentCategories.length) { 1393 requiredString += " og " + cat; 1394 } else { 1395 requiredString += cat; 1396 } 1397 } 1398 return {vendorName: vendorName, message: requiredString}; 1399 }, 1400 findEmbedVendorsFromString: function (string) { 1401 for (const p in jppolCMP.embedVendors) { 1402 if (jppolCMP.embedVendors.hasOwnProperty(p)) { 1403 1404 const vendor = jppolCMP.embedVendors[p]; 1405 if (vendor.hasOwnProperty("sources")) { 1406 1407 const src = vendor.sources; 1408 if (Array.isArray(src)) { 1409 for (let i = 0; i < src.length; i++) { 1410 if (string.indexOf(src[i]) !== -1) { 1411 return vendor; 1412 } 1413 } 1414 } 1415 } 1416 } 1417 } 1418 return {}; 1419 }, 1420 removeHtmlCollection: function (htmlCollection) { 1421 if (htmlCollection.length > 0) { 1422 for (let i = (htmlCollection.length - 1); i > -1; i--) { 1423 htmlCollection[i].remove(); 1424 // TODO: create polyfill: https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove 1425 } 1426 } 1427 }, 1428 getExternalUid: function (response) { 1429 if (response.hasOwnProperty("uid")) { 1430 window.jppolCMP.externalId = response.uid; 1431 } else { 1432 console.log("invalid response:", response); 1433 } 1434 }, 1435 setCMPTrackingPixel: function () { 1436 const context = window?.polContext__; 1437 // Set control pixel, as extra data gathering 1438 const endpoint = "https://w2w52itmtj.execute-api.eu-west-1.amazonaws.com/test" 1439 1440 const data = { 1441 tb: "event", 1442 t: "pizza", 1443 cd2: context.user.ssoId ? context.user.ssoId.toString() : "", 1444 cd3: context.user.type ? context.user.type.toString() : "", 1445 cd5: context.page.articleId ? context.page.articleId.toString() : "", 1446 cd7: context.page.sectionName ? context.page.sectionName.toString() : "", 1447 cd8: context.page.sectionHierarchy ? context.page.sectionHierarchy.toString() : "", 1448 cd9: context.page.contentType ? context.page.contentType.toString() : "", 1449 cd67: context.user.isPremium ? context.user.isPremium.toString() : "", 1450 cd68: context.page.currentURL ? context.page.currentURL.toString() : "", 1451 cd70: "old", // we set old, to establish the pixel is sat by our old platform 1452 cd71: "false", // we set false, to establish the pixel is not a bf_cache pixel 1453 cd82: window.sessionStorage.getItem("hashed_session") ? window.sessionStorage.getItem("hashed_session").toString() : "", 1454 cd83: navigator.userAgent ? navigator.userAgent.toString() : "", 1455 cd87: context.page.deviceType ? context.page.deviceType.toString().toLowerCase() : "", 1456 region: context.geo.stateName ? context.geo.stateName.toString() : "", 1457 country: context.geo.countryName ? context.geo.countryName.toString() : "", 1458 } 1459 1460 const params = new URLSearchParams(data) 1461 const url = new URL(endpoint) 1462 url.search = params.toString() 1463 1464 const pixel = document.createElement("img") 1465 pixel.height = 0 1466 pixel.width = 0 1467 pixel.style = "display: none"; 1468 pixel.src = url.toString() 1469 }, 1470 updateConsentModeV2: function () { 1471 function gtag() { 1472 window.dataLayer.push(arguments); 1473 } 1474 1475 // If a user gives full consent, we set all values to 'granted' 1476 if (window.jppolCMP.checkFullConsent()) { 1477 gtag('consent', 'update', { 1478 'ad_user_data': 'granted', 1479 'ad_personalization': 'granted', 1480 'ad_storage': 'granted',
1481 'analytics_storage': 'granted', 1482 'functionality_storage': 'granted', 1483 'personalization_storage': 'granted', 1484 'security_storage': 'granted', 1485 }) 1486 } else { 1487 // If we don't have full cookie permission, we need to be selective about what we grant permission to. 1488 const consentModeAttributes = { 1489 'ad_personalization': 'denied', 1490 'ad_storage': 'denied', 1491 'ad_user_data': 'denied', 1492 'analytics_storage': 'denied', 1493 'functionality_storage': 'denied', 1494 'personalization_storage': 'denied', 1495 'security_storage': 'granted', 1496 } 1497 1498 if (window.Cookiebot?.consent?.marketing === true) { 1499 // Update attributes, to enable marketing related consent mode permission 1500 consentModeAttributes.ad_user_data = 'granted'; 1501 consentModeAttributes.ad_personalization = 'granted'; 1502 consentModeAttributes.ad_storage = 'granted'; 1503 } 1504 if (window.Cookiebot?.consent?.statistics === true) { 1505 // Update attributes, to enable statistics related consent mode permission 1506 consentModeAttributes.analytics_storage = 'granted'; 1507 } 1508 if (window.Cookiebot?.consent?.preferences === true) { 1509 // Update attributes, to enable preferences related consent mode permission 1510 consentModeAttributes.functionality_storage = 'granted' 1511 consentModeAttributes.personalization_storage = 'granted' 1512 } 1513 1514 // After setting consent mode v2 permission, we can now set them in the dataLayer with the gtag function, 1515 gtag('consent', 'update', consentModeAttributes) 1516 } 1517 } 1518}; 1519 1520window.addEventListener('CookiebotOnConsentReady', function () { 1521 1522 // prevent cookiebot cookie from expiring after 7 days in Safari 1523 let c = Cookiebot, 1524 d = document, 1525 cookies = d.cookie.split(";"), 1526 consentCookie = "", 1527 consentDate = c.consentUTC, 1528 consentExpiry = new Date(consentDate.setMonth(consentDate.getMonth() + 12)), 1529 now = new Date(); 1530 1531 for (let i = 0; i < cookies.length; i++) { 1532 if (/^CookieConsent/g.test(cookies[i])) { 1533 consentCookie = cookies[i]; 1534 } 1535 if (consentCookie !== "" && now < consentExpiry) { 1536 d.cookie = consentCookie + ";expires=" + consentExpiry.toUTCString() + ";path=/"; 1537 } 1538 } 1539 1540 // since we have to load fonts before we know what the user has consented to, 1541 // we have to place a cookie, to let the font-script know, if it's ok to store fonts in localstorage 1542 // for subsequent visits 1543 if ('localStorage' in window) { 1544 if (window.hasOwnProperty("Cookiebot") 1545 && window.Cookiebot.hasOwnProperty("consent") 1546 && window.Cookiebot.consent.hasOwnProperty("preferences") 1547 && window.Cookiebot.consent.preferences) { 1548 localStorage.setItem('isPreferencesAllowed', true); 1549 } else { 1550 localStorage.removeItem('isPreferencesAllowed') 1551 } 1552 } 1553 1554 // scripts in Google Tag Manager will only fire if fullConsent = true 1555 jppolCMP.fullConsent = jppolCMP.checkFullConsent(); 1556 1557 // We update Consent Mode v2. 1558 jppolCMP.updateConsentModeV2(); 1559 1560 // TODO: quick-fix, find better solution which only runs in our App 1561 if (window.ReactNativeWebView) { 1562 if (jppolCMP.fullConsent) { 1563 window.ReactNativeWebView.postMessage(JSON.stringify({ 1564 href: window.location.href, 1565 type: 'FULL_CONSENT', 1566 page: window.polContext__.page, 1567 })); 1568 } else { 1569 window.ReactNativeWebView.postMessage(JSON.stringify({ 1570 href: window.location.href, 1571 type: 'LIMITED_CONSENT', 1572 page: window.polContext__.page, 1573 })); 1574 } 1575 } 1576 1577 jppolCMP.init(); 1578 1579 if (window.hasOwnProperty("jppolPerf") && window.jppolPerf.browserMeasureSupported) { 1580 window.performance.measure('jppolCmp:init:end'); 1581 } 1582}, false); 1583 1584/** 1585 * This event happens when cookiebot registeres that the consent state is 1586 * decline - meaning there has been given no consent. 1587 * This runs on every pageload. 1588 * 1589 * Please notice - this events run after the "CookiebotOnConsentReady" event 1590 */ 1591window.addEventListener("CookiebotOnDecline", () => { 1592 const snowplowConsent = window.jppolCMP.vendors.snowplow.getSnowplowConsentState(); 1593 if (snowplowConsent === "hasConsent") { 1594 window.jppolCMP.vendors.snowplow.setSnowplowConsentState("noConsent") // This will ensure we don't clear the session on every load. 1595 1596 // When no consent is registered first, we need to clear the session and start a new one. 1597 window.polSnowplowEventHandler.clearExistingSnowplowSessionAndStartNew(); 1598 window.jppolCMP.debug ? console.log("SNOWPLOW: We cleared the current snowplow session and started a new one.", window.jppolCMP.vendors.snowplow.snowplowConsentSettings) : null; 1599 } else if (!snowplowConsent) { 1600 // We set consent state to noConsent, since this else case would be the initial load, without previous consent registered for Snowplow 1601 window.jppolCMP.vendors.snowplow.setSnowplowConsentState("noConsent") 1602 } 1603}); 1604 1605/** 1606 * This event happens when cookiebot registers that the consent state is 1607 * accepted - meaning there has been given consent. 1608 * Note: This runs even when there is given consent to one parameter - like marketing. 1609 * So it can not be used to trust there is full consent for all c
1609ookies. 1610 * This runs on every pageload. 1611 * 1612 * Please notice - this events run after the "CookiebotOnConsentReady" event 1613 */ 1614window.addEventListener("CookiebotOnAccept", () => { 1615 const snowplowConsent = window.jppolCMP.vendors.snowplow.getSnowplowConsentState(); 1616 if (snowplowConsent === "noConsent") { 1617 window.jppolCMP.vendors.snowplow.setSnowplowConsentState("hasConsent") // flip the consent, to ensure it's not repeating. 1618 window.jppolCMP.debug ? console.log("SNOWPLOW: Consent was given - the tracking can now track with full consent") : null 1619 } else if (!snowplowConsent) { 1620 // We set consent state to hasConsent, since this else case would be the initial load, without previous consent registered for Snowplow 1621 window.jppolCMP.vendors.snowplow.setSnowplowConsentState("hasConsent"); 1622 } 1623});
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.