https://kobus-immobilien.de/wp-content/plugins/immomakler/assets/js/immomakler-consent.js?ver=5.69.0
1/** 2 * WP-ImmoMakler consent API. 3 * 4 * One gate for every third party we embed -- YouTube and Vimeo videos, virtual 5 * tours, Google Maps. Consumers (`immomakler-media-consent.js`, the map gates 6 * printed inline by `Single_Map` and `Map_Container_Renderer`) only ever talk 7 * to `window.ImmomaklerConsent`, so where the answer comes from is decided 8 * here and nowhere else. 9 * 10 * It comes from one of three places: 11 * 12 * - a consent-management plugin (CMP) on the site -- Complianz directly, or 13 * any CMP that speaks the WP Consent API; 14 * - our own overlay, whose answer is kept in localStorage; 15 * - nothing at all, when the site owner has told us their CMP already blocks 16 * the content itself. 17 * 18 * **The CMP wins whenever there is one.** It is the site's legal record of 19 * consent -- proof, granular services, withdrawal -- where ours is a 20 * localStorage flag with no audit trail. Deferring also stops the visitor 21 * being asked twice, and stops our overlay from loading YouTube on a visitor 22 * who denied marketing in the banner. 23 * 24 * Vanilla JS, no jQuery, and no dependency on any CMP being present. 25 */ 26( function () { 27 'use strict'; 28 29 const STORAGE_KEY = 'wp-immomakler-consents'; 30 31 /** Callbacks waiting per service, for `consentFor()`. */ 32 const waiting = new Map(); 33 34 /** 35 * Runtime configuration injected by `Consent_Assets`. 36 * 37 * Shipped as real JSON via `wp_add_inline_script()` rather than 38 * `wp_localize_script()`, which would stringify every top-level scalar. 39 * 40 * @return {Object} The config, or an empty object before it is injected. 41 */ 42 function config() { 43 const cfg = window.immomaklerConsentConfig; 44 return cfg && 'object' === typeof cfg ? cfg : {}; 45 } 46 47 /** 48 * @return {string} 'auto' (defer to a CMP when present), 'own' (always our 49 * own overlay) or 'off' (no gate from us at all). 50 */ 51 function mode() { 52 const value = config().mode; 53 return 'own' === value || 'off' === value ? value : 'auto'; 54 } 55 56 /** 57 * The consent category a service belongs to, for CMPs that group services. 58 * 59 * @param {string} service Our service slug. 60 * @return {string} A WP Consent API category name. 61 */ 62 function categoryFor( service ) { 63 const categories = config().categories; 64 return ( categories && categories[ service ] ) || 'marketing'; 65 } 66 67 /** 68 * Our slug translated to what a given CMP calls the same service. 69 * 70 * Ours are flat (`googlemaps`); Complianz hyphenates (`google-maps`). An 71 * unmapped service keeps its own name, which is right for `youtube` and 72 * `vimeo` and harmless for a tour provider the CMP has never heard of -- 73 * the category then decides. 74 * 75 * @param {string} provider Provider key. 76 * @param {string} service Our service slug. 77 * @return {string} The CMP's name for it. 78 */ 79 function serviceName( provider, service ) { 80 const names = config().serviceNames; 81 const forProvider = names && names[ provider ]; 82 return ( forProvider && forProvider[ service ] ) || service; 83 } 84 85 // ----------------------------------------------------------------------- 86 // Our own store. Kept in localStorage, same key and shape as before, so an 87 // existing visitor's consent survives the update. 88 // ----------------------------------------------------------------------- 89 90 function readOwn() { 91 try { 92 const raw = window.localStorage.getItem( STORAGE_KEY ); 93 return raw 94 ? new Map( Object.entries( JSON.parse( raw ) ) ) 95 : new Map(); 96 } catch ( error ) { 97 // Private mode, disabled storage, or corrupt JSON. Treat as "no 98 // consent yet" rather than throwing out of every caller. 99 return new Map(); 100 } 101 } 102 103 function writeOwn( consents ) { 104 try { 105 window.localStorage.setItem( 106 STORAGE_KEY, 107 JSON.stringify( Object.fromEntries( consents ) ) 108 ); 109 } catch ( error ) { 110 // Nothing to do: the gate still works for this page view. 111 } 112 } 113 114 // ----------------------------------------------------------------------- 115 // CMP providers. 116 // ----------------------------------------------------------------------- 117 118 /** 119 * The consent type a CMP has declared, from either place it can appear. 120 * 121 * `window.wp_consent_type` is set by the CMP at runtime; the fallback is 122 * filled from PHP for a CMP that only integrates server-side. Empty in 123 * both means no CMP has claimed responsibility. 124 * 125 * @return {string} The consent type, or '' when no CMP declared one. 126 */ 127 function consentType() { 128 return window.wp_consent_type || window.wp_fallback_consent_type || ''; 129 } 130 131 const complianz = { 132 key: 'complianz', 133 134 available() { 135 return 'function' === typeof window.cmplz_has_consent; 136 }, 137 138 hasConsent( service ) { 139 const category = categoryFor( service ); 140 if ( 'function' === typeof window.cmplz_has_service_consent ) { 141 // Falls back to the category itself for a service Complianz 142 // does not track individually. 143 return !! window.cmplz_has_service_consent( 144 serviceName( 'complianz', service ), 145 category 146 ); 147 } 148 return !! window.cmplz_has_consent( category ); 149 }, 150 151 subscribe( onChange ) { 152 document.addEventListener( 'cmplz_status_change', onChange ); 153 document.addEventListener( 154 'cmplz_status_change_service', 155 onChange 156 ); 157 }, 158 159 /** 160 * The site's own "manage consent" trigger, wherever the owner put it. 161 * 162 * @return {Element|null} The trigger, or null when the site has none. 163 */ 164 trigger() { 165 return document.querySelector( 166 '#cmplz-manage-consent .cmplz-manage-consent, .cmplz-manage-consent' 167 ); 168 }, 169 170 canOpenSettings() { 171 return ( 172 null !== this.trigger() || 173 ( 'function' === typeof window.cmplz_set_banner_status && 174 'function' === typeof window.conditionally_show_banner ) 175 ); 176 }, 177 178 openSettings() { 179 // Prefer the site's own trigger: it does exactly what the site 180 // intends, wherever the owner placed it. 181 const trigger = this.trigger(); 182 if ( trigger ) { 183 trigger.click(); 184 return true; 185 } 186 // Otherwise re-arm the banner and ask Complianz to show it. 187 if ( 188 'function' === typeof window.cmplz_set_banner_status && 189 'function' === typeof window.conditionally_show_banner 190 ) { 191 window.cmplz_set_banner_status( 'show' ); 192 window.conditionally_show_banner(); 193 return true; 194 } 195 return false;
196 }, 197 }; 198 199 const wpConsentApi = { 200 key: 'wp-consent-api', 201 202 /** 203 * The function existing is NOT enough. 204 * 205 * `wp_has_consent()` returns **true** for every category when no CMP 206 * is installed -- "no consent management, activate all cookies". Were 207 * we to treat that as an answer, every site that has the WP Consent 208 * API plugin but no banner would silently lose its only gate. A 209 * declared consent type is what proves a CMP is actually in charge. 210 * 211 * @return {boolean} Whether a CMP answers through this API. 212 */ 213 available() { 214 return ( 215 'function' === typeof window.wp_has_consent && 216 '' !== consentType() 217 ); 218 }, 219 220 hasConsent( service ) { 221 if ( 'function' === typeof window.wp_has_service_consent ) { 222 return !! window.wp_has_service_consent( 223 serviceName( 'wp-consent-api', service ) 224 ); 225 } 226 return !! window.wp_has_consent( categoryFor( service ) ); 227 }, 228 229 subscribe( onChange ) { 230 document.addEventListener( 231 'wp_listen_for_consent_change', 232 onChange 233 ); 234 document.addEventListener( 235 'wp_consent_api_status_change_service', 236 onChange 237 ); 238 }, 239 240 canOpenSettings() { 241 // The API standardises reading consent, not reopening a banner. 242 return false; 243 }, 244 245 openSettings() { 246 return false; 247 }, 248 }; 249 250 /** 251 * The CMP in charge, if any. 252 * 253 * Complianz is asked first: it answers on its own, whereas the WP Consent 254 * API needs a separate bridge plugin that Complianz sites often do not 255 * have installed. 256 * 257 * @return {object|null} The provider, or null when we gate this ourselves. 258 */ 259 function resolveProvider() { 260 if ( 'auto' !== mode() ) { 261 return null; 262 } 263 if ( complianz.available() ) { 264 return complianz; 265 } 266 if ( wpConsentApi.available() ) { 267 return wpConsentApi; 268 } 269 return null; 270 } 271 272 let provider = resolveProvider(); 273 let subscribed = false; 274 275 /** 276 * Re-resolve once, for a CMP whose script loads after ours. 277 * 278 * Registration order is not ours to control, and an optimiser may defer 279 * either script. Everything that reads consent goes through here, so a CMP 280 * that shows up late is picked up on the next question rather than being 281 * missed for the whole page view. 282 * 283 * @return {object|null} The provider, or null. 284 */ 285 function currentProvider() { 286 if ( ! provider ) { 287 provider = resolveProvider(); 288 } 289 if ( provider && ! subscribed ) { 290 subscribed = true; 291 provider.subscribe( notifyAll ); 292 } 293 return provider; 294 } 295 296 /** 297 * Whether a service may load right now. 298 * 299 * @param {string} service Our service slug. 300 * @return {boolean} True when the visitor has consented. 301 */ 302 function hasConsent( service ) { 303 if ( 'off' === mode() ) { 304 // The owner told us their consent plugin blocks this content 305 // itself, so we must not gate it a second time. 306 return true; 307 } 308 const active = currentProvider(); 309 return active 310 ? active.hasConsent( service ) 311 : !! readOwn().get( service ); 312 } 313 314 function runCallbacks( service ) { 315 const callbacks = waiting.get( service ); 316 if ( ! callbacks ) { 317 return; 318 } 319 // Take the list before running: a callback may subscribe again, and 320 // that subscription belongs to the next round, not this one. 321 waiting.set( service, [] ); 322 callbacks.forEach( function ( callback ) { 323 callback(); 324 } ); 325 } 326 327 /** A CMP changed something -- re-check every service anyone is waiting on. */ 328 function notifyAll() { 329 Array.from( waiting.keys() ).forEach( function ( service ) { 330 if ( hasConsent( service ) ) { 331 runCallbacks( service ); 332 } 333 } ); 334 } 335 336 window.ImmomaklerConsent = { 337 /** 338 * Record the visitor's consent for a service. 339 * 340 * With a CMP in charge this deliberately stores nothing: consent is 341 * that plugin's to collect and to prove. We open its settings instead, 342 * which is why the map gates keep working unchanged -- their accept 343 * button now reopens the banner rather than writing a flag we have no 344 * right to write. 345 * 346 * @param {string} service Our service slug. 347 * @return {boolean} True when consent was stored, false when the 348 * visitor was sent to the CMP instead. 349 */ 350 accept( service ) { 351 const active = currentProvider(); 352 if ( active ) { 353 active.openSettings(); 354 return false;
355 } 356 const consents = readOwn(); 357 consents.set( service, Date.now() ); 358 writeOwn( consents ); 359 runCallbacks( service ); 360 return true; 361 }, 362 363 /** 364 * Forget our own consent. A no-op while a CMP is in charge. 365 * 366 * @param {string} [service] Service to clear, or all when omitted. 367 */ 368 clear( service ) { 369 if ( currentProvider() ) { 370 return; 371 } 372 if ( ! service ) { 373 writeOwn( new Map() ); 374 return; 375 } 376 const consents = readOwn(); 377 if ( consents.delete( service ) ) { 378 writeOwn( consents ); 379 } 380 }, 381 382 /** 383 * Resolves once the service may load -- now, or when consent arrives. 384 * 385 * @param {string} service Our service slug. 386 * @return {Promise<void>} Resolved on consent. 387 */ 388 consentFor( service ) { 389 return new Promise( function ( resolve ) { 390 if ( hasConsent( service ) ) { 391 resolve(); 392 return; 393 } 394 waiting.set( 395 service, 396 ( waiting.get( service ) || [] ).concat( resolve ) 397 ); 398 // Make sure we are subscribed to the CMP's change events even 399 // if nothing has asked a question yet. 400 currentProvider(); 401 } ); 402 }, 403 404 /** 405 * @return {Object} Service slug -> truthy when consented. 406 */ 407 getConsents() { 408 const active = currentProvider(); 409 if ( ! active && 'off' !== mode() ) { 410 return Object.fromEntries( readOwn() ); 411 } 412 // A CMP answers per service rather than handing out a list, so 413 // report on the services this site actually embeds. 414 const result = {}; 415 ( config().services || [] ).forEach( function ( service ) { 416 if ( hasConsent( service ) ) { 417 result[ service ] = true; 418 } 419 } ); 420 return result; 421 }, 422 423 /** 424 * @param {string} service Our service slug. 425 * @return {boolean} Whether the service may load. 426 */ 427 hasConsent, 428 429 /** 430 * @return {boolean} Whether a consent plugin owns the decision. 431 */ 432 isManagedExternally() { 433 return null !== currentProvider(); 434 }, 435 436 /** 437 * @return {string} 'complianz', 'wp-consent-api' or '' for our own gate. 438 */ 439 providerName() { 440 const active = currentProvider(); 441 return active ? active.key : ''; 442 }, 443 444 /** 445 * Whether we can offer the visitor a way into the CMP's own dialog. 446 * 447 * A gate that shows a button doing nothing is worse than one that 448 * explains the situation, so callers ask before rendering the button. 449 * 450 * @return {boolean} Whether `openSettings()` would get anywhere. 451 */ 452 canOpenSettings() { 453 const active = currentProvider(); 454 return active ? active.canOpenSettings() : false; 455 }, 456 457 /** 458 * Open the CMP's own consent dialog. 459 * 460 * @return {boolean} False when there is no CMP, or it offers no way in. 461 */ 462 openSettings() { 463 const active = currentProvider(); 464 return active ? active.openSettings() : false; 465 }, 466 }; 467} )();
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.