1/* 2 * advanced ads functions to be used directly within ad codes 3 */ 4 5/** 6 * Polyfills 7 */ 8(function () { 9 if ( typeof window.CustomEvent !== "function" ) { 10 /** 11 * CustomEvent polyfill for IE11: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent 12 * 13 * @param {string} event Event name. 14 * @param {object} params Event parameters. 15 * @returne {object} Custom event. 16 */ 17 function CustomEvent ( event, params ) { 18 params = params || { bubbles: false, cancelable: false, detail: null }; 19 var evt = document.createEvent( 'CustomEvent' ); 20 evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); 21 return evt; 22 } 23 24 window.CustomEvent = CustomEvent; 25 } 26 27 /** 28 * ReplaceWith polyfill for IE11: https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/replaceWith 29 */ 30 function ReplaceWithPolyfill() { 31 'use-strict'; // For safari, and IE > 10 32 var parent = this.parentNode, i = arguments.length, currentNode; 33 if ( ! parent) { 34 return; 35 } 36 if ( ! i ) {// if there are no arguments 37 parent.removeChild( this ); 38 } 39 while ( i-- ) { // i-- decrements i and returns the value of i before the decrement 40 currentNode = arguments[i]; 41 if ( typeof currentNode !== 'object' ) { 42 currentNode = this.ownerDocument.createTextNode( currentNode ); 43 } else if ( currentNode.parentNode ) { 44 currentNode.parentNode.removeChild( currentNode ); 45 } 46 // the value of "i" below is after the decrement 47 if ( ! i ) { // if currentNode is the first argument (currentNode === arguments[0]) 48 parent.replaceChild( currentNode, this ); 49 } else { // if currentNode isn't the first 50 parent.insertBefore( currentNode, this.nextSibling ); 51 } 52 } 53 } 54 if ( ! Element.prototype.replaceWith ) { 55 Element.prototype.replaceWith = ReplaceWithPolyfill; 56 } 57 if ( ! CharacterData.prototype.replaceWith ) { 58 CharacterData.prototype.replaceWith = ReplaceWithPolyfill; 59 } 60 if ( ! DocumentType.prototype.replaceWith ) { 61 DocumentType.prototype.replaceWith = ReplaceWithPolyfill; 62 } 63 64 /** 65 * Polyfill for NodeList.foreach() because we need to support IE11: https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach 66 */ 67 if ( window.NodeList && ! NodeList.prototype.forEach ) { 68 NodeList.prototype.forEach = function( callback, thisArg ) { 69 var i; 70 var len = this.length; 71 72 thisArg = thisArg || window; 73 74 for ( i = 0; i < len; i++ ) { 75 callback.call( thisArg, this[ i ], i, this ); 76 } 77 }; 78 } 79 80})(); 81 82advads = { 83 /** 84 * check if localstorage is supported/enabled by client 85 */ 86 supports_localstorage: function () { 87 "use strict"; 88 try { 89 if ( ! window || window.localStorage === undefined ) { 90 return false; 91 } 92 // storage might be full or disabled 93 window.localStorage.setItem( "x", "x" ); 94 window.localStorage.removeItem( "x" ); 95 return true; 96 } catch ( e ) { 97 return false; 98 } 99 }, 100 /** 101 * check if the ad is displayed more than {max} times per session 102 * every check increases the counter 103 * 104 * @param {string} name (no id needed, just any id-formated string) 105 * @param {type} max number of maximum times the ad can be displayed within the period 106 * @returns {bool} true if limit is reached 107 */ 108 max_per_session: function ( name, max ) { 109 var num = 1; 110 if ( max === undefined || parseInt( max ) === 0 ) { 111 max = 1; 112 } 113 114 // check if cookie exists and get the value 115 if ( this.cookie_exists( name ) ) { 116 if ( this.get_cookie( name ) >= max ) { 117 return true; 118 } 119 num = num + parseInt( this.get_cookie( name ) ); 120 } 121 this.set_cookie( name, num ); 122 return false; 123 }, 124 /** 125 * increase a cookie with an integer value by 1 126 * 127 * @param {str} name of the cookie 128 * @param {int} exdays days until cookie expires 129 */ 130 count_up: function ( name, exdays ) { 131 var num = 1; 132 133 // check if cookie exists and get the value 134 if ( this.cookie_exists( name ) ) { 135 num = num + parseInt( this.get_cookie( name ) ); 136 } 137 this.set_cookie( name, num ); 138 }, 139 /** 140 * return true, if cookie exists 141 * return false, if not 142 * if not exists, create it 143 * use case: to check if something already happened in this page impression 144 * 145 * @param {type} name 146 * @returns {unresolved} 147 */ 148 set_cookie_exists: function ( name ) { 149 if ( get_cookie( name ) ) { 150 return true; 151 } 152 set_cookie( name, '', 0 ); 153 return false;
154 }, 155 /** 156 * get a cookie value 157 * 158 * @param {string} name of the cookie 159 * @return {string} decoded cookie value 160 */ 161 get_cookie: function ( name ) { 162 var i, x, y, ADVcookies = document.cookie.split( ';' ); 163 for ( i = 0; i < ADVcookies.length; i ++ ) { 164 x = ADVcookies[i].substr( 0, ADVcookies[i].indexOf( '=' ) ); 165 y = ADVcookies[i].substr( ADVcookies[i].indexOf( '=' ) + 1 ); 166 x = x.replace( /^\s+|\s+$/g, '' ); 167 if ( x === name ) { 168 return decodeURIComponent( y ); 169 } 170 } 171 }, 172 /** 173 * set a cookie value 174 * 175 * @param {str} name of the cookie 176 * @param {str} value of the cookie 177 * @param {int} exdays days until cookie expires 178 * set 0 to expire cookie immidiatelly 179 * set null to expire cookie in the current session 180 */ 181 set_cookie: function ( name, value, exdays, path, domain, secure ) { 182 // days in seconds 183 var expiry = ( 184 exdays == null 185 ) ? null : exdays * 24 * 60 * 60; 186 this.set_cookie_sec( name, value, expiry, path, domain, secure ); 187 }, 188 /** 189 * set a cookie with expiry given in seconds 190 * 191 * @param {str} name of the cookie 192 * @param {str} value of the cookie 193 * @param {int} expiry seconds until cookie expires 194 * set 0 to expire cookie immidiatelly 195 * set null to expire cookie in the current session 196 */
197 set_cookie_sec: function ( name, value, expiry, path, domain, secure ) { 198 var exdate = new Date(); 199 exdate.setSeconds( exdate.getSeconds() + parseInt( expiry ) ); 200 document.cookie = name + "=" + encodeURIComponent( value ) + 201 ( 202 ( 203 expiry == null 204 ) ? "" : "; expires=" + exdate.toUTCString() 205 ) + 206 ( 207 ( 208 path == null 209 ) ? "; path=/" : "; path=" + path 210 ) + 211 ( 212 ( 213 domain == null 214 ) ? "" : "; domain=" + domain 215 ) + 216 ( 217 ( 218 secure == null 219 ) ? "" : "; secure" 220 ); 221 }, 222 /** 223 * check if a cookie is set and contains a value 224 * 225 * @param {str} name of the cookie 226 * @returns {bool} true, if cookie is set 227 */ 228 cookie_exists: function ( name ) { 229 var c_value = this.get_cookie( name ); 230 if ( c_value !== null && c_value !== "" && c_value !== undefined ) { 231 return true; 232 } 233 return false; 234 }, 235 /** 236 * move one element into another 237 * 238 * @param {str} element selector of the element that should be moved 239 * @param {str} target selector of the element where to move 240 * @param {arr} options 241 */ 242 move: function ( element, target, options ) { 243 244 var el = jQuery( element ); 245 var target_string = target; 246 247 if ( typeof options === 'undefined' ) { 248 options = {}; 249 } 250 if ( typeof options.css === 'undefined' ) { 251 options.css = {}; 252 } 253 if ( typeof options.method === 'undefined' ) { 254 options.method = 'prependTo'; 255 } 256 257 // search for abstract target element 258 if ( target === '' && typeof options.target !== 'undefined' ) { 259 switch ( options.target ) { 260 case 'wrapper' : // wrapper 261 var offset = 'left'; 262 if ( typeof options.offset !== 'undefined' ) { 263 offset = options.offset; 264 } 265 target = this.find_wrapper( element, offset ); 266 break; 267 } 268 } 269 270 // use only visible elements 271 if ( typeof options.moveintohidden === 'undefined' ) { 272 target = jQuery( target ).filter( ':visible' ); 273 } else { 274 target = jQuery( target ); 275 } 276 277 // print warning in console if the element appears multiple times 278 if ( target.length > 1 ) { 279 console.log( "Advanced Ads: element '" + target_string + "' found " + target.length + " times." ); 280 } 281 282 // switch insert method 283 switch ( options.method ) { 284 case 'insertBefore' : 285 el.insertBefore( target ); 286 break; 287 case 'insertAfter' : 288 el.insertAfter( target ); 289 break; 290 case 'appendTo' : 291 el.appendTo( target ); 292 break; 293 case 'prependTo' : 294 el.prependTo( target ); 295 break; 296 default : 297 el.prependTo( target ); 298 } 299 }, 300 301 /** 302 * Set 'relative' position for a parent element. 303 * 304 * @param {str} element selector 305 */ 306 set_parent_relative: function ( element, options ) { 307 var options = typeof options !== 'undefined' ? options : {}; 308 var el = jQuery( element ); 309 // give "position" style to parent element, if missing 310 var parent = el.parent(); 311 312 if ( options.use_grandparent ) { 313 parent = parent.parent(); 314 } 315 316 if ( parent.css( 'position' ) === 'static' || parent.css( 'position' ) === '' ) { 317 parent.css( 'position', 'relative' ); 318 } 319 }, 320 321 /** 322 * make an absolute position element fixed at the current position 323 * hint: use only after DOM is fully loaded in order to fix a wrong position 324 * 325 * @param {str} element selector 326 * @param {obj} options 327 */ 328 fix_element: function ( element, options ) { 329 var options = typeof options !== 'undefined' ? options : {}; 330 331 var el = jQuery( element ); 332 333 if ( options.use_grandparent ) { 334 this.set_parent_relative( el.parent() ); 335 } else { 336 this.set_parent_relative( el ); 337 } 338 339 // fix element at current position 340 // get position for hidden elements by showing them for a very short time 341 if ( options.is_invisible ) { 342 el.show(); 343 } 344 var topoffset = parseInt( el.offset().top ); 345 var leftoffset = parseInt( el.offset().left ); 346 if ( options.is_invisible ) { 347 el.hide(); 348 } 349 if ( 'left' === options.offset ) { 350 // Allow to scale the nested image down when it has `max-width: 100%` and touches the left edge of the viewport. 351 var rightoffset = jQuery( window ).width() - leftoffset - el.outerWidth();
352 el.css( 'position', 'fixed' ).css( 'top', topoffset + 'px' ).css( 'right', rightoffset + 'px' ).css( 'left', '' ); 353 } else { 354 // reset "right" to prevent conflicts 355 el.css( 'position', 'fixed' ).css( 'top', topoffset + 'px' ).css( 'left', leftoffset + 'px' ).css( 'right', '' ); 356 } 357 358 }, 359 360 /** 361 * find the main wrapper 362 * either id or first of its class 363 * 364 * @param {str} element selector 365 * @param {str} offset which position of the offset to check (left or right) 366 * @return {str} selector 367 */ 368 find_wrapper: function ( element, offset ) { 369 // first margin: auto element after body 370 var returnValue; 371 372 jQuery( 'body' ).children().not('script, .screen-reader-text, .skip-link, ' + element).each( function ( key, value ) { 373 // check offset value 374 var checkedelement = jQuery( value ); 375 // check if there is space left or right of the element 376 if ( ( 377 offset === 'right' && ( 378 checkedelement.offset().left + jQuery( checkedelement ).width() < jQuery( window ).width() 379 ) 380 ) || 381 ( 382 offset === 'left' && checkedelement.offset().left > 0 383 ) ) { 384 // fix element 385 if ( checkedelement.css( 'position' ) === 'static' || checkedelement.css( 'position' ) === '' ) { 386 checkedelement.css( 'position', 'relative' ); 387 } 388 // set return value 389 returnValue = value; 390 return false; 391 } 392 } ); 393 return returnValue; 394 }, 395 /** 396 * center fixed element on the screen 397 * 398 * @param {str} element selector 399 */ 400 center_fixed_element: function ( element ) { 401 var el = jQuery( element ); 402 // half window width minus half element width 403 var left = ( 404 jQuery( window ).width() / 2 405 ) - ( 406 parseInt( el.css( 'width' ) ) / 2 407 ); 408 el.css( 'left', left + 'px' ); 409 }, 410 /** 411 * center element vertically on the screen 412 * 413 * @param {str} element selector 414 */ 415 center_vertically: function ( element ) { 416 var el = jQuery( element ); 417 // half window height minus half element height 418 var left = ( 419 jQuery( window ).height() / 2 420 ) - ( 421 parseInt( el.css( 'height' ) ) / 2 422 ); 423 424 // Center correctly when the ad is attached to the element that begins lower. 425 if ( el.css( 'position' ) !== 'fixed' ) { 426 left -= topoffset = parseInt( el.offset().top ); 427 } 428 el.css( 'top', left + 'px' ); 429 }, 430 /** 431 * close an ad and add a cookie 432 * 433 * @param {str} element selector 434 */ 435 close: function ( element ) { 436 var wrapper = jQuery( element ); 437 // remove the ad 438 wrapper.remove(); 439 }, 440 /** 441 * Wait until images are ready. 442 * 443 * @param {obj} $el jQuery object. 444 * @param {function} ready_callback Ready callback. 445 * derrived from https://github.com/alexanderdickson/waitForImages/blob/master/dist/jquery.waitforimages.js 446 */ 447 wait_for_images: function ( $el, ready_callback ) { 448 var loaded_count = 0; 449 var srcs = []; 450 451 $el.find( 'img[src][src!=""]' ).each( function () { 452 srcs.push( this.src ); 453 } ); 454 455 if ( srcs.length === 0 ) { 456 ready_callback.call( $el ); 457 } 458 459 jQuery.each( srcs, function ( i, src ) { 460 var image = new Image(); 461 image.src = src; 462 var events = 'load error'; 463 464 jQuery( image ).one( events, function me( event ) { 465 // Remove remaining handler (either 'load' or 'error'). 466 jQuery( this ).off( events, me ); 467 loaded_count ++; 468 469 if ( loaded_count == srcs.length ) { 470 ready_callback.call( $el[0] ); 471 return false; 472 } 473 } ); 474 } ); 475 }, 476 477 privacy: { 478 state: 'unknown', 479 state_executed: false, 480 /** 481 * Get consent state. 482 * IIFE so the events fire only once per event. 483 * 484 * @return string 485 * 'not_needed' - consent is not needed. 486 * 'accepted' - consent was given. 487 * 'unknown' - consent was not given yet. 488 */ 489 get_state: ( 490 function () { 491 return function () { 492 // if we already have a state, return that. 493 if ( window.advads_options.privacy.state !== 'unknown' ) { 494 // make sure this only gets executed once. 495 if ( ! advads.privacy.state_executed ) { 496 advads.privacy.state_executed = true; 497 advads.privacy.dispatch_event( window.advads_options.privacy.state, false ); 498 } 499 return advads.privacy.state; 500 } 501 502 // If using the cookie method, fire an initial event, regardless if cookie set or not. 503 if ( window.advads_options.privacy['consent-method'] === 'custom' ) { 504 var cookie_regex = new RegExp( '.*?' + window.advads_options.privacy['custom-cookie-value'] + '[^;]*?' );
505 let cookie = advads.get_cookie( window.advads_options.privacy['custom-cookie-name'] ) || ''; 506 507 // Force the event, if we haven't yet fired one. 508 if ( ! advads.privacy.state_executed ) { 509 advads.privacy.state_executed = true; 510 advads.privacy.dispatch_event( cookie.match( cookie_regex ) ? 'accepted' : 'unknown', true ); 511 } 512 } 513 514 // make sure this only gets executed once. 515 advads.privacy.state_executed = true; 516 517 // Run this in an interval (every 0.3s) just in case we are still waiting for consent 518 var cnt = 0, 519 consentSetInterval = setInterval( function () { 520 // Bail if we have not gotten a consent response after 60 seconds. 521 if ( ++ cnt === 181 ) { 522 clearInterval( consentSetInterval ); 523 } 524 switch ( window.advads_options.privacy['consent-method'] ) { 525 case 'custom' : 526 let cookie = advads.get_cookie( window.advads_options.privacy['custom-cookie-name'] ) || ''; 527 528 // check if custom cookie is set and matches value. 529 if ( cookie.match( cookie_regex ) ) { 530 clearInterval( consentSetInterval ); 531 if ( advads.privacy.state !== 'accepted' ) { 532 advads.privacy.dispatch_event( 'accepted', true ); 533 } 534 } 535 break; 536 537 case 'iab_tcf_20' : 538 // Check if window.__tcfapi has been set. 539 if ( typeof window.__tcfapi === 'undefined' ) { 540 return; 541 } 542 clearInterval( consentSetInterval ); 543 544 window.__tcfapi( 'addEventListener', 2, function ( TCData, listenerSuccess ) { 545 if ( ! listenerSuccess ) { 546 return; 547 } 548 if ( 549 TCData.eventStatus === 'tcloaded' 550 || TCData.eventStatus === 'useractioncomplete' 551 // if this is google funding choices, eventStatus is not set. Check if either gdpr doesn't apply or if there is a purpose object. 552 || ( TCData.eventStatus === null && typeof window.googlefc !== 'undefined' && ( 553 typeof TCData.purpose !== 'undefined' || ! TCData.gdprApplies 554 ) ) 555 ) { 556 var userAction = TCData.eventStatus === 'useractioncomplete'; 557 if ( ! TCData.gdprApplies ) { 558 if ( advads.privacy.state !== 'not_needed' ) { 559 advads.privacy.dispatch_event( 'not_needed', userAction ); 560 } 561 return; 562 } 563 564 if ( TCData.purpose.consents[1] ) { 565 if ( advads.privacy.state !== 'accepted' ) { 566 advads.privacy.dispatch_event( 'accepted', userAction ); 567 } 568 return; 569 } 570 571 // fire another event, in case the user revokes the previous consent. 572 if ( advads.privacy.state !== 'rejected' ) { 573 advads.privacy.dispatch_event( 'rejected', userAction ); 574 } 575 } 576 } 577 ); 578 break; 579 } 580 }, 333 ); 581 582 return advads.privacy.state; 583 } 584 } 585 )(), 586 /** 587 * Check if the privacy_method is custom cookie, and non personalized ads are allowed. 588 * 589 * @returns {boolean} 590 */ 591 is_adsense_npa_enabled: function () { 592 if ( ! window.advads_options || ! window.advads_options.privacy ) { 593 return true; 594 } 595 return !! ( 596 window.advads_options.privacy['show-non-personalized-adsense'] && window.advads_options.privacy['consent-method'] === 'custom' 597 ); 598 }, 599 /** 600 * Dispatch a custom event whenever the state changes. 601 * 602 * @param {String} state The current privacy state. 603 * @param {boolean} userAction This is result of action by user. 604 */ 605 dispatch_event: function ( state, userAction ) { 606 var previousState = advads.privacy.state, 607 fire_event = function () { 608 document.dispatchEvent( new CustomEvent( 'advanced_ads_privacy', { 609 detail: { 610 state: state, 611 previousState: previousState, 612 userAction: userAction 613 } 614 } ) ); 615 }; 616 617 advads.privacy.state = state; 618 619 console.log( { 620 state: state, 621 previousState: previousState, 622 userAction: userAction 623 } ); 624 625 window.advanced_ads_ready_queue.push( fire_event ); 626 }, 627 /** 628 * Check if ad is decoded. 629 * 630 * @param {integer} id 631 * 632 * @returns {boolean} 633 */ 634 is_ad_decoded: function ( id ) { 635 return document.querySelector( 'script[data-tcf="waiting-for-consent"][data-id="' + id + '"]' ) === null; 636 }, 637 /** 638 * Decode ad content. 639 * 640 * @param {Element} el 641 * @param {boolean} [inject=true] 642 */ 643 decode_ad: function ( el, inject ) { 644 // this can also be a number if used in a foreach. 645 inject = typeof inject === 'boolean' ? inject : true; 646 var string = decodeURIComponent( Array.prototype.map.call( atob( el.textContent ), function ( c ) {
647 return '%' + ( '00' + c.charCodeAt( 0 ).toString( 16 ) ).slice( - 2 ); 648 } ).join( '' ) ); 649 650 if ( ! inject ) { 651 return string; 652 } 653 654 el.replaceWith( document.createRange().createContextualFragment( string ) ); 655 } 656 } 657}; 658 659window.advanced_ads_ready_queue.push( advads.privacy.get_state ); 660 661document.addEventListener( 'advanced_ads_privacy', function ( event ) { 662 if ( 663 ( 664 event.detail.state !== 'accepted' && event.detail.state !== 'not_needed' 665 ) 666 || event.detail.userAction 667 || document.readyState === 'loading' 668 ) { 669 return; 670 } 671 672 // Find all scripts waiting for consent and decode them. 673 document.querySelectorAll( 'script[type="text/plain"][data-tcf="waiting-for-consent"]' ).forEach( advads.privacy.decode_ad ); 674} );
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.