1/* 2 * Copyright notice 3 * 4 * (c) sgalinski Internet Services (https://www.sgalinski.de) 5 * 6 * Commercial license 7 * You can buy a license key on the following site: 8 * https://www.sgalinski.de/en/typo3-produkte-webentwicklung/sgalinski-cookie-optin/ 9 */ 10 11const SgCookieOptin = { 12 13 COOKIE_NAME: 'cookie_optin', 14 LAST_PREFERENCES_COOKIE_NAME: 'cookie_optin_last_preferences', 15 LAST_PREFERENCES_LOCAL_STORAGE_NAME: 'SgCookieOptin.lastPreferences', 16 COOKIE_GROUP_EXTERNAL_CONTENT: 'iframes', 17 COOKIE_GROUP_ESSENTIAL: 'essential', 18 19 /** 20 * The CSS selector to match possible external content 21 * @type {string} 22 */ 23 EXTERNAL_CONTENT_ELEMENT_SELECTOR: 'iframe, object, video, audio, [data-external-content-protection], .frame-external-content-protection', 24 /** 25 * The CSS selector to match whitelisted external content 26 * @type {string} 27 */ 28 EXTERNAL_CONTENT_ALLOWED_ELEMENT_SELECTOR: '[data-external-content-no-protection], [data-iframe-allow-always], .frame-external-content-no-protection', 29 GOOGLE_PAGESPEED_USER_AGENT_REGEX: new RegExp( 30 [ 31 // Moto G4 32 'Mozilla/5.0 \\(Linux; Android 11; moto g power \\(2022\\)\\) AppleWebKit/537\\.36 \\(KHTML, like Gecko\\) Chrome/119\\.0\\.0\\.0 Mobile Safari/537\\.36', 33 // Desktop 34 'Mozilla/5.0 \\(Macintosh; Intel Mac OS X 10_15_7\\) AppleWebKit/537\\.36 \\(KHTML, like Gecko\\) Chrome/119\\.0\\.0\\.0 Safari/537\\.36', 35 // Known bots 36 '(?:Googlebot|Bingbot|Baiduspider|YandexBot|DuckDuckBot|Slackbot|Facebookbot|Twitterbot|LinkedInbot|Pinterest|WhatsApp|TelegramBot|Slurp|Sogou|Exabot|ia_archiver|msnbot|YandexMobileBot|AdsBot-Google-Mobile|Googlebot-Image|Googlebot-News|Googlebot-Video|Mediapartners-Google|AdsBot-Google|FeedFetcher-Google|Google-Read-Aloud|Google-Adwords-Instant|Yahoo! Slurp China|Yahoo! Slurp|Y!J-BRW|Y!J-SRD|Y!J-MBS|Y!J-MR2|Y!J-PSCS|Y!J-BSC|Y!J-GECC|Y!J-DSC|Y!J-DBS|Y!J-SRB|Y!J-RTS|Y!J-BEP|Y!J-BRP|Y!J-BSP|Y!J-SRS|Y!J-SRE|Y!J-SRT|Y!J-BRV|Y!J-BSV|Y!J-SBC|Y!J-BRL|Y!J-TRG|Y!J-BRD|Y!J-BRG|Y!J-SRQ|Y!J-BRW|Y!J-BRW|Google PageSpeed)' 37 ].join('|'), 38 'i', 39 ), 40 41 externalContentObserver: null, 42 protectedExternalContents: [], 43 lastOpenedExternalContentId: 0, 44 jsonData: {}, 45 isExternalGroupAccepted: false, 46 fingerprintIcon: null, 47 consentModeDefaultsSent: false, 48 consentModeDefaults: { 49 ad_personalization: "denied", 50 ad_storage: "denied", 51 ad_user_data: "denied", 52 analytics_storage: "denied", 53 functionality_storage: "denied", 54 personalization_storage: "denied", 55 security_storage: "denied", 56 wait_for_update: 500 57 }, 58 59 /** 60 * Executes the script 61 */ 62 run: function() { 63 SgCookieOptin.closestPolyfill(); 64 SgCookieOptin.customEventPolyfill(); 65 66 SgCookieOptin.jsonData = JSON.parse(document.getElementById('cookieOptinData').innerHTML); 67 if (SgCookieOptin.jsonData) { 68 SgCookieOptin.setCookieNameBasedOnLanguage(); 69 // https://plainjs.com/javascript/events/running-code-when-the-document-is-ready-15/ 70 document.addEventListener('DOMContentLoaded', function() { 71 SgCookieOptin.initialize(); 72 }); 73 SgCookieOptin.isExternalGroupAccepted = SgCookieOptin.checkIsExternalGroupAccepted(); 74 SgCookieOptin.checkForExternalContents(); 75 76 if (SgCookieOptin.jsonData.settings.iframe_enabled && SgCookieOptin.isExternalGroupAccepted) { 77 document.addEventListener("DOMContentLoaded", function() { 78 SgCookieOptin.checkForDataAttributeExternalContents(); 79 }); 80 } 81 } 82 }, 83 84 /** 85 * Initializes the whole functionality. 86 * 87 * @return {void} 88 */ 89 initialize: function() { 90 // define gtag if it does not exist to set the defaults 91 if (!((typeof gtag === "function") || (typeof gtag === "object"))) { 92 window.dataLayer = window.dataLayer || []; 93 window.gtag = function() { 94 window.dataLayer.push(arguments); 95 } 96 } 97 98 if (!SgCookieOptin.jsonData.settings.disable_automatic_loading) { 99 const googleGroups = SgCookieOptin.findGroupsWithGoogleConsentMode(); 100 if (googleGroups.length > 0) { 101 if (!SgCookieOptin.consentModeDefaultsSent) { 102 console.log('SG Cookie OptIn: sent default gtag settings'); 103 gtag('consent', 'default', SgCookieOptin.consentModeDefaults); 104 SgCookieOptin.consentModeDefaultsSent = true; 105 } 106 } 107 } 108 109 // Automatically Accept or Reject cookies based on selected strategy or GET parameter 110 // Auto accept 111 if ( 112 SgCookieOptin.jsonData.settings.auto_action_for_bots === 1 && SgCookieOptin.isBotAgent() 113 || SgCookieOptin.getParameterByName('autoOptIn') === 'accept' 114 ) { 115 SgCookieOptin.acceptAllCookies(); 116 SgCookieOptin.updateCookieList(); 117 } 118 119 // Auto reject 120 if ( 121 SgCookieOptin.jsonData.settings.auto_action_for_bots === 2 && SgCookieOptin.isBotAgent() 122 || SgCookieOptin.getParameterByName('autoOptIn') === 'reject' 123 ) { 124 SgCookieOptin.acceptEssentialCookies(); 125 SgCookieOptin.updateCookieList(); 126 } 127 128 SgCookieOptin.handleScriptActivations(); 129 130 const optInContentElements = document.querySelectorAll('.sg-cookie-optin-plugin-uninitialized'); 131 for (let index = 0; index < optInContentElements.length; ++index) { 132 const optInContentElement = optInContentElements[index]; 133 SgCookieOptin.openCookieOptin(optInContentElement, {hideBanner: true}); 134 optInContentElement.classList.remove('sg-cookie-optin-plugin-uninitialized'); 135 optInContentElement.classList.add('sg-cookie-optin-plugin-initialized'); 136 } 137 138 // noinspection EqualityComparisonWithCoercionJS 139 const disableOptIn = SgCookieOptin.getParameterByName('disableOptIn') == true; 140 if (disableOptIn) { 141 return; 142 } 143 144 // noinspection EqualityComparisonWithCoercionJS 145 const showOptIn = SgCookieOptin.getParameterByName('showOptIn') == true; 146 const cookieValue = SgCookieOptin.getCookie(SgCookieOptin.COOKIE_NAME); 147 if (showOptIn || !SgCookieOptin.jsonData.settings.activate_testing_mode && 148 (SgCookieOptin.shouldShowBannerBasedOnLastPreferences(cookieValue)) 149 ) { 150 SgCookieOptin.openCookieOptin(null, {hideBanner: false}); 151 } else { 152 if (!SgCookieOptin.jsonData.settings.activate_testing_mode) { 153 SgCookieOptin.showFingerprint(); 154 } 155 } 156 }, 157 158 /** 159 * Checks if the current user agent matches the known Google PageSpeed user agens 160 * @returns {boolean} 161 */ 162 isBotAgent: function() {
163 return (SgCookieOptin.GOOGLE_PAGESPEED_USER_AGENT_REGEX.test(navigator.userAgent)); 164 }, 165 166 /** 167 * Sets the sg_cookie_optin cookie name accordingly based on the current language settings 168 */ 169 setCookieNameBasedOnLanguage: function() { 170 if (!SgCookieOptin.jsonData.settings.unified_cookie_name) { 171 SgCookieOptin.COOKIE_NAME += '_' + SgCookieOptin.jsonData.settings.identifier + '_' 172 + SgCookieOptin.jsonData.settings.language; 173 SgCookieOptin.LAST_PREFERENCES_COOKIE_NAME += '_' + SgCookieOptin.jsonData.settings.identifier + '_' 174 + SgCookieOptin.jsonData.settings.language; 175 } 176 }, 177 178 /** 179 * Checks if the external cookie group has been accepted 180 * @returns {boolean} 181 */ 182 checkIsExternalGroupAccepted: function() { 183 return SgCookieOptin.checkIsGroupAccepted(SgCookieOptin.COOKIE_GROUP_EXTERNAL_CONTENT); 184 }, 185 186 /** 187 * Checks whether the given group has been accepted or not 188 * 189 * @param {string} groupName 190 * @param {string=} cookieValue 191 * @returns {boolean} 192 */ 193 checkIsGroupAccepted: function(groupName, cookieValue) { 194 if (typeof cookieValue === 'undefined') { 195 cookieValue = SgCookieOptin.getCookie(SgCookieOptin.COOKIE_NAME); 196 } 197 if (cookieValue) { 198 const splitedCookieValue = cookieValue.split('|'); 199 for (const splitedCookieValueIndex in splitedCookieValue) { 200 if (!splitedCookieValue.hasOwnProperty(splitedCookieValueIndex)) { 201 continue; 202 } 203 204 const splitedCookieValueEntry = splitedCookieValue[splitedCookieValueIndex]; 205 const groupAndStatus = splitedCookieValueEntry.split(':'); 206 if (!groupAndStatus.hasOwnProperty(0) || !groupAndStatus.hasOwnProperty(1)) { 207 continue; 208 } 209 210 const group = groupAndStatus[0]; 211 const status = parseInt(groupAndStatus[1]); 212 if (group === groupName) { 213 if (status === 1) { 214 return true; 215 } 216 break; 217 } 218 } 219 } 220 return false; 221 }, 222 223 /** 224 * Checks whether the given group has been accepted or not 225 * 226 * @param {string} cookieValue 227 * @returns {*[]} 228 */ 229 readCookieValues: function(cookieValue) { 230 if (typeof cookieValue === 'undefined') { 231 cookieValue = SgCookieOptin.getCookie(SgCookieOptin.COOKIE_NAME); 232 } 233 234 const cookieValues = []; 235 236 if (cookieValue) { 237 const splitedCookieValue = cookieValue.split('|'); 238 for (const splitedCookieValueIndex in splitedCookieValue) { 239 if (!splitedCookieValue.hasOwnProperty(splitedCookieValueIndex)) { 240 continue; 241 } 242 243 const splitedCookieValueEntry = splitedCookieValue[splitedCookieValueIndex]; 244 const groupAndStatus = splitedCookieValueEntry.split(':'); 245 if (!groupAndStatus.hasOwnProperty(0) || !groupAndStatus.hasOwnProperty(1)) { 246 continue; 247 } 248 249 const group = groupAndStatus[0]; 250 cookieValues[group] = parseInt(groupAndStatus[1]); 251 } 252 } 253 254 return cookieValues; 255 }, 256 257 /** 258 * Grants or denies the consent for all groups based on the cookie data 259 */ 260 dispatchGtagConsentForAllGroups: function() { 261 const cookieValues = SgCookieOptin.readCookieValues(); 262 for (let index in cookieValues) { 263 if (!cookieValues.hasOwnProperty(index) || index === 'essential') { 264 continue; 265 } 266 267 const group = SgCookieOptin.getGroupByGroupName(index); 268 if (!group || typeof group.googleName !== 'string' || group.googleName.trim() === '') { 269 continue; 270 } 271 272 if (cookieValues[index]) { 273 SgCookieOptin.dispatchGtagConsent(group['groupName']) 274 } else { 275 SgCookieOptin.dispatchGtagReject(group['groupName']) 276 } 277 } 278 }, 279 280 /** 281 * Grants the Google Consent Mode for the specific consent group 282 * 283 * @param {string} group 284 */ 285 dispatchGtagConsent: function(group) { 286 // dispatch Google Consent Mode API calls if Consent Mode is enabled 287 if ((typeof gtag === "function") || (typeof gtag === "object")) { 288 for (const groupIndex in SgCookieOptin.jsonData.cookieGroups) { 289 if (!SgCookieOptin.jsonData.cookieGroups.hasOwnProperty(groupIndex) || SgCookieOptin.jsonData.cookieGroups[groupIndex]['groupName'] !== group) { 290 continue; 291 } 292 293 if (SgCookieOptin.jsonData.cookieGroups[groupIndex]['googleName']) { 294 const googleGroups = SgCookieOptin.jsonData.cookieGroups[groupIndex]['googleName'].split(','); 295 for (index = 0; index < googleGroups.length; index++) { 296 let googleGroupName = googleGroups[index].trim(); 297 gtag('consent', 'update', { 298 [googleGroupName]: 'granted' 299 }); 300 console.log('Consent granted for ' + googleGroupName); 301 } 302 } 303 } 304 } 305 }, 306 307 /** 308 * Rejects the Google Consent Mode for the specific consent group 309 * 310 * @param {string} group 311 */ 312 dispatchGtagReject: function(group) { 313 if ((typeof gtag === "function") || (typeof gtag === "object")) { 314 for (const groupIndex in SgCookieOptin.jsonData.cookieGroups) { 315 if (!SgCookieOptin.jsonData.cookieGroups.hasOwnProperty(groupIndex) || SgCookieOptin.jsonData.cookieGroups[groupIndex]['groupName'] !== group) { 316 continue; 317 } 318 319 if (SgCookieOptin.jsonData.cookieGroups[groupIndex]['googleName']) { 320 const googleGroups = SgCookieOptin.jsonData.cookieGroups[groupIndex]['googleName'].split(','); 321 for (index = 0; index < googleGroups.length; index++) { 322 let googleGroupName = googleGroups[index].trim(); 323 gtag('consent', 'update', { 324 [googleGroupName]: 'denied' 325 }); 326 console.log('Consent rejected for ' + googleGroupName); 327 } 328 } 329 } 330 } 331 }, 332 333 /** 334 * Checks the configuration for groups having configured Google Consent Mode v2 335 */ 336 findGroupsWithGoogleConsentMode: function() { 337 const cookieValues = SgCookieOptin.readCookieValues(); 338 const googleGroups = []; 339 for (let index in cookieValues) { 340 if (!cookieValues.hasOwnProperty(index) || index === 'essential') { 341 continue; 342 } 343 344 const group = SgCookieOptin.getGroupByGroupName(index); 345 if (!group || typeof group.googleName !== 'string' || group.googleName.trim() === '') { 346 continue; 347 } 348 349 googleGroups.push(group); 350 } 351 return googleGroups; 352 }, 353 354 /** 355 * Handles the scripts of the allowed cookie groups. 356 * 357 * @return {void} 358 */ 359 handleScriptActivations: function() { 360 const cookieValue = SgCookieOptin.getCookie(SgCookieOptin.COOKIE_NAME); 361 if (!cookieValue) { 362 return; 363 } 364 365 const splitedCookieValue = cookieValue.split('|'); 366 for (const splitedCookieValueIndex in splitedCookieValue) { 367 if (!splitedCookieValue.hasOwnProperty(splitedCookieValueIndex)) { 368 continue; 369 } 370 371 const splitedCookieValueEntry = splitedCookieValue[splitedCookieValueIndex]; 372 const groupAndStatus = splitedCookieValueEntry.split(':'); 373 if (!groupAndStatus.hasOwnProperty(0) || !groupAndStatus.hasOwnProperty(1)) { 374 continue; 375 } 376 377 const group = groupAndStatus[0]; 378 const status = parseInt(groupAndStatus[1]); 379 if (!status) { 380 if (((typeof gtag === "function") || (typeof gtag === "object"))) { 381 SgCookieOptin.dispatchGtagReject(group); 382 } 383 continue; 384 } 385 386 for (const groupIndex in SgCookieOptin.jsonData.cookieGroups) { 387 if (!SgCookieOptin.jsonData.cookieGroups.hasOwnProperty(groupIndex) || SgCookieOptin.jsonData.cookieGroups[groupIndex]['groupName'] !== group) { 388 continue; 389 } 390 391 if ((typeof gtag === "function") || (typeof gtag === "object")) {
392 SgCookieOptin.dispatchGtagConsent(SgCookieOptin.jsonData.cookieGroups[groupIndex]['groupName']); 393 } 394 395 if ( 396 SgCookieOptin.jsonData.cookieGroups[groupIndex]['loadingHTML'] && 397 SgCookieOptin.jsonData.cookieGroups[groupIndex]['loadingHTML'] !== '' 398 ) { 399 const head = document.getElementsByTagName('head')[0]; 400 if (head) { 401 const range = document.createRange(); 402 range.selectNode(head); 403 head.appendChild(range.createContextualFragment(SgCookieOptin.jsonData.cookieGroups[groupIndex]['loadingHTML'])); 404 // Emit event 405 const addedLoadingHTMLEvent = new CustomEvent('addedLoadingHTML', { 406 bubbles: true, 407 detail: { 408 src: SgCookieOptin.jsonData.cookieGroups[groupIndex]['loadingHTML'] 409 } 410 }); 411 head.dispatchEvent(addedLoadingHTMLEvent); 412 } 413 } 414 415 if ( 416 SgCookieOptin.jsonData.cookieGroups[groupIndex]['loadingJavaScript'] && 417 SgCookieOptin.jsonData.cookieGroups[groupIndex]['loadingJavaScript'] !== '' 418 ) { 419 const script = document.createElement('script'); 420 script.setAttribute('src', SgCookieOptin.jsonData.cookieGroups[groupIndex]['loadingJavaScript']); 421 script.setAttribute('type', 'text/javascript'); 422 document.body.appendChild(script); 423 424 // Emit event 425 const addedLoadingScriptEvent = new CustomEvent('addedLoadingScript', { 426 bubbles: true, 427 detail: { 428 src: SgCookieOptin.jsonData.cookieGroups[groupIndex]['loadingJavaScript'] 429 } 430 }); 431 script.dispatchEvent(addedLoadingScriptEvent); 432 } 433 } 434 } 435 }, 436 437 /** 438 * Opens the cookie consent box. 439 * 440 * Supported options: 441 * {boolean} hideBanner Whether to show the cookie banner or not, if it's enabled 442 * 443 * @param {Element=} contentElement 444 * @param {object=} options 445 * 446 * @return {void} 447 */ 448 openCookieOptin: function(contentElement, options) { 449 if (!SgCookieOptin.shouldShowOptinBanner()) { 450 return; 451 } 452 453 SgCookieOptin.hideFingerprint(); 454 const hideBanner = typeof options == 'object' && options.hideBanner === true; 455 const fromBanner = typeof options == 'object' && options.fromBanner === true; 456 457 const wrapper = document.createElement('DIV'); 458 wrapper.setAttribute('data-nosnippet', ''); 459 wrapper.id = 'SgCookieOptin'; 460 461 let forceBanner; 462 if (SgCookieOptin.jsonData.settings.banner_force_min_width && parseInt(SgCookieOptin.jsonData.settings.banner_force_min_width) > 0) { 463 forceBanner = window.matchMedia('(max-width: ' + parseInt(SgCookieOptin.jsonData.settings.banner_force_min_width) + 'px)').matches; 464 } else { 465 forceBanner = false; 466 } 467 468 if (!fromBanner && !hideBanner && ((!contentElement && SgCookieOptin.jsonData.settings.banner_enable && !hideBanner) || forceBanner)) { 469 wrapper.classList.add('sg-cookie-optin-banner-wrapper'); 470 wrapper.insertAdjacentHTML('afterbegin', SgCookieOptin.jsonData.mustacheData.banner.markup); 471 } else { 472 wrapper.insertAdjacentHTML('afterbegin', SgCookieOptin.jsonData.mustacheData.template.markup); 473 } 474 475 SgCookieOptin.insertUserUuid(wrapper); 476 477 SgCookieOptin.addListeners(wrapper, contentElement); 478 479 if (!contentElement) { 480 document.body.insertAdjacentElement('beforeend', wrapper); 481 } else { 482 contentElement.appendChild(wrapper); 483 } 484 485 setTimeout(function() { 486 SgCookieOptin.adjustDescriptionHeight(wrapper, contentElement); 487 SgCookieOptin.updateCookieList(); 488 // Emit event 489 const cookieOptinShownEvent = new CustomEvent('cookieOptinShown', { 490 bubbles: true, 491 detail: {} 492 }); 493 document.body.dispatchEvent(cookieOptinShownEvent); 494 495 // check if there is a cookie consent plugin on the page - then don't focus the checkboxes 496 if (document.getElementsByClassName('sg-cookie-optin-plugin-initialized').length > 0) { 497 return; 498 } 499 500 const checkboxes = document.getElementsByClassName('sg-cookie-optin-checkbox'); 501 if (checkboxes.length > 1) { 502 if (checkboxes[1].focus) { 503 checkboxes[1].focus(); 504 } 505 } 506 507 }, 10); 508 }, 509 510 /** 511 * Checks if the cookie banner should be shown 512 * 513 * @returns {boolean} 514 */ 515 shouldShowOptinBanner: function() { 516 // check doNotTrack 517 if (typeof SgCookieOptin.jsonData.settings.consider_do_not_track !== 'undefined' 518 && SgCookieOptin.jsonData.settings.consider_do_not_track 519 && typeof navigator.doNotTrack !== 'undefined' && navigator.doNotTrack === '1') { 520 console.log('Cookie Consent: DoNotTrack detected - Auto-OptOut'); 521 return false;
522 } 523 524 // check if there is a cookie consent plugin on the page 525 if (document.getElementsByClassName('sg-cookie-optin-plugin-initialized').length > 0) { 526 return false; 527 } 528 529 // test if the current URL matches one of the whitelist regex 530 if (typeof SgCookieOptin.jsonData.settings.cookiebanner_whitelist_regex !== 'undefined' 531 && SgCookieOptin.jsonData.settings.cookiebanner_whitelist_regex.trim() !== '' 532 ) { 533 const regularExpressions = SgCookieOptin.jsonData.settings.cookiebanner_whitelist_regex.trim() 534 .split(/\r?\n/).map(function(value) { 535 return new RegExp(value); 536 }); 537 if (typeof regularExpressions === 'object' && regularExpressions.length > 0) { 538 for (const regExIndex in regularExpressions) { 539 if (regularExpressions[regExIndex].test(window.location)) { 540 return false; 541 } 542 } 543 } 544 } 545 546 return true; 547 }, 548 549 /** 550 * Adjusts the description height for each element, for the new design. 551 * 552 * @param {HTMLElement} container 553 * @param {Element} contentElement 554 * @return {void} 555 */ 556 adjustDescriptionHeight: function(container, contentElement) { 557 let index; 558 let columnsPerRow = 4; 559 if (contentElement === null) { 560 if (window.innerWidth <= 750) { 561 return; 562 } else if (window.innerWidth <= 1050) { 563 columnsPerRow = 2; 564 } else if (window.innerWidth <= 1250) { 565 columnsPerRow = 3; 566 } 567 } else { 568 columnsPerRow = 2; 569 } 570 571 const maxHeightPerRow = []; 572 let maxHeightPerRowIndex = 0; 573 const descriptions = container.querySelectorAll('.sg-cookie-optin-box-new .sg-cookie-optin-checkbox-description'); 574 for (index = 0; index < descriptions.length; ++index) { 575 if (!(index % columnsPerRow)) { 576 ++maxHeightPerRowIndex; 577 } 578 579 const descriptionHeight = descriptions[index].getBoundingClientRect().height; 580 const maxHeight = (maxHeightPerRow[maxHeightPerRowIndex] ? maxHeightPerRow[maxHeightPerRowIndex] : 0); 581 if (descriptionHeight > maxHeight) { 582 maxHeightPerRow[maxHeightPerRowIndex] = descriptionHeight; 583 } 584 } 585 586 maxHeightPerRowIndex = 0; 587 for (index = 0; index < descriptions.length; ++index) { 588 if (!(index % columnsPerRow)) { 589 ++maxHeightPerRowIndex; 590 } 591 592 descriptions[index].style.height = maxHeightPerRow[maxHeightPerRowIndex] + 'px'; 593 } 594 }, 595 596 /** 597 * Adjusts the description height for each element, for the new design. 598 * 599 * @param {HTMLElement} container 600 * @param {HTMLElement} contentElement 601 * @return {void} 602 */ 603 adjustReasonHeight: function(container, contentElement) { 604 let columnsPerRow = 3; 605 if (contentElement === null) { 606 if (window.innerWidth <= 750) { 607 return; 608 } else if (window.innerWidth <= 1050) { 609 columnsPerRow = 2; 610 } 611 } else { 612 columnsPerRow = 2; 613 } 614 615 const listItems = container.querySelectorAll('.sg-cookie-optin-box-new .sg-cookie-optin-box-cookie-list-item'); 616 for (let listItemIndex = 0; listItemIndex < listItems.length; ++listItemIndex) { 617 let index; 618 const maxHeightPerRow = []; 619 let maxHeightPerRowIndex = 0; 620 621 const reasons = listItems[listItemIndex].querySelectorAll('.sg-cookie-optin-box-table-reason'); 622 for (index = 0; index < reasons.length; ++index) { 623 if (!(index % columnsPerRow)) { 624 ++maxHeightPerRowIndex; 625 } 626 627 let reasonHeight = reasons[index].getBoundingClientRect().height; 628 reasonHeight -= parseInt(window.getComputedStyle(reasons[index], null).getPropertyValue('padding-top')); 629 reasonHeight -= parseInt(window.getComputedStyle(reasons[index], null).getPropertyValue('padding-bottom')); 630 const maxHeight = (maxHeightPerRow[maxHeightPerRowIndex] ? maxHeightPerRow[maxHeightPerRowIndex] : 0); 631 if (reasonHeight > maxHeight) { 632 maxHeightPerRow[maxHeightPerRowIndex] = reasonHeight; 633 } 634 } 635 636 maxHeightPerRowIndex = 0; 637 for (index = 0; index < reasons.length; ++index) { 638 if (!(index % columnsPerRow)) { 639 ++maxHeightPerRowIndex; 640 } 641 642 reasons[index].style.height = maxHeightPerRow[maxHeightPerRowIndex] + 'px'; 643 } 644 } 645 }, 646 647 /** 648 * Checks whether we must show the cookie banner based on the last preferences of the user. 649 * This may be necessary if there are new groups or new cookies since the last preferences save, 650 * or the user didn't select all preferences and the configured interval has expired 651 * 652 * @param {String} cookieValue 653 * @returns {boolean} 654 */ 655 shouldShowBannerBasedOnLastPreferences: function(cookieValue) { 656 if (!cookieValue) { 657 return true; 658 } 659 660 const lastPreferences = SgCookieOptin.getLastPreferences(); 661 662 if (typeof lastPreferences.timestamp === 'undefined') { 663 return true; 664 } 665 666 if (lastPreferences.version !== SgCookieOptin.jsonData.settings.version) { 667 return true; 668 } 669 670 for (const groupIndex in SgCookieOptin.jsonData.cookieGroups) { 671 if (!SgCookieOptin.jsonData.cookieGroups.hasOwnProperty(groupIndex)) { 672 continue; 673 } 674 675 // is the group new 676 if (typeof SgCookieOptin.jsonData.cookieGroups[groupIndex].crdate !== 'undefined' && 677 SgCookieOptin.jsonData.cookieGroups[groupIndex].crdate > lastPreferences.timestamp) { 678 return true; 679 } 680 681 // is there a new cookie in this group 682 let cookieIndex; 683 for (cookieIndex in SgCookieOptin.jsonData.cookieGroups[groupIndex].cookieData) { 684 if (!SgCookieOptin.jsonData.cookieGroups[groupIndex].cookieData.hasOwnProperty(cookieIndex)) { 685 continue; 686 } 687 688 if (typeof SgCookieOptin.jsonData.cookieGroups[groupIndex].cookieData[cookieIndex].crdate !== 'undefined' 689 && SgCookieOptin.jsonData.cookieGroups[groupIndex].cookieData[cookieIndex].crdate > lastPreferences.timestamp) { 690 return true; 691 } 692 } 693 694 // if the user didn't select all group last time, check if the group was not accepted and the configured interval has expired 695 if (!lastPreferences.isAll && 696 typeof SgCookieOptin.jsonData.cookieGroups[groupIndex].groupName !== 'undefined' && 697 !SgCookieOptin.checkIsGroupAccepted(SgCookieOptin.jsonData.cookieGroups[groupIndex].groupName, 698 lastPreferences.cookieValue) 699 && (new Date().getTime() / 1000) > (lastPreferences.timestamp + 24 * 60 * 60 700 * SgCookieOptin.jsonData.settings.banner_show_again_interval)) { 701 return true; 702 } 703 } 704 return false;
705 }, 706 707 /** 708 * Gets the current user uuid 709 * 710 * @param {boolean} setIfEmpty 711 * @returns {string} 712 */ 713 getUserUuid: function(setIfEmpty) { 714 const lastPreferences = SgCookieOptin.getLastPreferences(); 715 let userUuid = lastPreferences.uuid; 716 if (setIfEmpty && !userUuid && !SgCookieOptin.jsonData.settings.disable_usage_statistics) { 717 userUuid = SgCookieOptin.generateUUID(); 718 lastPreferences.uuid = userUuid; 719 window.localStorage.setItem(SgCookieOptin.LAST_PREFERENCES_LOCAL_STORAGE_NAME, JSON.stringify(lastPreferences)); 720 } 721 722 return userUuid; 723 }, 724 725 /** 726 * Shows the UserUuid in the cookie banner 727 * 728 * @param {HTMLElement} wrapper 729 */ 730 insertUserUuid: function(wrapper) { 731 const hashContainer = wrapper.querySelector('.sg-cookie-optin-box-footer-user-hash'); 732 const hashContainerParent = wrapper.querySelector('.sg-cookie-optin-box-footer-user-hash-container'); 733 734 // The banner does not have a hash container 735 if (!hashContainer || !hashContainerParent) { 736 return; 737 } 738 739 if (SgCookieOptin.jsonData.settings.disable_usage_statistics) { 740 hashContainerParent.style.display = 'none'; 741 return; 742 } 743 744 const uuid = SgCookieOptin.getUserUuid(false); 745 746 if (typeof hashContainer.innerText === 'string') { 747 if (uuid) { 748 hashContainer.innerText = uuid; 749 } else { 750 hashContainerParent.style.display = 'none'; 751 } 752 } 753 }, 754 755 /** 756 * Stores the last saved preferences in the localstorage 757 * 758 * @param {string} cookieValue 759 */ 760 saveLastPreferences: function(cookieValue) { 761 let isAll = true; 762 for (let groupIndex in SgCookieOptin.jsonData.cookieGroups) { 763 if (!SgCookieOptin.jsonData.cookieGroups.hasOwnProperty(groupIndex)) { 764 continue; 765 } 766 if (!SgCookieOptin.checkIsGroupAccepted(SgCookieOptin.jsonData.cookieGroups[groupIndex].groupName)) { 767 isAll = false; 768 } 769 } 770 771 const lastPreferences = SgCookieOptin.getLastPreferences(); 772 773 lastPreferences.timestamp = Math.floor(new Date().getTime() / 1000); 774 lastPreferences.cookieValue = cookieValue; 775 lastPreferences.isAll = isAll; 776 lastPreferences.version = SgCookieOptin.jsonData.settings.version; 777 lastPreferences.identifier = SgCookieOptin.jsonData.settings.identifier; 778 779 if (SgCookieOptin.lastPreferencesFromCookie()) { 780 SgCookieOptin.setCookie(SgCookieOptin.LAST_PREFERENCES_COOKIE_NAME, JSON.stringify(lastPreferences), '365') 781 } else { 782 window.localStorage.setItem(SgCookieOptin.LAST_PREFERENCES_LOCAL_STORAGE_NAME, JSON.stringify(lastPreferences)); 783 } 784 785 SgCookieOptin.saveLastPreferencesForStats(lastPreferences); 786 }, 787 788 /** 789 * Decides whether to read the last preferences from cookie or from local storage 790 * 791 * @returns {boolean} 792 */ 793 lastPreferencesFromCookie: function() { 794 return (SgCookieOptin.jsonData.settings.subdomain_support || SgCookieOptin.jsonData.settings.set_cookie_for_domain); 795 }, 796 797 /** 798 * Saves the preferences for statistics via AJAX 799 * 800 * @param lastPreferences 801 */ 802 saveLastPreferencesForStats: function(lastPreferences) { 803 if (SgCookieOptin.jsonData.settings.disable_usage_statistics) { 804 return; 805 } 806 807 lastPreferences.uuid = SgCookieOptin.getUserUuid(true); 808 809 const request = new XMLHttpRequest(); 810 const formData = new FormData(); 811 formData.append('lastPreferences', JSON.stringify(lastPreferences)); 812 const url = SgCookieOptin.jsonData.settings.save_history_webhook; 813 814 if (!url) { 815 return; 816 } 817 818 request.open('POST', url); 819 request.send(formData); 820 }, 821 822 /** 823 * Generates a RFC4122 v4 compliant UUID 824 * 825 * @returns {string} 826 */ 827 generateUUID: function() { // Public Domain/MIT 828 let d = new Date().getTime();//Timestamp 829 let d2 = (performance && performance.now && (performance.now() * 1000)) || 0;//Time in microseconds since page-load or 0 if unsupported 830 return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 831 let r = Math.random() * 16;//random number between 0 and 16 832 if (d > 0) {//Use timestamp until depleted 833 r = (d + r) % 16 | 0; 834 d = Math.floor(d / 16); 835 } else {//Use microseconds since page-load if supported 836 r = (d2 + r) % 16 | 0; 837 d2 = Math.floor(d2 / 16); 838 } 839 return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); 840 }); 841 }, 842 843 /**
844 * Delete all cookies that match the regex of the cookie name if a given group has been unselected by the user 845 */ 846 deleteCookiesForUnsetGroups: function() { 847 for (const groupIndex in SgCookieOptin.jsonData.cookieGroups) { 848 if (!SgCookieOptin.jsonData.cookieGroups.hasOwnProperty(groupIndex)) { 849 continue; 850 } 851 const documentCookies = document.cookie.split('; '); 852 if (!SgCookieOptin.checkIsGroupAccepted(SgCookieOptin.jsonData.cookieGroups[groupIndex].groupName)) { 853 for (const cookieIndex in SgCookieOptin.jsonData.cookieGroups[groupIndex].cookieData) { 854 if (isNaN(parseInt(cookieIndex))) { 855 continue; 856 } 857 858 for (const documentCookieIndex in documentCookies) { 859 if (isNaN(parseInt(documentCookieIndex))) { 860 continue; 861 } 862 863 const cookieName = documentCookies[documentCookieIndex].split('=')[0]; 864 let regExString = SgCookieOptin.jsonData.cookieGroups[groupIndex].cookieData[cookieIndex] 865 .Name.trim(); 866 867 if (!regExString) { 868 continue; 869 } 870 871 if (regExString.charAt(0) !== '^') { 872 regExString = '^' + regExString; 873 } 874 875 if (regExString.charAt(regExString.length - 1) !== '$') { 876 regExString += '$'; 877 } 878 879 const regEx = new RegExp(regExString); 880 if (regEx.test(cookieName)) { 881 // delete the cookie 882 SgCookieOptin.deleteGroupCookie(cookieName); 883 } 884 } 885 } 886 } 887 } 888 }, 889 890 /** 891 * Delete the cookie from the current domain and the additional domains 892 * 893 * @param cookieName 894 */ 895 deleteGroupCookie: function(cookieName) { 896 let cookie = cookieName + '=; path=/; Max-Age=-99999999;'; 897 document.cookie = cookie; // This is important in case the configuration that we test below has been changed 898 const currentHost = window.location.hostname; 899 900 if (SgCookieOptin.jsonData.settings.set_cookie_for_domain && SgCookieOptin.jsonData.settings.set_cookie_for_domain.length > 0) { 901 cookie += ';domain=' + SgCookieOptin.jsonData.settings.set_cookie_for_domain; 902 } else if (SgCookieOptin.jsonData.settings.subdomain_support) { 903 const domainParts = currentHost.split('.'); 904 if (domainParts.length > 2) { 905 domainParts.shift(); 906 const hostnameToFirstDot = '.' + domainParts.join('.'); 907 cookie += ';domain=' + hostnameToFirstDot; 908 } 909 } 910 911 document.cookie = cookie; 912 913 const additionalDomains = SgCookieOptin.jsonData.settings.domains_to_delete_cookies_for.trim() 914 .split(/\r?\n/).map(function(value) { 915 return value.trim(); 916 }); 917 918 for (const additionalDomainIndex in additionalDomains) { 919 if (!additionalDomains.hasOwnProperty(additionalDomainIndex)) { 920 continue; 921 } 922 923 document.cookie = cookieName + '=; path=/; ' + 'domain=' + additionalDomains[additionalDomainIndex] + '; Max-Age=-99999999;'; 924 } 925 }, 926 927 /** 928 * Adds the listeners to the given element. 929 * 930 * @param {HTMLElement} element 931 * @param {Element} contentElement 932 * 933 * @return {void} 934 */ 935 addListeners: function(element, contentElement) { 936 const closeButtons = element.querySelectorAll('.sg-cookie-optin-box-close-button'); 937 SgCookieOptin.addEventListenerToList(closeButtons, 'click', function() { 938 SgCookieOptin.acceptEssentialCookies(); 939 SgCookieOptin.updateCookieList(); 940 SgCookieOptin.handleScriptActivations(); 941 942 if (!contentElement) { 943 SgCookieOptin.hideCookieOptIn(); 944 } 945 }); 946 947 const openMoreLinks = element.querySelectorAll('.sg-cookie-optin-box-open-more-link'); 948 SgCookieOptin.addEventListenerToList(openMoreLinks, 'click', SgCookieOptin.openCookieDetails); 949 950 const openSubListLink = element.querySelectorAll('.sg-cookie-optin-box-sublist-open-more-link'); 951 SgCookieOptin.addEventListenerToList(openSubListLink, 'click', function(event) { 952 SgCookieOptin.openSubList(event, contentElement); 953 }); 954 955 const acceptAllButtons = element.querySelectorAll( 956 '.sg-cookie-optin-box-button-accept-all, .sg-cookie-optin-banner-button-accept' 957 ); 958 SgCookieOptin.addEventListenerToList(acceptAllButtons, 'click', function() { 959 SgCookieOptin.acceptAllCookies(); 960 SgCookieOptin.updateCookieList(); 961 SgCookieOptin.handleScriptActivations(); 962 963 if (!contentElement) { 964 SgCookieOptin.hideCookieOptIn(); 965 } else { 966 SgCookieOptin.showSaveConfirmation(contentElement); 967 } 968 }); 969 970 const acceptSpecificButtons = element.querySelectorAll('.sg-cookie-optin-box-button-accept-specific'); 971 SgCookieOptin.addEventListenerToList(acceptSpecificButtons, 'click', function() { 972 SgCookieOptin.acceptSpecificCookies(this); 973 SgCookieOptin.updateCookieList(this); 974 SgCookieOptin.handleScriptActivations(); 975 976 if (!contentElement) { 977 SgCookieOptin.hideCookieOptIn(); 978 } else { 979 SgCookieOptin.showSaveConfirmation(contentElement); 980 } 981 }); 982 983 const acceptEssentialButtons = element.querySelectorAll('.sg-cookie-optin-box-button-accept-essential, .sg-cookie-optin-banner-button-accept-essential'); 984 SgCookieOptin.addEventListenerToList(acceptEssentialButtons, 'click', function() { 985 SgCookieOptin.acceptEssentialCookies(); 986 SgCookieOptin.updateCookieList(); 987 SgCookieOptin.handleScriptActivations(); 988 989 if (!contentElement) { 990 SgCookieOptin.hideCookieOptIn(); 991 } else { 992 SgCookieOptin.showSaveConfirmation(contentElement); 993 } 994 }); 995 996 const openSettingsButtons = element.querySelectorAll('.sg-cookie-optin-banner-button-settings'); 997 SgCookieOptin.addEventListenerToList(openSettingsButtons, 'click', function() { 998 SgCookieOptin.hideCookieOptIn(); 999 SgCookieOptin.openCookieOptin(null, {hideBanner: true, fromBanner: true}); 1000 }); 1001 1002 // handle dependent cookies 1003 const checkboxes = element.querySelectorAll('.sg-cookie-optin-checkbox'); 1004 SgCookieOptin.addEventListenerToList(checkboxes, 'change', function(event) { 1005 SgCookieOptin.handleDependentGroups(event, checkboxes); 1006 SgCookieOptin.handleCheckboxChange(event.target); 1007 }); 1008 1009 }, 1010 1011 /** 1012 * If the changed group has dependent groups and is checked - check also all of the dependent groups 1013 * 1014 * @param event 1015 * @param checkboxes 1016 */ 1017 handleDependentGroups: function(event, checkboxes) { 1018 const changedCheckbox = event.target.closest('input.sg-cookie-optin-checkbox'); 1019 const changedGroup = SgCookieOptin.getGroupByGroupName(changedCheckbox.defaultValue); 1020 if (changedCheckbox.checked) { 1021 // check all of the dependent groups 1022 if (!changedGroup.dependentGroups) { 1023 return; 1024 } 1025 const dependentGroups = changedGroup.dependentGroups.split(','); 1026 for (let depIndex = 0; depIndex < dependentGroups.length; depIndex++) { 1027 for (let index = 0; index < checkboxes.length; ++index) { 1028 if (checkboxes[index].defaultValue === dependentGroups[depIndex].trim()) { 1029 checkboxes[index].checked = true; 1030 } 1031 } 1032 } 1033 } else { 1034 // see if other groups depend on this group and uncheck them as well 1035 for (const groupIndex in SgCookieOptin.jsonData.cookieGroups) { 1036 if (!SgCookieOptin.jsonData.cookieGroups.hasOwnProperty(groupIndex)) { 1037 continue; 1038 } 1039 1040 const groupName = SgCookieOptin.jsonData.cookieGroups[groupIndex]['groupName']; 1041 if (!groupName) { 1042 continue; 1043 } 1044 1045 if (SgCookieOptin.jsonData.cookieGroups[groupIndex].dependentGroups) { 1046 if (SgCookieOptin.jsonData.cookieGroups[groupIndex].dependentGroups.split(',') 1047 .map(function(item) { 1048 return item.trim(); 1049 }) 1050 .includes(changedGroup['groupName'])) { 1051 for (let checkboxIndex = 0; checkboxIndex < checkboxes.length; checkboxIndex++) {
1052 if (checkboxes[checkboxIndex].defaultValue === groupName) { 1053 checkboxes[checkboxIndex].checked = false; 1054 } 1055 } 1056 } 1057 } 1058 } 1059 } 1060 1061 }, 1062 1063 /** 1064 * Adds an event to a given node list. 1065 * 1066 * @param {NodeListOf<Element>} list 1067 * @param {string} event 1068 * @param {function} assignedFunction 1069 * 1070 * @return {void} 1071 */ 1072 addEventListenerToList: function(list, event, assignedFunction) { 1073 for (let index = 0; index < list.length; ++index) { 1074 list[index].addEventListener(event, assignedFunction, false); 1075 } 1076 }, 1077 1078 /** 1079 * Hides the cookie consent. 1080 * 1081 * @return {void} 1082 */ 1083 hideCookieOptIn: function() { 1084 // The content element cookie consents aren't removed, because querySelector gets only the first entry and it's 1085 // always the modular one. 1086 const optins = document.querySelectorAll('#SgCookieOptin'); 1087 for (const index in optins) { 1088 if (!optins.hasOwnProperty(index)) { 1089 continue; 1090 } 1091 1092 // Because of the IE11 no .remove(); 1093 const optin = optins[index]; 1094 const parentNode = optin.parentNode; 1095 if (!parentNode || parentNode.classList.contains('sg-cookie-optin-plugin')) { 1096 continue; 1097 } 1098 1099 parentNode.removeChild(optin); 1100 1101 // Emit event 1102 const cookieOptinHiddenEvent = new CustomEvent('cookieOptinHidden', { 1103 bubbles: true, 1104 detail: {} 1105 }); 1106 parentNode.dispatchEvent(cookieOptinHiddenEvent); 1107 } 1108 1109 SgCookieOptin.showFingerprint(); 1110 }, 1111 1112 /** 1113 * Returns the cookie list DOM. 1114 * 1115 * @param {Element=} triggerElement 1116 * 1117 * @return {void} 1118 */ 1119 updateCookieList: function(triggerElement) { 1120 let checkBoxesContainer; 1121 const statusMap = {}; 1122 const cookieValue = SgCookieOptin.getCookie(SgCookieOptin.COOKIE_NAME); 1123 if (cookieValue) { 1124 const splitedCookieValue = cookieValue.split('|'); 1125 for (const valueIndex in splitedCookieValue) { 1126 if (!splitedCookieValue.hasOwnProperty(valueIndex)) { 1127 continue; 1128 } 1129 1130 const splitedCookieValueEntry = splitedCookieValue[valueIndex]; 1131 const groupAndStatus = splitedCookieValueEntry.split(':'); 1132 if (!groupAndStatus.hasOwnProperty(0) || !groupAndStatus.hasOwnProperty(1)) { 1133 continue; 1134 } 1135 1136 statusMap[groupAndStatus[0]] = parseInt(groupAndStatus[1]); 1137 } 1138 } 1139 1140 for (const groupIndex in SgCookieOptin.jsonData.cookieGroups) { 1141 if (!SgCookieOptin.jsonData.cookieGroups.hasOwnProperty(groupIndex)) { 1142 continue; 1143 } 1144 1145 const groupName = SgCookieOptin.jsonData.cookieGroups[groupIndex]['groupName']; 1146 if (!groupName) { 1147 continue; 1148 } 1149 1150 if (!statusMap.hasOwnProperty(groupName)) { 1151 continue; 1152 } 1153 1154 if (!triggerElement) { 1155 checkBoxesContainer = document; 1156 } else { 1157 checkBoxesContainer = triggerElement.closest('.sg-cookie-optin-box'); 1158 } 1159 1160 // fallback to document if not found 1161 if (!checkBoxesContainer) { 1162 checkBoxesContainer = document; 1163 } 1164 const cookieList = checkBoxesContainer.querySelectorAll('.sg-cookie-optin-checkbox[value="' + groupName + '"]'); 1165 for (let index = 0; index < cookieList.length; ++index) { 1166 cookieList[index].checked = (statusMap[groupName] === 1); 1167 SgCookieOptin.handleCheckboxChange(cookieList[index]); 1168 } 1169 } 1170 }, 1171 1172 /** 1173 * Handles a change of the checkbox state 1174 * 1175 * @param {HTMLInputElement} checkbox 1176 */ 1177 handleCheckboxChange: function (checkbox) { 1178 checkbox.closest('.sg-cookie-optin-box-cookie-list-item') 1179 .querySelector('.sg-cookie-optin-checkbox-label') 1180 .setAttribute('aria-checked', checkbox.checked); 1181 }, 1182 1183 /** 1184 * Opens the cookie details box. 1185 * 1186 * @param {Event} event 1187 * @return {void} 1188 */ 1189 openCookieDetails: function(event) { 1190 event.preventDefault(); 1191 1192 let link = event.target; 1193 if (link.tagName !== 'A') { 1194 link = link.closest('a'); 1195 } 1196 1197 const openMoreElement = link.parentNode; 1198 const symbolElement = link.parentElement.querySelector('.sg-cookie-optin-box-sublist-open-more-symbol'); 1199 if (!openMoreElement) { 1200 return; 1201 } 1202 1203 const cookieDetailList = openMoreElement.previousElementSibling; 1204 if (!cookieDetailList) { 1205 return; 1206 } 1207 1208 if (cookieDetailList.classList.contains('sg-cookie-optin-visible')) { 1209 cookieDetailList.classList.remove('sg-cookie-optin-visible'); 1210 cookieDetailList.setAttribute('aria-hidden', 'true'); 1211 link.firstChild.textContent = SgCookieOptin.jsonData.textEntries.extend_box_link_text; 1212 if (symbolElement) { 1213 symbolElement.classList.remove('sg-cookie-optin-flipped'); 1214 } 1215 } else {
1216 cookieDetailList.classList.add('sg-cookie-optin-visible'); 1217 cookieDetailList.setAttribute('aria-hidden', 'false'); 1218 link.firstChild.textContent = SgCookieOptin.jsonData.textEntries.extend_box_link_text_close; 1219 if (symbolElement) { 1220 symbolElement.classList.add('sg-cookie-optin-flipped'); 1221 } 1222 } 1223 }, 1224 1225 /** 1226 * Opens the subList box. 1227 * 1228 * @param event 1229 * @param {Element} contentElement 1230 * @return {void} 1231 */ 1232 openSubList: function(event, contentElement) { 1233 event.preventDefault(); 1234 1235 let link = event.target; 1236 if (link.tagName !== 'A') { 1237 link = link.closest('a'); 1238 } 1239 1240 // todo remove redundant code. 1241 let height = 0; 1242 const cookieSubList = link.previousElementSibling; 1243 const symbolElement = link.parentElement.querySelector('.sg-cookie-optin-box-sublist-open-more-symbol'); 1244 if (!cookieSubList || !cookieSubList.classList.contains('sg-cookie-optin-box-cookie-detail-sublist')) { 1245 const cookieOptin = link.closest('#SgCookieOptin'); 1246 const cookieBoxes = cookieOptin.querySelectorAll('.sg-cookie-optin-box-new'); 1247 for (const index in cookieBoxes) { 1248 if (!cookieBoxes.hasOwnProperty(index)) { 1249 continue; 1250 } 1251 1252 let visible = true; 1253 const cookieBox = cookieBoxes[index]; 1254 if (cookieBox.classList.contains('sg-cookie-optin-visible')) { 1255 visible = false; 1256 cookieBox.classList.remove('sg-cookie-optin-visible'); 1257 cookieBox.setAttribute('aria-hidden', 'true'); 1258 cookieBox.classList.add('sg-cookie-optin-invisible'); 1259 link.firstChild.textContent = SgCookieOptin.jsonData.textEntries.extend_table_link_text; 1260 if (symbolElement) { 1261 symbolElement.classList.remove('sg-cookie-optin-flipped'); 1262 } 1263 } else { 1264 cookieBox.classList.remove('sg-cookie-optin-invisible'); 1265 cookieBox.classList.add('sg-cookie-optin-visible'); 1266 cookieBox.setAttribute('aria-hidden', 'false'); 1267 SgCookieOptin.adjustReasonHeight(cookieOptin, contentElement); 1268 link.firstChild.textContent = SgCookieOptin.jsonData.textEntries.extend_table_link_text_close; 1269 if (symbolElement) { 1270 symbolElement.classList.add('sg-cookie-optin-flipped'); 1271 } 1272 } 1273 1274 const descriptions = cookieBox.querySelectorAll('.sg-cookie-optin-checkbox-description'); 1275 for (const descriptionIndex in descriptions) { 1276 if (!descriptions.hasOwnProperty(descriptionIndex)) { 1277 continue; 1278 } 1279 1280 const description = descriptions[descriptionIndex]; 1281 if (visible) { 1282 description.setAttribute('data-optimal-height', description.style.height); 1283 description.style.height = 'auto'; 1284 } else { 1285 description.style.height = description.getAttribute('data-optimal-height'); 1286 } 1287 } 1288 } 1289 } else { 1290 if (cookieSubList.classList.contains('sg-cookie-optin-visible')) { 1291 cookieSubList.classList.remove('sg-cookie-optin-visible'); 1292 cookieSubList.setAttribute('aria-hidden', 'true'); 1293 cookieSubList.style.height = ''; 1294 link.firstChild.textContent = SgCookieOptin.jsonData.textEntries.extend_table_link_text; 1295 if (symbolElement) { 1296 symbolElement.classList.remove('sg-cookie-optin-flipped'); 1297 } 1298 } else { 1299 cookieSubList.classList.add('sg-cookie-optin-visible'); 1300 cookieSubList.setAttribute('aria-hidden', 'false'); 1301 cookieSubList.style.height = 'auto'; 1302 height = cookieSubList.getBoundingClientRect().height + 'px'; 1303 cookieSubList.style.height = ''; 1304 if (symbolElement) { 1305 symbolElement.classList.add('sg-cookie-optin-flipped'); 1306 } 1307 requestAnimationFrame(function() { 1308 setTimeout(function() { 1309 cookieSubList.style.height = height; 1310 }, 10); 1311 }); 1312 1313 link.firstChild.textContent = SgCookieOptin.jsonData.textEntries.extend_table_link_text_close; 1314 } 1315 } 1316 }, 1317 1318 /** 1319 * Accepts all cookies and saves them. 1320 * 1321 * @return {void} 1322 */ 1323 acceptAllCookies: function() { 1324 let cookieData = ''; 1325 for (const index in SgCookieOptin.jsonData.cookieGroups) { 1326 if (!SgCookieOptin.jsonData.cookieGroups.hasOwnProperty(index)) { 1327 continue; 1328 } 1329 1330 const groupName = SgCookieOptin.jsonData.cookieGroups[index]['groupName']; 1331 if (!groupName) { 1332 continue; 1333 } 1334 1335 if (cookieData.length > 0) { 1336 cookieData += '|'; 1337 } 1338 cookieData += groupName + ':' + 1; 1339 1340 if (groupName === SgCookieOptin.COOKIE_GROUP_EXTERNAL_CONTENT) { 1341 SgCookieOptin.acceptAllExternalContents(); 1342 } 1343 } 1344 SgCookieOptin.setCookieWrapper(cookieData); 1345 }, 1346 1347 /** 1348 * Accepts specific cookies and saves them. 1349 * 1350 * @param {HTMLElement} triggerElement 1351 * @return {void} 1352 */ 1353 acceptSpecificCookies: function(triggerElement) { 1354 let externalContentGroupFoundAndActive = false;
1355 let cookieData = ''; 1356 let checkBoxesContainer; 1357 if (!triggerElement) { 1358 checkBoxesContainer = document; 1359 } else { 1360 checkBoxesContainer = triggerElement.closest('.sg-cookie-optin-box'); 1361 } 1362 const checkboxes = checkBoxesContainer.querySelectorAll('.sg-cookie-optin-checkbox:checked'); 1363 for (const index in SgCookieOptin.jsonData.cookieGroups) { 1364 if (!SgCookieOptin.jsonData.cookieGroups.hasOwnProperty(index)) { 1365 continue; 1366 } 1367 1368 const groupName = SgCookieOptin.jsonData.cookieGroups[index]['groupName']; 1369 if (!groupName) { 1370 continue; 1371 } 1372 1373 let status = 0; 1374 for (let subIndex = 0; subIndex < checkboxes.length; ++subIndex) { 1375 if (checkboxes[subIndex].value === groupName) { 1376 status = 1; 1377 break; 1378 } 1379 } 1380 1381 if (groupName === SgCookieOptin.COOKIE_GROUP_EXTERNAL_CONTENT && status === 1) { 1382 externalContentGroupFoundAndActive = true; 1383 } 1384 1385 if (cookieData.length > 0) { 1386 cookieData += '|'; 1387 } 1388 cookieData += groupName + ':' + status; 1389 } 1390 1391 SgCookieOptin.setCookieWrapper(cookieData); 1392 1393 if (SgCookieOptin.jsonData.settings.iframe_enabled) { 1394 if (externalContentGroupFoundAndActive) { 1395 SgCookieOptin.acceptAllExternalContents(); 1396 } else { 1397 SgCookieOptin.checkForExternalContents(); 1398 } 1399 } 1400 }, 1401 1402 /** 1403 * Accepts essential cookies and saves them. 1404 * 1405 * @return {void} 1406 */ 1407 acceptEssentialCookies: function() { 1408 let cookieData = ''; 1409 for (const index in SgCookieOptin.jsonData.cookieGroups) { 1410 if (!SgCookieOptin.jsonData.cookieGroups.hasOwnProperty(index)) { 1411 continue; 1412 } 1413 1414 const groupName = SgCookieOptin.jsonData.cookieGroups[index]['groupName']; 1415 if (!groupName) { 1416 continue; 1417 } 1418 1419 let status = 0; 1420 if (SgCookieOptin.jsonData.cookieGroups[index]['required']) { 1421 status = 1; 1422 } 1423 1424 if (cookieData.length > 0) { 1425 cookieData += '|'; 1426 } 1427 cookieData += groupName + ':' + status; 1428 } 1429 1430 SgCookieOptin.setCookieWrapper(cookieData); 1431 }, 1432 1433 /** 1434 * Checks if external content elements are added to the dom and replaces them with a consent, if the cookie isn't accepted, or set. 1435 * 1436 * @return {void} 1437 */ 1438 checkForExternalContents: function() { 1439 if (!SgCookieOptin.jsonData.settings.iframe_enabled) { 1440 return; 1441 } 1442 1443 // noinspection EqualityComparisonWithCoercionJS 1444 const showOptIn = SgCookieOptin.getParameterByName('showOptIn') == true; 1445 if (SgCookieOptin.jsonData.settings.activate_testing_mode && !showOptIn) { 1446 return; 1447 } 1448 1449 if (SgCookieOptin.isExternalGroupAccepted) { 1450 return; 1451 } 1452 1453 if (!SgCookieOptin.externalContentObserver) { 1454 const externalContents = document.querySelectorAll(SgCookieOptin.EXTERNAL_CONTENT_ELEMENT_SELECTOR); 1455 if (externalContents.length > 0) { 1456 for (const externalContentIndex in externalContents) { 1457 if (!externalContents.hasOwnProperty(externalContentIndex)) { 1458 continue; 1459 } 1460 SgCookieOptin.replaceExternalContentWithConsent(externalContents[externalContentIndex]); 1461 } 1462 } 1463 } 1464 1465 // Create an observer instance linked to the callback function 1466 SgCookieOptin.externalContentObserver = new MutationObserver(function(mutationsList) { 1467 // Use traditional 'for loops' for IE 11 1468 for (const index in mutationsList) { 1469 if (!mutationsList.hasOwnProperty(index)) { 1470 continue; 1471 } 1472 1473 const mutation = mutationsList[index]; 1474 if (mutation.type !== 'childList' || mutation.addedNodes.length <= 0) { 1475 continue; 1476 } 1477 1478 for (const addedNodeIndex in mutation.addedNodes) { 1479 if (!mutation.addedNodes.hasOwnProperty(addedNodeIndex)) { 1480 continue; 1481 } 1482 1483 const addedNode = mutation.addedNodes[addedNodeIndex]; 1484 if (typeof addedNode.matches === 'function' && addedNode.matches(SgCookieOptin.EXTERNAL_CONTENT_ELEMENT_SELECTOR)) { 1485 SgCookieOptin.replaceExternalContentWithConsent(addedNode); 1486 } else if (addedNode.querySelectorAll && typeof addedNode.querySelectorAll === 'function') { 1487 // check if there is an external content in the subtree 1488 const externalContents = addedNode.querySelectorAll(SgCookieOptin.EXTERNAL_CONTENT_ELEMENT_SELECTOR); 1489 if (externalContents.length > 0) { 1490 for (const externalContentIndex in externalContents) { 1491 if (!externalContents.hasOwnProperty(externalContentIndex)) { 1492 continue; 1493 } 1494 SgCookieOptin.replaceExternalContentWithConsent(externalContents[externalContentIndex]); 1495 } 1496 } 1497 } 1498 } 1499 } 1500 }); 1501 1502 // Start observing the target node for configured mutations 1503 SgCookieOptin.externalContentObserver.observe(document, {subtree: true, childList: true}); 1504 }, 1505 1506 /** 1507 * Checks whether this element matches the rules in the whitelist 1508 * 1509 * @param {Element} externalContent 1510 * @return {boolean} 1511 */ 1512 isContentWhiteListed: function(externalContent) { 1513 if (!SgCookieOptin.jsonData.mustacheData.iframeWhitelist.markup.trim()) { 1514 return false;
1515 } 1516 1517 const regularExpressions = SgCookieOptin.jsonData.mustacheData.iframeWhitelist.markup.trim() 1518 .split(/\r?\n/).map(function(value) { 1519 return new RegExp(value); 1520 }); 1521 if (typeof regularExpressions === 'object' && regularExpressions.length < 1) { 1522 return false; 1523 } 1524 1525 switch (externalContent.tagName) { 1526 case 'IFRAME': 1527 return SgCookieOptin.matchRegexElementSource(externalContent, 'src', regularExpressions, true); 1528 case 'OBJECT': 1529 return SgCookieOptin.matchRegexElementSource(externalContent, 'data', regularExpressions, true); 1530 case 'VIDEO': 1531 case 'AUDIO': 1532 return SgCookieOptin.matchRegexAudioVideo(externalContent, regularExpressions, true); 1533 default: 1534 return false; 1535 } 1536 }, 1537 1538 /** 1539 * Tests whether a dom element attribute matches the given regular expressions 1540 * 1541 * @param {HTMLElement} externalContent 1542 * @param {string} attribute 1543 * @param {RegExp[]} regularExpressions 1544 * @param {boolean} checkForLocal 1545 * @return {boolean} 1546 */ 1547 matchRegexElementSource: function(externalContent, attribute, regularExpressions, checkForLocal) { 1548 if (typeof externalContent.getAttribute !== 'function') { 1549 return false; 1550 } 1551 1552 if (checkForLocal && SgCookieOptin.isUrlLocal(externalContent.dataset['contentReplaceSrc'] || externalContent.getAttribute(attribute))) { 1553 return true; 1554 } 1555 1556 for (const regExIndex in regularExpressions) { 1557 if (typeof externalContent.getAttribute === 'function' && regularExpressions[regExIndex].test(externalContent.getAttribute(attribute))) { 1558 return true; 1559 } 1560 } 1561 return false; 1562 }, 1563 1564 /** 1565 * Tests if an audio or video element's source matches the given regular expressions 1566 * 1567 * @param {Element} externalContent 1568 * @param {RegExp[]} regularExpressions 1569 * @param {boolean} checkForLocal 1570 * @return {boolean} 1571 */ 1572 matchRegexAudioVideo: function(externalContent, regularExpressions, checkForLocal) { 1573 if (externalContent.hasAttribute('src')) { 1574 return SgCookieOptin.matchRegexElementSource(externalContent, 'src', regularExpressions, checkForLocal); 1575 } 1576 1577 const sources = externalContent.querySelectorAll('source'); 1578 let foundMatches = false; 1579 for (const sourceIndex in sources) { 1580 if (!sources.hasOwnProperty(sourceIndex)) { 1581 continue; 1582 } 1583 1584 if (!sources[sourceIndex].getAttribute('src')) { 1585 continue; 1586 } 1587 1588 // noinspection JSUnfilteredForInLoop 1589 if (!SgCookieOptin.matchRegexElementSource(sources[sourceIndex], 'src', regularExpressions, checkForLocal)) { 1590 foundMatches = true; 1591 } 1592 } 1593 1594 return !foundMatches; 1595 }, 1596 1597 /** 1598 * Checks whether the given element is in a container that is allowed to always render external content 1599 * @param {HTMLElement} externalContent 1600 * @returns {boolean} 1601 */ 1602 isElementInAllowedNode: function(externalContent) { 1603 const potentialParents = document.querySelectorAll(SgCookieOptin.EXTERNAL_CONTENT_ALLOWED_ELEMENT_SELECTOR); 1604 for (let i in potentialParents) { 1605 if (typeof potentialParents[i].contains === 'function' && potentialParents[i].contains(externalContent)) { 1606 return true; 1607 } 1608 } 1609 return false; 1610 }, 1611 1612 /** 1613 * Checks if the given URL is a local or relative path 1614 * @param {string} url 1615 */ 1616 isUrlLocal: function(url) { 1617 const tempA = document.createElement('a'); 1618 tempA.setAttribute('href', url); 1619 return window.location.protocol === tempA.protocol && window.location.host === tempA.host; 1620 }, 1621 1622 /** 1623 * Adds a consent for externalContents, which needs to be accepted. 1624 * 1625 * @param {HTMLElement} externalContent 1626 * 1627 * @return {void} 1628 */ 1629 replaceExternalContentWithConsent: function(externalContent) { 1630 // Skip allowed elements and whitelisted sources 1631 // noinspection EqualityComparisonWithCoercionJS 1632 if (externalContent.matches(SgCookieOptin.EXTERNAL_CONTENT_ALLOWED_ELEMENT_SELECTOR) 1633 || SgCookieOptin.isElementInAllowedNode(externalContent) 1634 || SgCookieOptin.isContentWhiteListed(externalContent)) { 1635 return; 1636 } 1637 1638 // Skip detached elements 1639 const parent = externalContent.parentElement; 1640 if (!parent) { 1641 return; 1642 } 1643 1644 const src = externalContent.dataset['contentReplaceSrc'] || externalContent.src; 1645 // Skip iframes with no source 1646 if (externalContent.tagName === 'IFRAME' 1647 && (!src || externalContent.src.indexOf('chrome-extension') >= 0)) { 1648 return; 1649 } 1650 1651 // Get the position of the element within its parent 1652 let positionIndex = 0; 1653 let child = externalContent; 1654 while ((child = child.previousSibling) != null) { 1655 positionIndex++; 1656 } 1657 externalContent.setAttribute('data-iframe-position-index', positionIndex.toString()); 1658 1659 // Got problems with the zero. 1660 let externalContentId = SgCookieOptin.protectedExternalContents.length + 1; 1661 if (externalContentId === 0) { 1662 externalContentId = 1; 1663 } 1664 SgCookieOptin.protectedExternalContents[externalContentId] = externalContent; 1665
1666 // Create the external content consent box DIV container 1667 const container = document.createElement('DIV'); 1668 container.setAttribute('data-iframe-id', externalContentId.toString()); 1669 container.setAttribute('data-content-replace-src', src); 1670 container.setAttribute('style', 'height: ' + externalContent.offsetHeight + 'px;'); 1671 container.classList.add('sg-cookie-optin-iframe-consent'); 1672 SgCookieOptin.insertExternalContentReplacementHTML(externalContent, container); 1673 1674 // Add event Listeners to the consent buttons 1675 const externalContentConsentAccept = container.querySelectorAll('.sg-cookie-optin-iframe-consent-accept'); 1676 SgCookieOptin.addEventListenerToList(externalContentConsentAccept, 'click', function() { 1677 SgCookieOptin.acceptExternalContent(externalContentId) 1678 }); 1679 1680 // Set custom accept text if available 1681 if (externalContent.getAttribute('data-consent-button-text')) { 1682 for (let acceptButtonIndex = 0; acceptButtonIndex < externalContentConsentAccept.length; ++acceptButtonIndex) { 1683 externalContentConsentAccept[acceptButtonIndex].innerText = externalContent.getAttribute('data-consent-button-text'); 1684 } 1685 } 1686 1687 SgCookieOptin.setExternalContentDescriptionText(container, externalContent); 1688 1689 const externalContentConsentLink = container.querySelectorAll('.sg-cookie-optin-iframe-consent-link'); 1690 SgCookieOptin.addEventListenerToList(externalContentConsentLink, 'click', SgCookieOptin.openExternalContentConsent); 1691 1692 // Replace the element 1693 if (parent.childNodes.length > 0) { 1694 parent.insertBefore(container, parent.childNodes[positionIndex]); 1695 } else { 1696 parent.appendChild(container); 1697 } 1698 1699 // Accept child nodes as well when it is accepted 1700 externalContent.addEventListener('externalContentAccepted', function() { 1701 SgCookieOptin.acceptContainerChildNodes(externalContent); 1702 return true; 1703 }); 1704 1705 // Because of the IE11 no .remove(); 1706 parent.removeChild(externalContent); 1707 1708 // Emit event for replaced content 1709 const externalContentReplacedEvent = new CustomEvent('externalContentReplaced', { 1710 bubbles: true, 1711 detail: { 1712 positionIndex: positionIndex, 1713 parent: parent, 1714 externalContent: externalContent, 1715 container: container 1716 } 1717 }); 1718 container.dispatchEvent(externalContentReplacedEvent); 1719 }, 1720 1721 /** 1722 * Inserts the external content replacement HTML 1723 * 1724 * @param {HTMLElement} externalContent 1725 * @param {HTMLElement} container 1726 */ 1727 insertExternalContentReplacementHTML: function(externalContent, container) { 1728 let serviceUsed = false; 1729 let service = null; 1730 1731 // check if the service template is explicitly set 1732 if (typeof externalContent.dataset.sgCookieOptinService !== 'undefined') { 1733 service = SgCookieOptin.jsonData.mustacheData.services[ 1734 externalContent.dataset.sgCookieOptinService 1735 ]; 1736 1737 if (service) { 1738 serviceUsed = true; 1739 SgCookieOptin.emitBeforeExternalContentReplacedEvent(parent, externalContent, container, service); 1740 container.insertAdjacentHTML('afterbegin', service.rendered); 1741 } else { 1742 console.log('Sg Cookie Consent: Template ' + externalContent.dataset.sgCookieOptinReplacementTemplate 1743 + ' not found!'); 1744 } 1745 } else { 1746 // try to match service regex 1747 if (typeof SgCookieOptin.jsonData.mustacheData.services !== 'undefined') { 1748 for (const serviceIndex in SgCookieOptin.jsonData.mustacheData.services) { 1749 if (!SgCookieOptin.jsonData.mustacheData.services.hasOwnProperty(serviceIndex)) { 1750 continue; 1751 } 1752 1753 if (SgCookieOptin.jsonData.mustacheData.services[serviceIndex].regex.trim() === '') { 1754 continue; 1755 } 1756 1757 const regularExpressions = SgCookieOptin.jsonData.mustacheData.services[serviceIndex].regex.trim() 1758 .split(/\r?\n/).map(function(value) { 1759 return new RegExp(value); 1760 }); 1761 1762 let serviceMatched = false; 1763 if (typeof regularExpressions === 'object' && regularExpressions.length > 0) { 1764 switch (externalContent.tagName) { 1765 case 'IFRAME': 1766 serviceMatched = SgCookieOptin.matchRegexElementSource(externalContent, 'src', regularExpressions, false); 1767 break; 1768 case 'OBJECT': 1769 serviceMatched = SgCookieOptin.matchRegexElementSource(externalContent, 'data', regularExpressions, false); 1770 break; 1771 case 'VIDEO': 1772 case 'AUDIO': 1773 serviceMatched = SgCookieOptin.matchRegexAudioVideo(externalContent, regularExpressions, false); 1774 break; 1775 } 1776 } 1777 1778 if (serviceMatched) {
1779 serviceUsed = true; 1780 service = SgCookieOptin.jsonData.mustacheData.services[serviceIndex]; 1781 SgCookieOptin.emitBeforeExternalContentReplacedEvent(parent, externalContent, container, service); 1782 container.insertAdjacentHTML('afterbegin', service.rendered); 1783 break; 1784 } 1785 1786 } 1787 } 1788 } 1789 1790 // No service matched - use the default template and pass undefined as the service argument 1791 if (!serviceUsed) { 1792 SgCookieOptin.emitBeforeExternalContentReplacedEvent(parent, externalContent, container, service); 1793 container.insertAdjacentHTML('afterbegin', SgCookieOptin.jsonData.mustacheData.iframeReplacement.markup); 1794 } 1795 1796 // Set the backgrond image 1797 let backgroundImage; 1798 if (typeof externalContent.dataset.sgCookieOptinBackgroundImage !== 'undefined') { 1799 backgroundImage = externalContent.dataset.sgCookieOptinBackgroundImage; 1800 } else if (service && service.replacement_background_image !== '') { 1801 backgroundImage = service.replacement_background_image; 1802 } else if (typeof SgCookieOptin.jsonData.settings.iframe_replacement_background_image !== 'undefined' 1803 && SgCookieOptin.jsonData.settings.iframe_replacement_background_image !== '') { 1804 backgroundImage = SgCookieOptin.jsonData.settings.iframe_replacement_background_image; 1805 } 1806 1807 if (backgroundImage) { 1808 container.style.backgroundImage = 'url(' + backgroundImage + ')'; 1809 container.style.backgroundSize = 'cover'; 1810 } 1811 }, 1812 1813 /** 1814 * Emits the event that takes place right before we replace the external content with the replacement 1815 * 1816 * @param {Object} parent 1817 * @param {HTMLElement} externalContent 1818 * @param {HTMLElement} container 1819 * @param {null|Object} service 1820 */ 1821 emitBeforeExternalContentReplacedEvent: function(parent, externalContent, container, service) { 1822 // Emit event for replaced content 1823 const beforeExternalContentReplaced = new CustomEvent('beforeExternalContentReplaced', { 1824 bubbles: true, 1825 detail: { 1826 parent: parent, 1827 externalContent: externalContent, 1828 container: container, 1829 service: service 1830 } 1831 }); 1832 document.dispatchEvent(beforeExternalContentReplaced); 1833 }, 1834 1835 /** 1836 * Sets external content description text bellow the button and in the settings description 1837 * @param {HTMLElement} wrapper 1838 * @param {HTMLElement} externalContent 1839 */ 1840 setExternalContentDescriptionText: function(wrapper, externalContent) { 1841 const flashMessageContainer = wrapper.querySelector('.sg-cookie-optin-box-flash-message'); 1842 if (flashMessageContainer !== null) { 1843 let flashMessageText = externalContent.getAttribute('data-consent-description'); 1844 // fallback to default if no data attribute has been set 1845 if (!flashMessageText) { 1846 flashMessageText = SgCookieOptin.jsonData.textEntries.iframe_button_load_one_description; 1847 } 1848 1849 if (flashMessageText) { 1850 flashMessageContainer.appendChild(document.createTextNode(flashMessageText)); 1851 } else { 1852 flashMessageContainer.remove(); 1853 } 1854 } 1855 }, 1856 1857 /** 1858 * Adds a consent for iFrames, which needs to be accepted. 1859 * 1860 * @return {void} 1861 */ 1862 openExternalContentConsent: function() { 1863 const parent = this.parentElement; 1864 if (!parent) { 1865 return; 1866 } 1867 1868 const externalContentId = parent.getAttribute('data-iframe-id'); 1869 if (!externalContentId) { 1870 return; 1871 } 1872 1873 SgCookieOptin.lastOpenedExternalContentId = externalContentId; 1874 const externalContent = SgCookieOptin.protectedExternalContents[externalContentId]; 1875 if (!externalContent) { 1876 return; 1877 } 1878 1879 const wrapper = document.createElement('DIV'); 1880 wrapper.id = 'SgCookieOptin'; 1881 wrapper.insertAdjacentHTML('afterbegin', SgCookieOptin.jsonData.mustacheData.iframe.markup); 1882 1883 SgCookieOptin.setExternalContentDescriptionText(wrapper, externalContent); 1884 SgCookieOptin.addExternalContentListeners(wrapper); 1885 1886 document.body.insertAdjacentElement('afterbegin', wrapper); 1887 1888 // focus the first button for better accessability 1889 const buttons = document.getElementsByClassName('sg-cookie-optin-box-button-accept-all'); 1890 if (buttons.length > 0) { 1891 if (buttons[0].focus) { 1892 buttons[0].focus(); 1893 } 1894 } 1895 1896 // Emit event 1897 const externalContentConsentDisplayedEvent = new CustomEvent('externalContentConsentDisplayed', { 1898 bubbles: true, 1899 detail: {} 1900 }); 1901 wrapper.dispatchEvent(externalContentConsentDisplayedEvent); 1902 }, 1903 1904 /** 1905 * Adds the listeners to the given element. 1906 * 1907 * @param {dom} element 1908 * 1909 * @return {void} 1910 */ 1911 addExternalContentListeners: function(element) { 1912 const closeButtons = element.querySelectorAll('.sg-cookie-optin-box-close-button'); 1913 SgCookieOptin.addEventListenerToList(closeButtons, 'click', SgCookieOptin.hideCookieOptIn); 1914 1915 const rejectButtons = element.querySelectorAll('.sg-cookie-optin-box-button-iframe-reject'); 1916 SgCookieOptin.addEventListenerToList(rejectButtons, 'click', SgCookieOptin.hideCookieOptIn); 1917 1918 const acceptAllButtons = element.querySelectorAll('.sg-cookie-optin-box-button-accept-all'); 1919 SgCookieOptin.addEventListenerToList(acceptAllButtons, 'click', function() { 1920 SgCookieOptin.acceptAllExternalContents(); 1921 SgCookieOptin.acceptExternalConentGroup(); 1922 SgCookieOptin.updateCookieList(); 1923 SgCookieOptin.handleScriptActivations(); 1924 SgCookieOptin.hideCookieOptIn(); 1925 }); 1926 1927 const acceptSpecificButtons = element.querySelectorAll('.sg-cookie-optin-box-button-accept-specific'); 1928 SgCookieOptin.addEventListenerToList(acceptSpecificButtons, 'click', function() { 1929 SgCookieOptin.acceptExternalContent(SgCookieOptin.lastOpenedExternalContentId); 1930 SgCookieOptin.updateCookieList(); 1931 SgCookieOptin.handleScriptActivations(); 1932 SgCookieOptin.hideCookieOptIn(); 1933 }); 1934 }, 1935 1936 /**
1937 * Replaces all external content consent containers with the corresponding external content and adapts the cookie for further requests. 1938 * 1939 * @return {void} 1940 */ 1941 acceptAllExternalContents: function() { 1942 if (SgCookieOptin.jsonData.settings.iframe_enabled && SgCookieOptin.externalContentObserver) { 1943 SgCookieOptin.externalContentObserver.disconnect(); 1944 } 1945 1946 for (let index in SgCookieOptin.protectedExternalContents) { 1947 index = parseInt(index); 1948 if (!document.querySelector('div[data-iframe-id="' + index + '"]')) { 1949 continue; 1950 } 1951 1952 SgCookieOptin.acceptExternalContent(index) 1953 } 1954 }, 1955 1956 /** 1957 * Accepts the external content group and updates the cookie value if needed 1958 * 1959 * @return {void} 1960 */ 1961 acceptExternalConentGroup: function() { 1962 const cookieValue = SgCookieOptin.getCookie(SgCookieOptin.COOKIE_NAME); 1963 1964 let groupFound = false; 1965 let newCookieValue = ''; 1966 const splitedCookieValue = cookieValue.split('|'); 1967 for (const splitedCookieValueIndex in splitedCookieValue) { 1968 if (!splitedCookieValue.hasOwnProperty(splitedCookieValueIndex)) { 1969 continue; 1970 } 1971 1972 const splitedCookieValueEntry = splitedCookieValue[splitedCookieValueIndex]; 1973 const groupAndStatus = splitedCookieValueEntry.split(':'); 1974 if (!groupAndStatus.hasOwnProperty(0) || !groupAndStatus.hasOwnProperty(1)) { 1975 continue; 1976 } 1977 1978 const group = groupAndStatus[0]; 1979 let status = parseInt(groupAndStatus[1]); 1980 if (group === SgCookieOptin.COOKIE_GROUP_EXTERNAL_CONTENT) { 1981 groupFound = true; 1982 status = 1; 1983 } 1984 1985 if (newCookieValue.length > 0) { 1986 newCookieValue += '|'; 1987 } 1988 newCookieValue += group + ':' + status; 1989 } 1990 1991 if (!groupFound) { 1992 newCookieValue += '|' + SgCookieOptin.COOKIE_GROUP_EXTERNAL_CONTENT + ':' + 1; 1993 } 1994 1995 if (cookieValue !== newCookieValue) { 1996 SgCookieOptin.setCookieWrapper(newCookieValue); 1997 } 1998 }, 1999 2000 /** 2001 * Replaces an external content consent container with the external content element. 2002 * 2003 * @param {string} externalContentId 2004 * @return {void} 2005 */ 2006 acceptExternalContent: function(externalContentId) { 2007 if (!externalContentId) { 2008 externalContentId = parent.getAttribute('data-iframe-id'); 2009 if (!externalContentId) { 2010 return; 2011 } 2012 } 2013 2014 const container = document.querySelector('div[data-iframe-id="' + externalContentId + '"]'); 2015 const externalContent = SgCookieOptin.protectedExternalContents[externalContentId]; 2016 if (!externalContent || !container) { 2017 return; 2018 } 2019 2020 externalContent.setAttribute('data-iframe-allow-always', 1); 2021 const positionIndex = externalContent.getAttribute('data-iframe-position-index'); 2022 externalContent.removeAttribute('data-iframe-position-index'); 2023 2024 // Because of the IE11 no .replaceWith(); 2025 const parentNode = container.parentNode; 2026 parentNode.removeChild(container); 2027 if (parentNode.childNodes.length > 0) { 2028 parentNode.insertBefore(externalContent, parentNode.childNodes[positionIndex]); 2029 } else { 2030 parentNode.appendChild(externalContent); 2031 } 2032 2033 SgCookieOptin.fixExternalContentAttributes(externalContent); 2034 SgCookieOptin.emitExternalContentAcceptedEvent(externalContent); 2035 }, 2036 2037 /** 2038 * Emit event when the external content element has been accepted 2039 * @param {HTMLElement} externalContent 2040 */ 2041 emitExternalContentAcceptedEvent: function(externalContent) { 2042 const externalContentAcceptedEvent = new CustomEvent('externalContentAccepted', { 2043 bubbles: true, 2044 detail: { 2045 externalContent: externalContent 2046 } 2047 }); 2048 externalContent.dispatchEvent(externalContentAcceptedEvent); 2049 }, 2050 2051 /** 2052 * Accept the external contents nested into the given container 2053 * 2054 * @param {HTMLElement} container 2055 */ 2056 acceptContainerChildNodes: function(container) { 2057 const replacedChildren = container.querySelectorAll('[data-iframe-id]'); 2058 if (replacedChildren.length > 0) { 2059 for (const childIndex in replacedChildren) { 2060 const childElement = replacedChildren[childIndex]; 2061 if (typeof childElement.getAttribute !== 'function') { 2062 continue; 2063 } 2064 const externalContentId = childElement.getAttribute('data-iframe-id'); 2065 if (!externalContentId) { 2066 continue; 2067 } 2068 SgCookieOptin.acceptExternalContent(externalContentId); 2069 } 2070 } 2071 }, 2072 2073 /** 2074 * todo Optimize the cookie handling with hasSetting, addSetting, removeSetting and use it everywhere. 2075 * 2076 * Returns the cookie, found with the given name, or null. 2077 * 2078 * @param {string} name 2079 * @return {string} 2080 */ 2081 getCookie: function(name) { 2082 const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); 2083 return v ? v[2] : null; 2084 }, 2085 2086 /** 2087 * Sets the given cookie with the given value for X days. 2088 * 2089 * @param {string} name 2090 * @param {string} value 2091 * @param {string} days 2092 */
2093 setCookie: function(name, value, days) { 2094 const d = new Date; 2095 d.setTime(d.getTime() + 24 * 60 * 60 * 1000 * days); 2096 let cookie = name + '=' + value + ';path=/'; 2097 2098 const currentHost = window.location.hostname; 2099 const cookieStringEnd = ';expires=' + d.toUTCString() + '; SameSite=None; Secure'; 2100 2101 if (SgCookieOptin.jsonData.settings.set_cookie_for_domain && SgCookieOptin.jsonData.settings.set_cookie_for_domain.length > 0) { 2102 cookie += ';domain=' + SgCookieOptin.jsonData.settings.set_cookie_for_domain; 2103 } else if (SgCookieOptin.jsonData.settings.subdomain_support) { 2104 const domainParts = currentHost.split('.'); 2105 if (domainParts.length > 2) { 2106 domainParts.shift(); 2107 const hostnameToFirstDot = '.' + domainParts.join('.'); 2108 cookie += ';domain=' + hostnameToFirstDot; 2109 } 2110 } 2111 2112 document.cookie = cookie + cookieStringEnd; 2113 }, 2114 2115 /** 2116 * Sets the given cookie with the given value only for the current session. 2117 * 2118 * @param {string} name 2119 * @param {string} value 2120 */ 2121 setSessionCookie: function(name, value) { 2122 let cookie = name + '=' + value + '; path=/'; 2123 2124 const currentHost = window.location.hostname; 2125 const cookieStringEnd = ';SameSite=None; Secure'; 2126 2127 if (SgCookieOptin.jsonData.settings.set_cookie_for_domain && SgCookieOptin.jsonData.settings.set_cookie_for_domain.length > 0) { 2128 cookie += ';domain=' + SgCookieOptin.jsonData.settings.set_cookie_for_domain; 2129 } else if (SgCookieOptin.jsonData.settings.subdomain_support) { 2130 const domainParts = currentHost.split('.'); 2131 if (domainParts.length > 2) { 2132 domainParts.shift(); 2133 const hostnameToFirstDot = '.' + domainParts.join('.'); 2134 cookie += ';domain=' + hostnameToFirstDot; 2135 } 2136 } 2137 2138 document.cookie = cookie + cookieStringEnd; 2139 }, 2140 2141 /** 2142 * Cookie is set with lifetime if the user has accepted a non-essential group that exists. 2143 * 2144 * @param {string} cookieValue 2145 */ 2146 setCookieWrapper: function(cookieValue) { 2147 let setCookieForSessionOnly = false; 2148 if (SgCookieOptin.jsonData.settings.session_only_essential_cookies) { 2149 let hasNonEssentialGroups = false; 2150 let hasAcceptedNonEssentials = false; 2151 const splitCookieValue = cookieValue.split('|'); 2152 for (const cookieValueIndex in splitCookieValue) { 2153 if (!splitCookieValue.hasOwnProperty(cookieValueIndex)) { 2154 continue; 2155 } 2156 2157 const valueEntry = splitCookieValue[cookieValueIndex]; 2158 if (valueEntry.indexOf(SgCookieOptin.COOKIE_GROUP_ESSENTIAL) === 0 || valueEntry.indexOf(SgCookieOptin.COOKIE_GROUP_EXTERNAL_CONTENT) === 0) { 2159 continue; 2160 } 2161 2162 hasNonEssentialGroups = true; 2163 if (valueEntry.indexOf(':1') > 0) { 2164 hasAcceptedNonEssentials = true; 2165 break; 2166 } 2167 } 2168 2169 setCookieForSessionOnly = hasNonEssentialGroups && !(hasNonEssentialGroups && hasAcceptedNonEssentials); 2170 } 2171 2172 if (setCookieForSessionOnly) { 2173 SgCookieOptin.setSessionCookie(SgCookieOptin.COOKIE_NAME, cookieValue); 2174 } else { 2175 SgCookieOptin.setCookie(SgCookieOptin.COOKIE_NAME, cookieValue, SgCookieOptin.jsonData.settings.cookie_lifetime); 2176 } 2177 2178 SgCookieOptin.saveLastPreferences(cookieValue); 2179 SgCookieOptin.deleteCookiesForUnsetGroups(); 2180 2181 // Emit event 2182 const consentCookieSetEvent = new CustomEvent('consentCookieSet', { 2183 bubbles: true, 2184 detail: { 2185 cookieValue: cookieValue 2186 } 2187 }); 2188 document.body.dispatchEvent(consentCookieSetEvent); 2189 }, 2190 2191 /** 2192 * Displays a notification as a box as first element in the given contentElement 2193 * 2194 * @param {Element} contentElement 2195 */ 2196 showSaveConfirmation: function(contentElement) { 2197 const oldNotification = contentElement.firstChild; 2198 if (!oldNotification.classList.contains('sg-cookie-optin-save-confirmation')) { 2199 const notification = document.createElement('DIV'); 2200 notification.classList.add('sg-cookie-optin-save-confirmation'); 2201 notification.insertAdjacentText('afterbegin', SgCookieOptin.jsonData.textEntries.save_confirmation_text); 2202 contentElement.insertBefore(notification, contentElement.firstChild); 2203 } 2204 }, 2205 2206 /** 2207 * Returns the value of a query parameter as a string, or null on error. 2208 * 2209 * @param {string} name 2210 * @param {string} url 2211 * @return {string|null} 2212 */ 2213 getParameterByName: function(name, url) { 2214 if (!url) { 2215 url = window.location.href; 2216 } 2217 2218 name = name.replace(/[\[\]]/g, '\\$&');
2219 const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'), 2220 results = regex.exec(url); 2221 if (!results) { 2222 return null; 2223 } 2224 2225 if (!results[2]) { 2226 return ''; 2227 } 2228 2229 return decodeURIComponent(results[2].replace(/\+/g, ' ')); 2230 }, 2231 2232 /** 2233 * Returns all the protected and unaccepted elements that match the given selector 2234 * 2235 * @param {string} selector 2236 * @returns {[]} 2237 */ 2238 findProtectedElementsBySelector: function(selector) { 2239 const foundElements = []; 2240 for (const elementIndex in SgCookieOptin.protectedExternalContents) { 2241 if (typeof SgCookieOptin.protectedExternalContents[elementIndex].matches === 'function' 2242 && SgCookieOptin.protectedExternalContents[elementIndex].matches(selector)) { 2243 foundElements.push(SgCookieOptin.protectedExternalContents[elementIndex]); 2244 } 2245 } 2246 return foundElements; 2247 }, 2248 2249 /** 2250 * Adds a trigger function to all protected elements that will be fired when the respective element has been granted 2251 * consent. You may filter the elements by a CSS selector. 2252 * 2253 * @param {function} callback 2254 * @param {string} selector 2255 */ 2256 addAcceptHandlerToProtectedElements: function(callback, selector) { 2257 if (typeof callback !== 'function') { 2258 throw new Error('Required argument "callback" has not been passed.'); 2259 } 2260 2261 if (!selector) { 2262 selector = '*'; 2263 } 2264 2265 const elements = SgCookieOptin.findProtectedElementsBySelector(selector); 2266 if (elements.length > 0) { 2267 SgCookieOptin.addEventListenerToList(elements, 'externalContentAccepted', callback); 2268 } else { 2269 // workaround for when the external group has been accepted and we have no protected elements 2270 if (SgCookieOptin.isExternalGroupAccepted) { 2271 SgCookieOptin.triggerAcceptedEventListenerToExternalContentElements(callback, selector); 2272 } 2273 } 2274 }, 2275 2276 /** 2277 * Filters the elements that would have been replaced by the external protection by the given selector and triggers 2278 * the ExternalContentAccepted Event for each of the elements found 2279 * 2280 * @param {function} callback 2281 * @param {string} selector 2282 */ 2283 triggerAcceptedEventListenerToExternalContentElements: function(callback, selector) { 2284 let index; 2285 const elements = []; 2286 const externalContentElements = document.querySelectorAll(SgCookieOptin.EXTERNAL_CONTENT_ELEMENT_SELECTOR); 2287 for (index = 0; index < externalContentElements.length; ++index) { 2288 if (typeof externalContentElements[index].matches === 'function' && externalContentElements[index].matches(selector)) { 2289 elements.push(externalContentElements[index]); 2290 } 2291 } 2292 2293 if (elements.length > 0) { 2294 // add the listener with the same callback to keep the same API 2295 SgCookieOptin.addEventListenerToList(elements, 'externalContentAccepted', callback); 2296 // trigger the event immediately 2297 for (index = 0; index < elements.length; ++index) { 2298 SgCookieOptin.emitExternalContentAcceptedEvent(elements[index]); 2299 } 2300 } 2301 }, 2302 2303 /** 2304 * Polyfill for the Element.matches and Element.closest 2305 */ 2306 closestPolyfill: function() { 2307 const ElementPrototype = window.Element.prototype; 2308 2309 if (typeof ElementPrototype.matches !== 'function') { 2310 ElementPrototype.matches = ElementPrototype.msMatchesSelector || ElementPrototype.mozMatchesSelector || ElementPrototype.webkitMatchesSelector || function matches(selector) { 2311 const element = this; 2312 const elements = (element.document || element.ownerDocument).querySelectorAll(selector); 2313 let index = 0; 2314 2315 while (elements[index] && elements[index] !== element) { 2316 ++index; 2317 } 2318 2319 return Boolean(elements[index]); 2320 }; 2321 } 2322 2323 if (typeof ElementPrototype.closest !== 'function') { 2324 ElementPrototype.closest = function closest(selector) { 2325 let element = this; 2326 2327 while (element && element.nodeType === 1) { 2328 if (element.matches(selector)) { 2329 return element; 2330 } 2331 2332 element = element.parentNode; 2333 } 2334 2335 return null; 2336 }; 2337 } 2338 }, 2339 2340 /** 2341 * Adds the Polyfill for the Window.CustomEvent 2342 * @returns {boolean} 2343 */ 2344 customEventPolyfill: function() { 2345 if (typeof window.CustomEvent === "function") { 2346 return false; 2347 } 2348 2349 function CustomEvent(event, params) { 2350 params = params || {bubbles: false, cancelable: false, detail: null}; 2351 const evt = document.createEvent('CustomEvent'); 2352 evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); 2353 return evt; 2354 } 2355 2356 window.CustomEvent = CustomEvent; 2357 }, 2358 2359 /** 2360 * Get the last preferences object in the local storage 2361 * 2362 * @return {Object}|undefined 2363 */ 2364 getLastPreferences: function() { 2365 let lastPreferences; 2366 if (SgCookieOptin.lastPreferencesFromCookie()) { 2367 lastPreferences = SgCookieOptin.getCookie(SgCookieOptin.LAST_PREFERENCES_COOKIE_NAME); 2368 } else { 2369 lastPreferences = window.localStorage.getItem(SgCookieOptin.LAST_PREFERENCES_LOCAL_STORAGE_NAME); 2370 } 2371 2372 if (!lastPreferences) { 2373 return {}; 2374 } 2375 2376 try { 2377 lastPreferences = JSON.parse(lastPreferences); 2378 return lastPreferences; 2379 } catch (e) { // we don't want to break the rest of the code if the JSON is malformed for some reason 2380 return {}; 2381 } 2382 }, 2383 2384 /** 2385 * Replaces the data- attribute placeholders for source URLs with actual attributes depending on the element type 2386 * 2387 * @param {HTMLElement}externalContent 2388 * @return {void} 2389 */ 2390 fixExternalContentAttributes: function(externalContent) { 2391 switch (externalContent.tagName) { 2392 case 'IFRAME': 2393 return SgCookieOptin.fixExternalContentAttribute(externalContent, 'src'); 2394 case 'OBJECT': 2395 return SgCookieOptin.fixExternalContentAttribute(externalContent, 'data'); 2396 default: 2397 return; 2398 } 2399 }, 2400 2401 /** 2402 * Replaces the data-src and data-data with actual src and data attributes 2403 * 2404 * @param {HTMLElement} externalContent 2405 * @param {string} attribute 2406 */ 2407 fixExternalContentAttribute: function(externalContent, attribute) { 2408 if (externalContent.dataset['contentReplaceSrc']) { 2409 externalContent.setAttribute(attribute, externalContent.dataset['contentReplaceSrc']); 2410 } 2411 }, 2412 2413 /**
2414 * Checks if there are any external contents with data-src attributes and sets their real attributes to the 2415 * data-attribute value 2416 */ 2417 checkForDataAttributeExternalContents: function() { 2418 if (SgCookieOptin.jsonData.settings.iframe_enabled && SgCookieOptin.isExternalGroupAccepted) { 2419 const externalContents = document.querySelectorAll(SgCookieOptin.EXTERNAL_CONTENT_ELEMENT_SELECTOR); 2420 if (externalContents.length > 0) { 2421 for (const externalContentToFixIndex in externalContents) { 2422 if (!externalContents.hasOwnProperty(externalContentToFixIndex)) { 2423 continue; 2424 } 2425 2426 SgCookieOptin.fixExternalContentAttributes(externalContents[externalContentToFixIndex]); 2427 } 2428 } 2429 } 2430 }, 2431 2432 /** 2433 * Shows the fingerprint and creates the element if necessary 2434 */ 2435 showFingerprint: function() { 2436 // noinspection EqualityComparisonWithCoercionJS 2437 // checkbox and position must be set properly 2438 if (!(SgCookieOptin.jsonData.settings.show_fingerprint 2439 && SgCookieOptin.jsonData.settings.fingerprint_position > 0)) { 2440 return; 2441 } 2442 2443 if (SgCookieOptin.fingerprintIcon === null) { 2444 SgCookieOptin.fingerprintIcon = SgCookieOptin.createFingeprint(); 2445 } 2446 2447 SgCookieOptin.fingerprintIcon.style.display = 'block'; 2448 }, 2449 2450 /** 2451 * Hides the fingerprint 2452 */ 2453 hideFingerprint: function() { 2454 if (SgCookieOptin.fingerprintIcon === null || !SgCookieOptin.shouldShowOptinBanner()) { 2455 return; 2456 } 2457 2458 SgCookieOptin.fingerprintIcon.style.display = 'none'; 2459 }, 2460 2461 /** 2462 * Creates the fingerprint HTML element 2463 * @return {HTMLDivElement} 2464 */ 2465 createFingeprint: function() { 2466 const fingerprintContainer = document.createElement('div'); 2467 let iconPositionClass = ''; 2468 2469 switch (SgCookieOptin.jsonData.settings.fingerprint_position) { 2470 case 1: 2471 iconPositionClass = 'bottom-left'; 2472 break; 2473 case 2: 2474 iconPositionClass = 'bottom-right'; 2475 break; 2476 case 3: 2477 iconPositionClass = 'top-left'; 2478 break; 2479 case 4: 2480 iconPositionClass = 'top-right'; 2481 break; 2482 } 2483 2484 fingerprintContainer.classList.add('sg-cookie-optin-fingerprint'); 2485 fingerprintContainer.classList.add('sg-cookie-optin-fingerprint-' + iconPositionClass); 2486 fingerprintContainer.addEventListener('click', function() { 2487 SgCookieOptin.openCookieOptin(null, {hideBanner: true}); 2488 }); 2489 2490 fingerprintContainer.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill="currentColor" class="sg-cookie-optin-fingerprint-icon" viewBox="0 0 512 512"><path d="M275.334 511c-5.207 0-8.678-4.339-8.678-8.678 6.075-182.237-16.488-256.868-17.356-257.736-1.736-4.339.868-9.546 5.207-11.281 4.339-1.736 9.546.868 11.281 5.207.868 3.471 24.298 77.234 17.356 262.942.868 6.075-3.471 9.546-7.81 9.546zM491.415 294.051c-4.339 0-7.81-3.471-8.678-7.81-6.075-58.142-14.753-104.136-25.166-128.434C423.727 77.102 345.625 25.034 257.978 25.034c-28.637 0-57.275 5.207-83.308 16.488-14.753 6.075-28.637 13.885-41.654 21.695-4.339 2.603-9.546 1.736-12.149-2.603-2.603-4.339-1.736-9.546 2.603-12.149 13.885-8.678 28.637-16.488 45.125-23.431 27.769-11.281 58.142-17.356 89.383-17.356 94.59 0 178.766 56.407 216.081 143.186 13.885 33.844 22.563 91.119 26.034 133.641.868 5.207-2.604 8.678-8.678 9.546.868 0 0 0 0 0zM43.632 363.475c-3.471 0-6.942-2.603-8.678-6.075-43.39-145.79 2.603-231.702 49.464-277.695 3.471-3.471 8.678-3.471 12.149 0s3.471 8.678 0 12.149C34.086 152.6 18.466 242.851 51.442 352.193c1.736 4.339-.868 9.546-6.075 10.414 0 .868-.867.868-1.735.868z" style="fill:currentColor" transform="translate(1 1)"/><path d="M100.906 450.254c-5.207 0-8.678-3.471-8.678-8.678 0-61.614 0-101.532-3.471-122.359-1.736-9.546-4.339-19.092-6.075-27.769-25.165-88.516 19.96-182.238 105.004-217.817 24.298-10.414 50.332-14.753 77.234-13.885 69.424 2.603 144.922 60.746 168.353 130.169 19.092 56.407 19.092 150.129 17.356 234.305 0 5.207-4.339 8.678-8.678 8.678-5.207 0-8.678-4.339-8.678-8.678 2.603-82.441 1.736-174.427-16.488-228.23-20.827-62.481-91.119-116.285-152.732-118.888-24.298-.868-47.729 3.471-69.424 13.017-77.234 32.108-118.02 117.153-95.458 196.99 2.603 9.546 5.207 19.959 6.942 29.505 3.471 22.563 3.471 62.481 3.471 124.963 0 5.206-3.471 8.677-8.678 8.677z" style="fill:currentColor" transform="translate(1 1)"/><path d="M388.147 476.288c-5.207 0-8.678-4.339-8.678-8.678 5.207-139.715 3.471-219.553-17.356-269.017-17.356-41.654-58.142-68.556-104.136-68.556-7.81 0-16.488.868-24.298 2.603-4.339.868-9.546-1.736-10.414-6.942-.868-4.339 1.736-9.546 6.942-10.414 9.546-1.736 18.224-2.603 27.77-2.603 52.068 0 99.797 31.241 119.756 79.837 22.563 52.936 24.298 131.905 19.092 276.827 0 3.472-4.339 6.943-8.678 6.943zm-226.495-17.356c-5.207 0-8.678-3.471-8.678-8.678 0-99.797-17.356-164.014-18.224-164.881-15.62-58.142 3.471-118.888 48.597-150.129 4.339-2.603 9.546-1.736 12.149 1.736 2.603 4.339 1.736 9.546-1.736 12.149-39.051 26.902-55.539 79.837-41.654 131.037.868 2.603 18.224 66.82 18.224 169.22 0 6.075-3.471 9.546-8.678 9.546zM336.079 502.322c-5.207 0-8.678-4.339-8.678-8.678v-42.522c0-10.414-.868-19.959 0-26.902 0-4.339 4.339-8.678 8.678-8.678 5.207 0 8.678 4.339 8.678 8.678v26.034c0 14.753.868 32.108 0 43.39 0 5.207-4.339 8.678-8.678 8.678z" style="fill:currentColor" transform="translate(1 1)"/><path d="M213.72 502.322c-5.207 0-8.678-3.471-8.678-8.678 0-103.268-5.207-207.403-12.149-227.363-12.149-36.447 4.339-74.631 38.183-88.515 8.678-3.471 17.356-5.207 26.902-5.207 27.77 0 52.936 16.488 64.217 42.522 17.356 40.786 22.563 112.814 23.431 165.749 0 5.207-3.471 8.678-8.678 8.678-4.339 0-8.678-3.471-8.678-8.678
2490-.868-51.2-6.075-120.624-22.563-158.807-7.81-19.959-26.902-32.108-47.729-32.108-6.942 0-13.885 1.736-19.959 4.339-25.166 10.414-37.315 39.051-28.637 66.82 7.81 24.298 13.017 139.715 13.017 232.569-.001 5.208-3.472 8.679-8.679 8.679z" style="fill:currentColor" transform="translate(1 1)"/><path d="M267.523 511c-5.207 0-8.678-4.339-8.678-8.678 6.075-183.105-16.488-265.546-17.356-266.414-1.736-4.339 1.736-9.546 6.075-10.414 4.339-1.736 9.546 1.736 10.414 6.075.868 3.471 24.298 85.912 17.356 271.62.867 4.34-3.472 7.811-7.811 7.811zM483.605 285.373c-4.339 0-7.81-3.471-8.678-7.81-3.471-33.844-11.281-95.458-25.166-128.434C415.917 68.424 337.815 16.356 250.167 16.356c-28.637 0-57.275 5.207-83.308 16.488-14.753 6.075-28.637 13.885-41.654 21.695-4.339 2.603-9.546 1.736-12.149-2.603-2.603-4.339-1.736-9.546 2.603-12.149 13.885-8.678 28.637-16.488 45.125-23.431C188.554 5.075 218.927-1 250.167-1c94.59 0 178.766 56.407 216.081 143.186 13.885 33.844 22.563 91.119 26.034 133.641.001 5.207-3.471 8.678-8.677 9.546.868 0 0 0 0 0zM35.822 354.797c-3.471 0-6.942-2.603-8.678-6.075-43.39-145.79 2.603-231.702 49.464-277.695 3.471-3.471 8.678-3.471 12.149 0 3.471 3.471 3.471 8.678 0 12.149-62.481 60.746-78.102 150.997-45.125 260.339 1.736 4.339-.868 9.546-6.075 10.414 0 .868-.868.868-1.735.868z" style="fill:currentColor" transform="translate(1 1)"/><path d="M93.096 441.576c-5.207 0-8.678-3.471-8.678-8.678 0-61.614 0-101.532-3.471-122.359-1.736-9.546-3.471-18.224-6.075-27.77-25.166-88.515 19.96-182.237 105.004-217.816 24.298-10.414 50.332-14.753 77.234-13.885 69.424 2.603 144.922 60.746 168.353 130.169 19.092 56.407 19.092 150.129 17.356 234.305 0 5.207-4.339 8.678-8.678 8.678-5.207 0-8.678-4.339-8.678-8.678 2.603-82.441 1.736-174.427-16.488-228.23-20.827-62.481-91.119-116.285-152.732-118.888-24.298-.868-47.729 3.471-69.424 13.017-77.234 32.108-118.02 117.153-95.458 196.99 2.603 9.546 5.207 19.959 6.942 29.505 3.471 22.563 3.471 62.481 3.471 124.963 0 5.206-3.471 8.677-8.678 8.677z" style="fill:currentColor" transform="translate(1 1)"/><path d="M380.337 467.61c-5.207 0-8.678-4.339-8.678-8.678 5.207-139.715 3.471-219.553-17.356-269.017-17.356-41.654-58.142-68.556-104.136-68.556-7.81 0-16.488.868-24.298 2.603-4.339.868-9.546-1.736-10.414-6.942-.868-4.339 1.736-9.546 6.942-10.414 9.546-1.736 18.224-2.603 27.77-2.603 52.068 0 99.797 31.241 119.756 79.837 22.563 52.936 24.298 131.905 19.092 276.827 0 3.472-4.339 6.943-8.678 6.943zm-226.495-17.356c-5.207 0-8.678-3.471-8.678-8.678 0-99.797-17.356-164.014-18.224-164.881-15.62-58.142 3.471-118.888 48.597-150.129 4.339-2.603 9.546-1.736 12.149 1.736 2.603 4.339 1.736 9.546-1.736 12.149-39.051 26.902-55.539 79.837-41.654 131.037.868 3.471 18.224 67.688 18.224 170.088 0 5.207-3.471 8.678-8.678 8.678zM328.269 493.644c-5.207 0-8.678-4.339-8.678-8.678v-42.522c0-10.414-.868-19.959 0-26.034 0-4.339 4.339-8.678 8.678-8.678 5.207 0 8.678 4.339 8.678 8.678v26.034c0 15.62.868 32.108 0 43.39 0 4.339-4.339 7.81-8.678 7.81z" style="fill:currentColor" transform="translate(1 1)"/><path d="M205.91 493.644c-5.207 0-8.678-3.471-8.678-8.678 0-103.268-5.207-207.403-12.149-227.363-12.149-36.447 4.339-74.631 38.183-88.515 8.678-3.471 17.356-5.207 26.902-5.207 27.769 0 52.936 16.488 64.217 42.522 17.356 40.786 22.563 112.814 23.431 165.749 0 5.207-3.471 8.678-8.678 8.678-4.339 0-8.678-3.471-8.678-8.678-.868-51.2-6.075-120.624-22.563-159.675-7.81-19.092-26.902-32.108-47.729-32.108-6.942 0-13.885 1.736-19.959 4.339-25.166 10.414-37.315 39.051-28.637 66.82 7.81 25.166 13.017 140.583 13.017 233.437-.001 5.208-3.472 8.679-8.679 8.679z" style="fill:currentColor" transform="translate(1 1)"/></svg>'; 2491 document.body.appendChild(fingerprintContainer); 2492 2493 // Emit event 2494 const fingerprintCreatedEvent = new CustomEvent('fingerprintCreated', { 2495 bubbles: true, 2496 detail: {} 2497 }); 2498 document.dispatchEvent(fingerprintCreatedEvent); 2499 2500 return fingerprintContainer; 2501 }, 2502 2503 /** 2504 * Gets the group settings by group name 2505 * @param groupName 2506 * @returns {*} 2507 */ 2508 getGroupByGroupName: function(groupName) { 2509 for (const groupIndex in SgCookieOptin.jsonData.cookieGroups) { 2510 if (!SgCookieOptin.jsonData.cookieGroups.hasOwnProperty(groupIndex)) { 2511 continue; 2512 } 2513 2514 if (SgCookieOptin.jsonData.cookieGroups[groupIndex]['groupName'] === groupName) { 2515 return SgCookieOptin.jsonData.cookieGroups[groupIndex]; 2516 } 2517 } 2518 } 2519}; 2520 2521SgCookieOptin.run();
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.