PageSourceSearch

https://open2chat.at/typo3temp/assets/sg_cookie_optin/siteroot-18101/cookieOptin.js?1774361690

js open2chat.at collected 2026-10-06 10:58:46 UTC 88,731 bytes, 2,521 lines download raw bytes

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.