1/** 2* @author Dominik Kressler 3* @version 2.4.0 4* @date 2021-11-12 5* @description control scripts and iframes that cause in gdpr related problems 6* @dependencies [email protected], [email protected] 7**/ 8 9function PrivacyControl(args) 10{ 11 _this = this 12 13 if(window.pCinit !== undefined) 14 { 15 alert("*** PricacyControl Error! ***\n\nPlease don't call PrivacyControl twice!\nPrivacyControl bitte nicht doppelt aufrufen!") 16 return false 17 } 18 else window.pCinit = true 19 20 /** 21 * internal helper to merge the given deps object to the defaults 22 * the function removes deps from obj1 which are set to false in obj2 23 */ 24 var mergeDeps = function(obj1, obj2) { 25 for (var dependency in obj2) { 26 if(obj2[dependency] === false) delete obj1[dependency] 27 } 28 return obj1 29 } 30 31 var params = 32 { 33 deps:false, 34 sessionName: 'privacy-ctrl', 35 force: false, 36 badge: true, 37 placeholder: '/privacyControl-2.0.placeholder.html', 38 legal_page: { 39 de: '/impressum.php', 40 en: '/legal-notice.php' 41 }, 42 privacy_page: { 43 de: '/datenschutzerklaerung.php', 44 en: '/privacy-policy.php' 45 }, 46 storage: localStorage 47 }, 48 loaded = [] 49 _this.options = {}, 50 _this.options = J.merge(params, args), 51 _this.frames = [] 52 _this.storage = params.storage || localStorage 53 _this.options.deps = mergeDeps(_this.options.deps, args.deps) 54 55 if(_this.options.deps === false) return _this 56 57 var docLang = document.documentElement.lang.split('_')[0].toLowerCase() 58 docLang = document.documentElement.lang.split('-')[0].toLowerCase() 59 60 _this.lang = (_this.options.lang === undefined) ? docLang : _this.options.lang 61 if(pCl10n[_this.lang] === undefined) _this.lang = 'en' 62 63 var init = function() 64 { 65 if(_this.options.badge) addBadge() 66 normalizeServices() 67 createMarkup() 68 _this.apply() 69 iframePlaceholder() 70 bindEvents() 71 72 if(_this.options.force === false || interacted() === true) _this.container.parentElement.parentElement.classList.add('hide') 73 74 return _this 75 }; 76 77 var normalizeServices = function() 78 { 79 for(var key in _this.options.deps) 80 { 81 if (typeof _this.options.deps[key] == 'boolean' && pCServiceTemplates[key] !== undefined) 82 { 83 _this.options.deps[key] = pCServiceTemplates[key] 84 } 85 else if (typeof _this.options.deps[key] == 'object' && pCServiceTemplates[key] !== undefined) 86 { 87 _this.options.deps[key] = J.merge(pCServiceTemplates[key], _this.options.deps[key]) 88 } 89 } 90 91 return _this; 92 } 93 94 var getText = function(key, service) 95 { 96 var string = '' 97 try { 98 if(service !== undefined && _this.options.deps[service].l10n[_this.lang][key] !== undefined) 99 { 100 string = _this.options.deps[service].l10n[_this.lang][key] 101 } 102 else if(typeof _this.options[key] === 'object' && _this.options[key][_this.lang] !== undefined) 103 { 104 string = _this.options[key][_this.lang] 105 } 106 else if (pCl10n[_this.lang][key] !== undefined) 107 { 108 string = pCl10n[_this.lang][key] 109 } 110 } catch (error) { 111 console.log('Translation string missing for: '+key); 112 } 113 114 return string; 115 }; 116 117 var createMarkup = function() 118 { 119 _this.container = customTag('privacy-controller') 120 _this.container.id = 'privacy-controller' 121 122 var pheader = customTag('privacy-header', _this.container) 123 pheader.id = 'privacy-header' 124 var ptext = customTag('privacy-text', pheader) 125 ptext.innerHTML = getText('intro') 126 127 var btns = customTag('privacy-buttons', pheader) 128 129 var btn1 = customTag('privacy-btn', btns) 130 btn1.id = 'privacy-open' 131 btn1.setAttribute('href', '#privacy-controller') 132 btn1.innerHTML = getText('settings') 133 134 var btn2 = customTag('privacy-btn', btns) 135 btn2.id = 'privacy-confirm' 136 btn2.setAttribute('href', '#privacy-confirm') 137 btn2.innerHTML = getText('confirm') 138 139 var fields = customTag('privacy-fields') 140 fields.id = 'privacy-fields' 141 var services = _this.options.deps 142 for (var key in services) 143 { 144 try { 145 var cb = document.createElement('input') 146 cb.type = 'checkbox' 147 cb.setAttribute('name', key) 148 var id = _this.options.sessionName+':'+key 149 150 var row = customTag('privacy-row', fields) 151 152 customCells(getText('accept'), '<label for="'+id+'"><input id="'+id+'" name="'+key+'" type="checkbox"><span data-on="'+getText('on')+'" data-off="'+getText('off')+'"></span></label>', row) 153 154 if(typeof services[key].name !== 'undefined') customCells(getText('name'), services[key].name, row) 155 if(typeof services[key].provider !== 'undefined') customCells(getText('provider'), services[key].provider, row) 156 if(typeof services[key].l10n[_this.lang].description !== 'undefined') customCells(getText('purpose'), services[key].l10n[_this.lang].description, row) 157 if(typeof services[key].l10n[_this.lang].policy !== 'undefined') customCells(getText('policy'), '<a href="'+services[key].l10n[_this.lang].policy+'" target="_blank">Link</a>', row) 158 } catch (error) { 159 console.log('It is missing some properties for that service: '+key) 160 } 161 } 162 163 _this.container.appendChild(fields) 164 165 var footer = customTag('privacy-footer', _this.container) 166 var reset = customTag('privacy-link', footer) 167 reset.id = 'privacy-reset' 168 reset.classList.add('privacy-reset') 169 reset.innerHTML = getText('reset') 170 171 var legal_page = getText('legal_page') 172 if (legal_page && legal_page != '') 173 { 174 var imprint = customTag('privacy-link', footer) 175 imprint.setAttribute('href', legal_page) 176 imprint.innerHTML = getText('legal_notice') 177 } 178 179 var privacy_page = getText('privacy_page') 180 if(privacy_page && privacy_page != '') 181 { 182 var policy = customTag('privacy-link', footer) 183 policy.setAttribute('href', privacy_page) 184 policy.innerHTML = getText('policy') 185 } 186 187 notification.add({id: _this.options.sessionName, message: _this.container, saveState: !_this.options.force, remove: false}) 188 189 _this.elms = {} 190 _this.elms.open = btn1 191 _this.elms.submit = btn2 192 193 return _this 194 } 195 196 var customCells = function(cell1, cell2, target) 197 { 198 var cells = [] 199 cells[0] = customTag('privacy-cell', target) 200 cells[0].innerHTML = cell1 201 cells[1] = customTag('privacy-cell', target) 202 cells[1].innerHTML = cell2 203 return cells 204 } 205 var customTag = function(name, target) 206 { 207 var elm = document.createElement(name) 208 elm.classList.add(name) 209 if(target) target.appendChild(elm) 210 return elm 211 } 212 213 var open = function() 214 { 215 _this.container.classList.add('open') 216 _this.container.parentElement.parentElement.classList.remove('hide') 217 document.body.classList.add('privacy-control-is-open') 218 _this.elms.open.classList.add('hide') 219 return 220 } 221 222 var close = function() 223 { 224 _this.container.classList.remove('open'); 225 document.body.classList.remove('privacy-control-is-open'); 226 _this.elms.open.classList.remove('hide'); 227 notification.close(_this.options.sessionName); 228 return; 229 } 230 231 var interacted = function() 232 { 233 let storage = getStorage(); 234 235 for (let s in storage) 236 { 237 if (storage[s] === true) return true 238 } 239 240 return false 241 }; 242 243 var activateButtons = function(key) 244 { 245 let btns = document.querySelectorAll('[data-privacy-control
245="'+key+'"]') 246 btns.forEach(function (btn, i) { 247 btn.classList.add('granted', 'activated') 248 btn.dataset.privacyControlConsent = 'true' 249 }) 250 } 251 252 var bindEvents = function() 253 { 254 document.addEventListener('click', function(e) 255 { 256 closeInfoBox(true) 257 }) 258 259 var buttons = document.querySelectorAll('[data-privacy-control]') 260 for(var i=0; i < buttons.length; i++) 261 { 262 buttons[i].addEventListener('click', function(e) 263 { 264 e.preventDefault() 265 266 var key = this.dataset.privacyControl 267 268 if(key == '') 269 { 270 open() 271 } 272 else 273 { 274 activateButtons(key) 275 _this.set(key, true) 276 _this.apply() 277 } 278 }) 279 } 280 281 var extraHandler = document.querySelectorAll('[href="#privacy-controller"]') 282 for(var i=0; i < extraHandler.length; i++) 283 { 284 extraHandler[i].addEventListener('click', function(e) 285 { 286 e.preventDefault() 287 open() 288 }) 289 } 290 291 var checks = _this.container.querySelectorAll('[type=checkbox]') 292 for(var i = 0; i < checks.length; i++) 293 { 294 checks[i].addEventListener('change', function(e) 295 { 296 if(this.checked) _this.set(this.name, true) 297 else _this.set(this.name, false) 298 299 _this.apply() 300 }) 301 } 302 303 _this.elms.submit.addEventListener('click', function(e) 304 { 305 e.preventDefault() 306 307 _this.set('all', true) 308 _this.apply() 309 close() 310 }) 311 312 var resets = document.querySelectorAll('.privacy-reset') 313 for(var i = 0; i < resets.length; i++) 314 { 315 resets[i].addEventListener('click', function(e) 316 { 317 _this.reset() 318 }); 319 } 320 321 _this.container.parentElement.parentElement.querySelector('.close').addEventListener('click', close) 322 323 var links = document.querySelectorAll('.privacy-link') 324 for(var i = 0; i < links.length; i++) 325 { 326 links[i].addEventListener('click', function(e) 327 { 328 var href = this.getAttribute('href') 329 if(href !== null) 330 { 331 var win = window.open(href, '_blank') 332 win.focus() 333 } 334 }) 335 } 336 337 return _this 338 } 339 340 var addBadge = function() 341 { 342 var badge = document.createElement('A') 343 badge.href = '#privacy-controller' 344 badge.classList.add('privacy-control-badge') 345 var span = document.createElement('SPAN') 346 span.innerText = getText('badge') 347 badge.appendChild(span) 348 document.body.appendChild(badge) 349 return badge 350 } 351 352 var iframePlaceholder = function() 353 { 354 var services = _this.options.deps 355 356 for(var key in services) 357 { 358 if(services[key].type == 'iframe') 359 { 360 var elms = document.querySelectorAll('iframe[data-pc-service='+key+']:not(.enabled), iframe[data-iframe='+key+']:not(.enabled)') 361 362 for(var i = 0; i < elms.length; i++) 363 { 364 var frame = elms[i] 365 frame.addEventListener('load', function() 366 { 367 try { 368 if(this.classList.contains('enabled')) return false 369 var frame = this 370 key = frame.dataset.pcService || frame.dataset.iframe, 371 doc = frame.contentDocument ? frame.contentDocument : frame.contentWindow.document; 372 373 _this.frames.push(frame) 374 375 var policy = (services[key].l10n[_this.lang].policy) ? '<a href="'+services[key].l10n[_this.lang].policy+'" target="_blank">'+services[key].l10n[_this.lang].policy+'</a>' : '' 376 doc.body.innerHTML = doc.body.innerHTML.replaceAll('{{policy}}', policy) 377 doc.body.innerHTML = doc.body.innerHTML.replaceAll('{{name}}', services[key].name) 378 doc.body.innerHTML = doc.body.innerHTML.replaceAll('{{activate}}', getText('activate')) 379 doc.body.innerHTML = doc.body.innerHTML.replaceAll('{{msg}}', services[key].l10n[_this.lang].description) 380 doc.body.innerHTML = doc.body.innerHTML.replaceAll('{{provider}}', getText('provider')) 381 doc.body.innerHTML = doc.body.innerHTML.replaceAll('{{providerName}}', services[key].provider) 382 doc.body.innerHTML = doc.body.innerHTML.replaceAll('{{key}}', key) 383 doc.head.innerHTML = doc.head.innerHTML.replaceAll('{{key}}', key) 384 doc.body.id = key 385 doc.body.classList.add('enabled') 386 387 doc.querySelector('html').lang = _this.lang 388 389 if(services[key].css) doc.head.innerHTML += '<link rel="stylesheet" href="'+services[key].css+'"></link>' 390 bindInFrameEvent(doc) 391 } catch (error) { 392 console.log("Can't handle the placeholder. The service options are broken: "+key) 393 } 394 }); 395 frame.dataset.lang = document.querySelector('html').lang 396 frame.src = _this.options.placeholder+'?key='+key 397 } 398 } 399 } 400 } 401 402 var bindInFrameEvent = function(doc) 403 { 404 var infoOpen = doc.querySelectorAll('.infoOpen') 405 for(var i = 0, c = infoOpen.length; i < c; i++) 406 { 407 infoOpen[i].addEventListener('click', function(e) 408 { 409 e.preventDefault() 410 411 var frame = document.activeElement 412 var doc = frame.contentDocument ? frame.contentDocument : frame.contentWindow.document 413 414 doc.querySelector('.infoBox').classList.add('open') 415 doc.querySelector('.infoOpen').classList.add('hide') 416 }) 417 } 418 419 var infoClose = doc.querySelectorAll('.infoClose') 420 for(var i = 0, c = infoClose.length; i < c; i++) 421 {
422 infoClose[i].addEventListener('click', closeInfoBox) 423 } 424 425 doc.addEventListener('keydown', function(e) 426 { 427 if (e.keyCode == 27) closeInfoBox() 428 return 429 }); 430 431 var btn = doc.querySelector('.accept') 432 btn.addEventListener('click', function(e) 433 { 434 e.preventDefault() 435 e.stopPropagation() 436 437 var key = document.activeElement.dataset.iframe || document.activeElement.dataset.pcService 438 439 _this.set(key, true) 440 _this.apply() 441 442 return 443 }, false) 444 } 445 446 var closeInfoBox = function(all) 447 { 448 var frames = (all === null || all === false) ? [document.activeElement] : _this.frames 449 450 for(var i in frames) 451 { 452 try { 453 doc = frames[i].contentDocument ? frames[i].contentDocument : frames[i].contentWindow.document 454 455 doc.querySelector('.infoBox').classList.remove('open') 456 doc.querySelector('.infoOpen').classList.remove('hide') 457 } catch (error) { 458 } 459 } 460 } 461 462 var applyInterface = function() 463 { 464 for(var key in _this.options.deps) 465 { 466 var val = _this.options.deps[key] 467 var status = _this.get(key) 468 469 if(status === undefined) 470 { 471 status = false 472 _this.set(key, status) 473 } 474 475 var id = _this.options.sessionName+':'+key 476 var input = document.getElementById(id) 477 input.checked = status 478 } 479 480 return _this 481 } 482 483 var loadDep = function(key, type) 484 { 485 if(loaded[key] === true) return false; 486 switch(type) 487 { 488 case "script": 489 490 try 491 { 492 if(_this.options.deps[key].src !== undefined) 493 { 494 var srcs = _this.options.deps[key].src; 495 if(typeof srcs != 'object') 496 { 497 srcs = [srcs]; 498 } 499 500 rIt({ 501 "*": { 502 "assets": srcs, 503 "function": _this.options.deps[key].callback 504 } 505 }); 506 } 507 else 508 { 509 var scripts = document.querySelectorAll('script[data-script="'+key+'"], script[data-pc-service="'+key+'"]'); 510 for(var i = 0, l = scripts.length; i < l; i++) 511 { 512 if(i==l-1) scripts[i].addEventListener('load', _this.options.deps[key].callback); 513 scripts[i].src = scripts[i].dataset.pcAsset; 514 } 515 } 516 517 } catch (error) { 518 console.log('Could not load script for '+key+', no src defined.'); 519 } 520 521 break; 522 case "iframe": 523 524 var iframes = document.querySelectorAll('[data-iframe="'+key+'"], iframe[data-pc-service="'+key+'"]'); 525 526 for (var i in iframes) 527 { 528 if(typeof iframes[i] == 'object' && iframes[i].dataset) 529 { 530 var placeholder = iframes[i].pcAsset; 531 var src = iframes[i].dataset.pcAsset || iframes[i].dataset.src; 532 533 iframes[i].dataset.pcAsset = placeholder; 534 iframes[i].src = src; 535 iframes[i].classList.add('enabled', 'pc-loaded'); 536 iframes[i].classList.remove('pc-unloaded'); 537 iframes[i].dataset.frameId = (new Date()).getTime(); 538 } 539 } 540 541 break; 542 case "image": 543 544 let images = document.querySelectorAll('img[data-pc-service="'+key+'"]'); 545 for(let img of images) 546 { 547 let temp = img.src; 548 img.src = img.dataset.pcAsset; 549 img.dataset.pcAsset = temp; 550 img.classList.add('pc-loaded'); 551 img.classList.remove('pc-unloaded'); 552 } 553 554 break; 555 } 556 557 loaded[key] = true; 558 559 activateButtons(key); 560 561 return true; 562 }; 563 564 var unloadDep = function(key, type) 565 { 566 if(loaded[key] === false) return false;
567 switch(type) 568 { 569 case "iframe": 570 571 var iframes = document.querySelectorAll('[data-iframe="'+key+'"], iframe[data-pc-service="'+key+'"]'); 572 for (var i in iframes) 573 { 574 if(typeof iframes[i] == 'object' && iframes[i].dataset) 575 { 576 var placeholder = iframes[i].dataset.src; 577 var src = iframes[i].src; 578 579 iframes[i].classList.remove('enabled', 'pc-loaded'); 580 iframes[i].classList.add('pc-unloaded'); 581 iframes[i].dataset.pcAsset = src; 582 iframes[i].src = placeholder; 583 iframePlaceholder(); 584 } 585 } 586 587 break; 588 case "image": 589 let images = document.querySelectorAll('img[data-pc-service="'+key+'"]'); 590 for(let img of images) 591 { 592 let temp = img.src; 593 img.src = img.dataset.pcAsset; 594 img.dataset.pcAsset = temp; 595 img.classList.remove('pc-loaded'); 596 img.classList.add('pc-unloaded'); 597 } 598 break; 599 } 600 601 loaded[key] = false; 602 603 return true; 604 }; 605 606 var getStorage = function() 607 { 608 var storage = _this.storage.getItem(_this.options.sessionName); 609 storage = (storage === null || storage === '' || storage === false) ? {} : JSON.parse(storage); 610 611 return storage; 612 }; 613 614 _this.apply = function() 615 { 616 var storage = _this.get('all'); 617 618 for(var key in storage) 619 { 620 if(_this.options.deps[key] === undefined) continue; 621 var type = _this.options.deps[key].type; 622 623 if(_this.options.deps[key] === undefined || storage[key] === false) 624 { 625 if(key !== undefined && loaded[key] === true) unloadDep(key, type); 626 } 627 else 628 { 629 if(key !== undefined) loadDep(key, type); 630 } 631 } 632 633 applyInterface(); 634 return _this; 635 }; 636 637 _this.set = function(key, status) 638 { 639 var storage = getStorage(); 640 if(key === 'all') 641 { 642 for(var i in storage) 643 { 644 _this.set(i, status); 645 } 646 } 647 else 648 { 649 storage[key] = status; 650 try { 651 _this.storage.setItem(_this.options.sessionName, JSON.stringify(storage)); 652 if(!_this.options.deps[key]) throw new Error("Key "+key+" is missing in the PrivacyControl deps configuration object."); 653 if(_this.options.deps[key].forceReload !== undefined && _this.options.deps[key].forceReload === true && document.readyState === 'complete') window.location.reload(); 654 } catch (error) { 655 console.error(error); 656 } 657 } 658 659 return storage; 660 }; 661 662 _this.get = function(key) 663 { 664 var storage = getStorage(); 665 if(key == 'all') return storage; 666 return storage[key]; 667 }; 668 669 _this.reset = function() 670 { 671 _this.storage.setItem(_this.options.sessionName, '{}'); 672 _this.storage.removeItem('note-'+_this.options.sessionName); 673 674 location.reload(); 675 return _this.get('all'); 676 }; 677 678 _this.please = function() 679 { 680 _this.set('all', true); 681 console.info("%c Hello my dear, I appreciate that you ask me to enable all services now and you're welcome. But keep in mind, you should not!", 'background: #222; color: #bada55'); 682 _this.apply(); 683 684 return "This is a shortcut for set('all', true) and apply()"; 685 } 686 687 return init(); 688} 689 690if(String.prototype.replaceAll === undefined) 691{ 692 String.prototype.replaceAll = function(search, replacement) { 693 var target = this; 694 return target.replace(new RegExp(search, 'g'), replacement); 695 }; 696}
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.