PageSourceSearch

https://kobus-immobilien.de/wp-content/plugins/immomakler/assets/js/immomakler-consent.js?ver=5.69.0

js kobus-immobilien.de collected 2026-10-06 22:22:47 UTC 13,311 bytes, 467 lines download raw bytes

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.