PageSourceSearch

https://www.sae.edu/deu/wp-content/cache/www.sae.edu/wpfc-minified/dhwwk2qc/2kyqf.js

js sae.edu collected 2026-09-24 08:31:48 UTC 104,414 bytes, 3,182 lines download raw bytes

1// source --> /wp-content/themes/sae-2021/resources/js/toast.script.js 
2(function(){
3    "use strict";
4    $.Toast = function(title, message, type, options){
5        var defaultOptions = {
6            appendTo: "body",
7            stack: false,
8            position_class: "toast-bottom-right",
9            fullscreen:false,
10            width: 250,
11            spacing:20,
12            timeout: 4000,
13            has_close_btn:true,
14            has_icon:true,
15            sticky:false,
16            border_radius:6,
17            has_progress:false,
18            rtl:false
19        }
20
21        var $element = null;
22
23        var $options = $.extend(true, {}, defaultOptions, options);
24
25        var spacing = $options.spacing;
26
27        var css = {
28            "position":($options.appendTo == "body") ? "fixed" : "absolute",
29            "min-width":$options.width,
30            "display":"none",
31            "border-radius":$options.border_radius,
32            "z-index":99999
33        }
34
35        $element = $('<div class="toast-item-wrapper ' + type + ' ' + $options.position_class + '"></div>');
36        $('<p class="toast-title">' + title + '</p>').appendTo($element);
37        $('<p class="toast-message">' + message + '</p>').appendTo($element);
38
39        if($options.fullscreen){
40            $element.addClass( "fullscreen" );
41        }
42
43        if($options.rtl){
44            $element.addClass( "rtl" );
45        }
46
47        if($options.has_close_btn){
48            $('<span class="toast-close">&times;</span>').appendTo($element);
49            if( $options.rtl){
50                css["padding-left"] = 20;
51            } else {
52                css["padding-right"] = 20;
53            }
54        }
55
56        if($options.has_icon){
57            $('<i class="toast-icon toast-icon-' + type + '"></i>').appendTo($element);
58            if( $options.rtl){
59                css["padding-right"] = 50;
60            } else {
61                css["padding-left"] = 50;
62            }            
63        }
64
65        if($options.has_progress && $options.timeout > 0){
66            $('<div class="toast-progress"></div>').appendTo($element);
67        }
68
69        if($options.sticky){
70            $options.spacing = 0;
71            spacing = 0;
72
73            switch($options.position_class){
74                case "toast-top-left" : {
75                    css["top"] = 0;
76                    css["left"] = 0;
77                    break;
78                }
79                case "toast-top-right" : {
80                    css["top"] = 0;
81                    css["left"] = 0;                    
82                    break;
83                }
84                case "toast-top-center" : {
85                    css["top"] = 0;
86                    css["left"] = css["right"] = 0;  
87                    css["width"] = "100%";                  
88                    break;
89                }
90                case "toast-bottom-left" : {
91                    css["bottom"] = 0;
92                    css["left"] = 0;                     
93                    break;
94                }
95                case "toast-bottom-right" : {
96                    css["bottom"] = 0;
97                    css["right"] = 0;                     
98                    break;
99                }
100                case "toast-bottom-center" : {
101                    css["bottom"] = 0;
102                    css["left"] = css["right"] = 0;  
103                    css["width"] = "100%";                     
104                    break;
105                }
106                default : {
107                    break;
108                }                                                                        
109            }
110        }
111
112        if($options.stack){
113            if($options.position_class.indexOf("toast-top") !== -1 ){
114                $($options.appendTo).find('.toast-item-wrapper').each(function(){
115                    css["top"] = parseInt($(this).css("top")) + this.offsetHeight + spacing;
116                });
117            } else if($options.position_class.indexOf("toast-bottom") !== -1 ){
118                $($options.appendTo).find('.toast-item-wrapper').each(function(){
119                    css["bottom"] = parseInt($(this).css("bottom")) + this.offsetHeight + spacing;
120                });
121            }
122        }        
123
124        $element.css(css);
125
126        $element.appendTo($options.appendTo);
127
128		if($element.fadeIn) {
129            $element.fadeIn();
130        }else {
131            $alert.css({display: 'block', opacity: 1});
132        }
133
134		function removeToast(){          
135			$.Toast.remove( $element );
136		}
137
138		if($options.timeout > 0){
139			setTimeout(removeToast, $options.timeout);
140            if($options.has_progress){
141                $(".toast-progress", $element).animate({"width":"100%"}, $options.timeout);
142            }
143		}        
144
145        $(".toast-close", $element).click(removeToast)
146
147        return $element;
148    }
149
150    $.Toast.remove = function( $element ){
151        "use strict";        
152		if($element.fadeOut)
153		{
154			$element.fadeOut(function(){
155				return $element.remove();
156			});
157		}
158		else{
159			$element.remove();
160		}        
161    }
162})();
163// source --> /wp-content/themes/sae-2021/resources/js/core.js?v=188 
164/**
165 * Extract the value of an url parameter
166 * @param {string} parameterName parameter to be matched
167 * @param {string} url (optional) url to scan for get parameter. Defaults to 'location.search' if not provided
168 * @returns {string} parameterValue if parameter is set (ex. ?foo=bar) OR
169 * @returns {string} ''(empty string) if parameterValue is empty (ex. ?foo=) OR
170 * @returns {object} null if parameterValue is not set (ex. ?foo) OR
171 * @returns {bool} false if parameter is not set (ex. ?)
172 */
173function getValueOfUrlParameter(parameterName, url = location.search){
174	parameterName = parameterName.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]")
175	let regexSearch = "[\\?&]" + parameterName + "=([^&#]*)"
176	let regex = new RegExp(regexSearch)
177	if(parameter = regex.exec(url)){
178		return decodeURIComponent(parameter[1])
179	}
180	regexSearch = "[\\?&]" + parameterName + "([^&#]*)"
181	regex = new RegExp(regexSearch)
182	parameter = regex.exec(url)
183	return parameter && parameterName === parameter[0].slice(1) ? null : false
184}
185function getCookie(cname) {
186	let name = cname + "=";
187	let ca = document.cookie.split(';');
188	for(let i = 0; i < ca.length; i++) {
189		let c = ca[i];
190		while (c.charAt(0) == ' ') {
191			c = c.substring(1);
192		}
193		if (c.indexOf(name) == 0) {
194			return c.substring(name.length, c.length);
195		}
196	}
197	return "";
198}
199/**
200 *
201 * @param {Element} element – The DOM node to scroll to
202 * @param {Number} offsetTopY – Optional offset between
203 *
204 */
205function scrollAnimatedToElement(element, offsetTopY){
206	offsetTopY = (typeof offsetTopY !== 'undefined') ? offsetTopY : 50;
207	var headerHeight = document.querySelector('.header-container').offsetHeight;
208	$('html,body').stop().animate({scrollTop: $(element).offset().top - headerHeight - offsetTopY});
209}
210
211function addParameterStringToUrlSave(targetUrl, stringToAdd){
212	if(['', '?', '&'].indexOf(stringToAdd) === -1){
213		if(['?', '&'].indexOf(stringToAdd[0]) !== -1){
214			stringToAdd = stringToAdd.slice(1);
215		}
216		return (targetUrl.indexOf('?') === -1) ? targetUrl + '?' + stringToAdd : targetUrl + '&' + stringToAdd;
217	}
218	return targetUrl;
219}
220
221// get it from the DEV Menu in the gate mapping
222// https://docs.google.com/spreadsheets/d/1uCscd6X7Gwq9cgCQ51iA3p2SD7U0DD9v1GHL1gwfXe4/edit
223// v2.1.
224const gateMap = {
225	"Audio & Music": ["Audio Engineering", "Audio Engineering Diploma", "Audio Engineering Advanced Diploma", "BA/BSc (Hons) Audio Production", "Electronic Music Production Certificate", "Hip Hop & RnB Producer Certificate", "Home Studio Recording Certificate", "Songwriting Certificate", "Music Business", "Music Business Diploma", "Music Business Advanced Diploma", "BA/BSc (Hons) Music Business", "Creative Industries", "Event Engineering Diploma", "Event Engineering", "Event Engineering Advanced Diploma", "BA/BSc (Hons) Event Engineering", "SQQ7 Certificate", "Voice Acting", "Voice Acting Diploma", "Voice", "Music Production", "Sound Selling Certificate", "DJ Kurs", "Music Production & Sound Design", "Artist & Media Management", "Songwriting", "Popular Music Performance"],
226	"Audio Engineering": ["Audio Engineering Diploma", "Audio Engineering Advanced Diploma", "BA/BSc (Hons) Audio Production", "Electronic Music Production Certificate", "Hip Hop & RnB Producer Certificate", "Home Studio Recording Certificate", "Songwriting Certificate", "Music Production", "DJ Kurs", "Music Production & Sound Design", "Songwriting", "Popular Music Performance"],
227	"Media Business & Marketing": ["Audio Engineering", "Songwriting Certificate", "Music Business", "Music Business Diploma", "Music Business Advanced Diploma", "BA/BSc (Hons) Music Business", "Webdesign & Development", "Webdesign & Development Diploma", "Webdesign & Development Advanced Diploma", "BA/BSc (Hons) Web Development", "Content Creation & Online Marketing", "Designsysteme in Tailwind CSS Certificate", "Content Creation & Online Marketing Diploma", "Content Creation & Online Marketing Advanced Diploma", "BA/BSc (Hons) Content Creation and Online Marketing", "Voice Acting", "Voice Acting Diploma", "Voice", "Graphic Design", "Graphic Design Diploma", "Design", "Graphic Design Advanced Diploma", "BA/BSc (Hons) Graphic Design", "Design Essentials Certificate", "Fotografie Kurzkurs", "Grafik Design", "Social Media Manager", "Sound Selling Certificate", "Creative Photography & AI Technologies", "Film Production", "2D Animation & Storytelling", "Artist & Media Management"],
228	"Music Business": ["Music Business Diploma", "Music Business Advanced Diploma", "BA/BSc (Hons) Music Business", "Sound Selling Certificate"],
229	"Film & Visual Arts": ["Creative Industries", "Event Engineering Diploma", "Event Engineering", "Event Engineering Advanced Diploma", "BA/BSc (Hons) Event Engineering", "SQQ7 Certificate", "Game Art & 3D Animation", "Game Art & 3D Animation Diploma", "Game Art & 3D Animation Advanced Diploma", "BA/BSc (Hons) Game Art Animation", "Game Design Certificate", "Game Creature Creation Certificate", "3D Printing Certificate", "Content Creation & Online Marketing", "Content Creation & Online Marketing Diploma", "Content Creation & Online Marketing Advanced Diploma", "BA/BSc (Hons) Content Creation and Online Marketing", "Graphic Design", "Graphic Design Diploma", "Design", "Graphic Design Advanced Diploma", "BA/BSc (Hons) Graphic Design", "Design Essentials Certificate", "Film Production", "Film Production Diploma", "Film Production Advanced Diploma", "BA/BSc (Hons) Film Production", "DaVinci Resolve Kompaktkurs Certificate", "DaVinci Resolve End User Certificate", "Certified Drone Pilot Certificate", "Music Video Production Certificate", "Video Production Certificate", "Animation & Visual Effects", "Visual FX & 3D Animation Diploma", "Animation & Visual Effects Diploma", "Visual FX & 3D Animation Advanced Diploma", "Animation & Visual Effects Advanced Diploma", "BA/BSc (Hons) Visual Effects Animation", "3D Animation Certificate", "Visual Motion Design", "Fotografie Kurzkurs", "Grafik Design", "Digital Designer", "Game Artist", "Indie Game Developer", "3D Production", "Blender Kurzkurs", "KI-Integriertes Drehbuch­schreiben Certificate", "Creative Photography & AI Technologies", "2D Animation & Storytelling"],
230	"Creative Industries": ["Event Engineering Diploma", "Event Engineering Advanced Diploma", "BA/BSc (Hons) Event Engineering", "SQQ7 Certificate"],
231	"Event Engineering": ["Event Engineering Diploma", "Event Engineering Advanced Diploma", "BA/BSc (Hons) Event Engineering", "SQQ7 Certificate"],
232	"Coding & Technology": ["Creative Technologies", "Software Engineering Diploma", "Software Engineering", "Software Engineering Advanced Diploma", "BA/BSc (Hons) Software Engineering", "Webdesign & Development", "Webdesign & Development Diploma", "Webdesign & Development Advanced Diploma", "BA/BSc (Hons) Web Development", "Content Creation & Online Marketing", "Designsysteme in Tailwind CSS Certificate", "Games Programming", "Games Programming Diploma", "Game Development", "Game Development Diploma", "Games Programming Advanced Diploma", "Game Development Advanced Diploma", "BSc (Hons) Games Programming", "Web Design Grundlagen", "Games Programming Zertifikat", "Digital Designer"],
233	"Creative Technologies": ["Software Engineering Diploma", "Software Engineering Advanced Diploma", "BA/BSc (Hons) Software Engineering"],
234	"Software Engineering": ["Software Engineering Diploma", "Software Engineering Advanced Diploma", "BA/BSc (Hons) Software Engineering"],
235	"Webdesign & Development": ["Webdesign & Development Diploma", "Webdesign & Development Advanced Diploma", "BA/BSc (Hons) Web Development", "Web Design Grundlagen"],
236	"Content Creation & Online Marketing": ["Designsysteme in Tailwind CSS Certificate", "Content Creation & Online Marketing Diploma", "Content Creation & Online Marketing Advanced Diploma", "BA/BSc (Hons) Content Creation and Online Marketing", "Design Essentials Certificate", "Fotografie Kurzkurs", "Grafik Design", "Digital Designer", "Social Media Manager", "Creative Photography & AI Technologies"],
237	"Games & Animation": ["Game Art & 3D Animation", "Game Art & 3D Animation Diploma", "Game Art & 3D Animation Advanced Diploma", "BA/BSc (Hons) Game Art Animation", "Zeichnen Certificate", "Game Design Certificate", "Game Creature Creation Certificate", "3D Printing Certificate", "Games Programming", "Games Programming Diploma", "Game Development", "Game Development Diploma", "Games Programming Advanced Diploma", "Game Development Advanced Diploma", "BSc (Hons) Games Programming", "Animation & Visual Effects", "Visual FX & 3D Animation Diploma", "Animation & Visual Effects Diploma", "Visual FX & 3D Animation Advanced Diploma", "Animation & Visual Effects Advanced Diploma", "BA/BSc (Hons) Visual Effects Animation", "3D Animation Certificate", "Visual Motion Design", "Games Programming Zertifikat", "Game Artist", "Indie Game Developer", "3D Production", "Blender Kurzkurs"],
238	"Game Art & 3D Animation": ["Game Art & 3D Animation Diploma", "Game Art & 3D Animation Advanced Diploma", "BA/BSc (Hons) Game Art Animation", "Zeichnen Certificate", "Game Design Certificate", "Game Creature Creation Certificate", "3D Printing Certificate", "Game Artist", "Indie Game Developer"],
239	"Games Programming": ["Games Programming Diploma", "BSc (Hons) Games Programming", "Games Programming Zertifikat"],
240	"Game Development": ["Game Development Diploma", "Games Programming Advanced Diploma", "Game Development Advanced Diploma", "BSc (Hons) Games Programming"],
241	"Voice Acting": ["Voice Acting Diploma"],
242	"Voice": ["Voice Acting Diploma"],
243	"Graphic Design": ["Graphic Design Diploma", "Graphic Design Advanced Diploma", "BA/BSc (Hons) Graphic Design"],
244	"Design": ["Graphic Design Diploma", "Graphic Design Advanced Diploma", "BA/BSc (Hons) Graphic Design"],
245	"Film Production": ["Film Production Diploma", "Film Production Advanced Diploma", "BA/BSc (Hons) Film Production", "DaVinci Resolve Kompaktkurs Certificate", "DaVinci Resolve End User Certificate", "Certified Drone Pilot Certificate", "Music Video Production Certificate", "Video Production Certificate", "KI-Integriertes Drehbuch­schreiben Certificate", "2D Animation & Storytelling"],
246	"Animation & Visual Effects": ["Visual FX & 3D Animation Diploma", "Animation & Visual Effects Diploma", "Visual FX & 3D Animation Advanced Diploma", "Animation & Visual Effects Advanced Diploma", "BA/BSc (Hons) Visual Effects Animation", "3D Animation Certificate", "Visual Motion Design", "3D Production", "Blender Kurzkurs"],
247	"Medienvorkurs": ["Medienvorkurs"],
248	"Music Production & Sound Design": ["Music Production & Sound Design"],
249	"Artist & Media Management": ["Artist & Media Management"]
250}
251
252
253function getLocaleStringBasedOnCountryData( blogId, language ){
254	language = ( language !== undefined ) ? language : ''
255	const countryData = {
256		'2' : 'DE',
257		'9' : 'IT',
258		'21' : 'NL',
259		'23' : 'AT',
260		'26' : 'CH',
261	}
262	return ( typeof countryData[ blogId ] !== 'undefined' && language !== '' ) ? language + '_' + countryData[ blogId ] : false
263}
264
265/* SAE-5580 */
266
267function expandTextIntroPreview( triggerElement ) {
268	let rootContainer = triggerElement.closest( '.text_intro-container' )
269
270	if( triggerElement.type === 'button' ) {
271		triggerElement.parentElement.removeChild( triggerElement )
272		let textIntro = rootContainer.querySelector( '.text_intro-text' )
273		textIntro.classList.remove( 'preview' )
274		textIntro.removeAttribute( 'title' )
275		textIntro.removeAttribute( 'data-click-purpose' )
276	}
277	else {
278		triggerElement.classList.remove( 'preview' )
279		triggerElement.removeAttribute( 'title' )
280		triggerElement.removeAttribute( 'data-click-purpose' )
281		let expandButton = rootContainer.querySelector( '.h-button--s_icon' )
282		expandButton.parentElement.removeChild( expandButton )
283	}
284}
285
286/* / SAE-5580 */
287
288$(function(){
289	const url = window.location.href;
290	const paths = window.location.pathname.split('/');
291	const countryPath = '/' + paths[1];
292	const languagePath = paths[2].length === 2 ? '/' + paths[2] : '';
293	const userFirstname = $('body').data('userfirstname');
294
295	console.log('MODE:', countryPath, languagePath)
296
297	if(parseInt(localStorage.getItem("displayWelcomeToast")) === 1){
298		var welcomeBackTitle = __ple("Howdy") + ' ' + userFirstname;
299		var welcomeBackMessage = __ple("Welcome back at SAE,") + " " + userFirstname + "!";
300
301		$.Toast(welcomeBackTitle, welcomeBackMessage, "success", {
302			has_icon: false,
303			has_close_btn: true,
304			stack: true,
305			fullscreen: false,
306			timeout: 18000,
307			sticky: false,
308			has_progress: true,
309			rtl: false,
310			position_class: 'toast-top-right'
311		});
312		localStorage.setItem("displayWelcomeToast", "0");
313	}
314
315	if(url.indexOf('permalogin') > 0){
316		localStorage.setItem("displayWelcomeToast", "1");
317		window.location.href = window.location.href.split('?')[0];
318	}
319
320	if(countryPath === '/deu' || countryPath === '/aut' || countryPath === '/che' || countryPath === '/ch-ge'){
321		if(countryPath !== '/deu'){
322			$('.um-field-gsacampus').hide();
323		}
324
325		$('.get_new_login_token_form .h-button').on('click', function(){
326			$.ajax({
327				type: 'GET',
328				dataType: 'json',
329				cache: false,
330				async: false,
331				url: 'https://www.sae.edu' + countryPath + languagePath + "?act=resendpermanlink&email=" + $('.email_reset_email').val().replace('+', '%2b'),
332				complete: function(datum){
333					if(countryPath.toLowerCase() === '/deu'){
334						window.location.href = 'https://www.sae.edu' + countryPath + "/login-started/";
335					}else{
336						alert(__ple('Thank you, if we were able to assign your e-mail address, you will receive your custom login link by e-mail in a few seconds.'));
337						window.location.href = 'https://www.sae.edu' + countryPath + languagePath;
338					}
339				}
340			});
341		});
342
343		$('body').attr('data-gsacampusid', countryPath.slice(1));
344
345		var userId = parseInt($('body').data('userid'));
346		if(userId > 0){
347			if(countryPath !== '/aut'){
348				// no enquire modal
349				$('.desktop_navigation_menu-enquire-btn').find('button').remove();
350				$('.mobile_navigation-actions').find('button').eq(0).remove();
351				if(countryPath !== '/ch-ge' && languagePath === ''){
352					const buttonCtaGsa = '<a id="header--book-a-tour-button" class="h-button h-button--v_contained h-button--light" data-url="' + countryPath + '/termin-vereinbaren" href="' + countryPath + '/termin-vereinbaren"><span class="h-button-content">' + __ple("Book appointment") + '</span></a>';
353					$('.desktop_navigation_menu-enquire-btn').append(buttonCtaGsa);
354					$('.mobile_navigation-actions').prepend(buttonCtaGsa);
355				}
356			}
357		}
358
359		// SAE GSA specific - add url into url-field of registration form
360		$('.um-field.um-field-url.um-field-gsauser_url.um-field-url.um-field-type_url').find('input').val(window.location.href);
361		// SAE GSA specific - add consent into textarea of registration form
362		$consentTextBlock = $('.um-field.um-field-textarea.um-field-gsaconsent_text.um-field-textarea.um-field-type_textarea');
363		if($consentTextBlock.prev().length){
364			$consentTextBlock.find('textarea').val($consentTextBlock.prev().text());
365		}
366
367		// SAE GSA specific - forward tracking cookies into the hidden gsauser_cookies field of the registration form
368		function fillGsauserCookiesField(){
369			var adStorage = null;
370			if(typeof dataLayer !== 'undefined' && Array.isArray(dataLayer)){
371				for(var i = dataLayer.length - 1; i >= 0; i--){
372					var entry = dataLayer[i];
373					if(entry && entry[0] === 'consent' && entry[2] && typeof entry[2] === 'object' && 'ad_storage' in entry[2]){
374						adStorage = entry[2].ad_storage;
375						break;
376					}
377				}
378			}
379
380			var cookies = {'_sc_ad_storage': adStorage};
381			if(adStorage === 'granted'){
382				var cookieNames = ['_fbc', '_scid_gcl', '_scid_gbr', '_scid_wbr', 'ttclid'];
383				document.cookie.split(';').forEach(function(part){
384					var eq = part.indexOf('=');
385					if(eq === -1){
386						return;
387					}
388					var name = part.slice(0, eq).trim();
389					if(cookieNames.indexOf(name) === -1){
390						return;
391					}
392					cookies[name] = decodeURIComponent(part.slice(eq + 1));
393				});
394			}
395
396			var $cookieField = $('.um-field-gsauser_cookies input');
397			if($cookieField.length){
398				$cookieField.val(JSON.stringify(cookies));
399			}
400		}
401		const buggySubmitInput = document.querySelector('input[type="submit"]');
402		if(buggySubmitInput){
403			buggySubmitInput.addEventListener('click', fillGsauserCookiesField);
404		}
405
406		if($('.um-3820').length || $('.um-3821').length || $('.um-12068').length){ // /GSA/de || /GSA/en || /deu/dev
407			// SAE GSA pre-select course on course-pages by discipline
408			if($('.gate_introduction').length || $('.gate_form_introduction').length){
409				let course = $('.course_hero-svg-icon h4').text().trim();
410				course = course === 'Audio Engineering / Music Production' ? 'Audio Engineering' : course;
411				if(course && $("#gsacourse option[value='" + course + "']").length > 0){
412					$('#gsacourse').val(course).trigger('change');
413					$('.um-field-gsacourse').hide();
414				}
415			}
416			// SAE Gate pre-select campus
417			let campus = getValueOfUrlParameter('saecampus') || document.referrer.match(/campuses\/(\w*)/)
418			campus = campus && typeof campus == 'object' ? campus[1] : campus
419			campus = campus ? campus[0].toUpperCase() + campus.slice(1).replace('ue', 'ü').replace('oe', 'ö') : false
420			if(campus && $("#gsacampus option[value='" + campus + "']").length > 0){
421				$('#gsacampus').val(campus).trigger('change');
422			}
423
424		}
425		// SAE Gate pre-select cluster / discipline / course
426		if($('#gate_controller').length){
427			let parentValue = false
428			for(key of ['clt', 'dcp', 'crs']){
429				let val = getValueOfUrlParameter(key)
430				val = val === null ? '' : val
431				val = !val && typeof sae !== 'undefined' && typeof sae.gate !== 'undefined' && typeof sae.gate[key] !== 'undefined' ? sae.gate[key] : val
432				if(val !== false){
433					val = val.replace(/\s?\+\s?/, ' & ')
434					if($('#gate_controller option[value="' + key + '"]').length > 0){
435						$('#gate_controller').val(key).trigger('change');
436					}
437					if($('#' + key + ' option[value="' + val + '"]').length > 0){
438						$('#' + key).val(val).trigger('change');
439						if(!parentValue && key == 'crs'){
440							//$('#crs').prop('disabled', true) /* SAE2030 */
441						}
442					}
443					if(parentValue){
444						for(let option of $('#' + key).children()){
445							if(option.value !== '' && !gateMap[parentValue].includes(option.value)){
446								option.remove()
447							}
448						}
449					}
450					parentValue = val;
451				}
452			}
453		}
454
455		if(null !== document.getElementById('further-education-detail-page-primary-card-cta')){
456			(function(furtherEducationDetailPagePrimaryCardCta){
457				if(null !== document.querySelector('.eventbrite_tiles-section')){
458					var eventbriteTilesSection = document.querySelector('.eventbrite_tiles-section');
459					furtherEducationDetailPagePrimaryCardCta.querySelector('span').textContent = 'Kurs buchen';
460					furtherEducationDetailPagePrimaryCardCta.href = '#';
461					['touched', 'click'].forEach(event => {
462						furtherEducationDetailPagePrimaryCardCta.addEventListener(event, function(){
463							scrollAnimatedToElement(eventbriteTilesSection);
464							event.preventDefault();
465						});
466					});
467				}
468			}(document.getElementById('further-education-detail-page-primary-card-cta')));
469		}
470
471		/*
472		// deprecated: SAE-2118
473		$('#um-submit-btn').on('click', function(){
474			formID = $(this).parents('form').children('[name=form_id]').val();
475			switch(formID){
476				case '3149': // Registration DE
477				case '3353': // Registration EN
478					window.dataLayer.push({event: 'CourseCTA registration'});
479					break;
480				case '3820': // Registration on enquire now DE
481				case '3821': // Registration on enquire now EN
482					window.dataLayer.push({event: 'HeaderCTA registration'});
483					break;
484			}
485		});
486		*/
487
488	}
489
490	/**
491	 * Scroll to a standard form component container if the get parameter 'stf' is not null ( e.g.: ?stf=1 )
492	 */
493	if(getValueOfUrlParameter('stf') !== null && getValueOfUrlParameter('stf') !== false && $('[data-scroll-form-anchor]').length > 0){
494		setTimeout(function(){
495			scrollAnimatedToElement('[data-scroll-form-anchor]', 0);
496		}, 1500);
497	}
498
499	/**
500	 * SAE-1981
501	 */
502	let userAccountMenus = Array.from(document.getElementsByClassName('user-account-menu'));
503	if(userAccountMenus.length > 0){
504		let desktopNavigationMenuUserAccountBackdrop = document.getElementById('desktop_navigation_menu-user-account-backdrop');
505		let userAccountNavigationTriggers = {
506			'Activation': [
507				document.getElementById('desktop_navigation_menu-user-account-btn'),
508				document.getElementById('user-logged-in-as-hint'),
509				document.getElementById('mobile_navigation_menu-user-account-btn')
510			],
511			'Deactivation': [
512				document.getElementById('desktop_navigation_menu-user-account-backdrop'),
513				document.querySelector('.mobile_navigation_menu-user-account-container [data-trigger=mobile_navigation_menu-user-account-close-btn]')
514			]
515		}
516		Object.keys(userAccountNavigationTriggers).forEach(function(key){
517			userAccountNavigationTriggers[key].forEach(function(triggerItem){
518				triggerItem.addEventListener('click', function(){
519					userAccountMenus.forEach(function(userAccountMenu){
520						if(key === 'Activation'){
521							userAccountMenu.classList.add('user-account-menu-active');
522						}else if(key === 'Deactivation'){
523							userAccountMenu.classList.remove('user-account-menu-active');
524						}
525					});
526					if(key === 'Activation'){
527						desktopNavigationMenuUserAccountBackdrop.classList.add('user-account-menu-active');
528						document.body.classList.add('overflow-hidden');
529					}else if(key === 'Deactivation'){
530						desktopNavigationMenuUserAccountBackdrop.classList.remove('user-account-menu-active');
531						document.body.classList.remove('overflow-hidden');
532					}
533				});
534			});
535		});
536	}
537
538	/* SAE-2363 */
539	function updateProgressUi(progressUi){
540		let body = document.body;
541		let scrollProgress = (window.pageYOffset / (body.scrollHeight - window.innerHeight - document.querySelector('footer.footer-container').offsetHeight)) * 100;
542		if(scrollProgress > 100){
543			scrollProgress = 100;
544		}
545		progressUi.style = 'width:' + scrollProgress + '%';
546	}
547
548	function generateProgressUi(config){
549		let body = document.body;
550		if(config[body.dataset['blogId']]){
551			let post_id = parseInt(body.dataset['postId']);
552			if(config[body.dataset['blogId']].indexOf(post_id) === -1){
553				let progress_ui = document.createElement('div');
554				progress_ui.classList.add('progress-ui');
555				progress_ui.id = 'progress-ui';
556				progress_ui = body.appendChild(progress_ui);
557
558				updateProgressUi(progress_ui);
559				window.addEventListener('scroll', function(){
560					updateProgressUi(progress_ui);
561				});
562				window.addEventListener('resize', function(){
563					updateProgressUi(progress_ui);
564				});
565			}
566		}
567	}
568
569	// sae.progress_ui_data = { 26 : [ 18495 ] } => { {{blog-id}}: [ comma separated list of all post-ids that shall be excluded ] }
570	// add data at Theme Settings > Tracking Scripts > Footer Tracking Scripts
571	// TODO: create backend interface to make sae.progress_ui_data editable for authors
572	if(sae.progress_ui_data){
573		generateProgressUi(sae.progress_ui_data);
574	}
575	/* /SAE-2363 */
576
577
578	/* SAE-3122 */
579	if(countryPath === '/deu' && sae && sae.userData && sae.userData.hash){
580		const drecksDownloadButton = $('button[data-download-course-structure-url]')
581		drecksDownloadButton.parent().html(drecksDownloadButton.parent().html()).children().on('click', () => { // course page
582			$.ajax({
583				'url': '/api/wp-wp.php',
584				'data': {user: sae.userData.id, hash: sae.userData.hash, task: 'download', data: 'coursePDF'}
585			}).always(() => {
586				location.href = drecksDownloadButton.data('downloadCourseStructureUrl')
587			})
588		})
589		$('.sidepanel_content-fees_download a').on('click', () => { // course page
590			$.ajax({
591				'url': '/api/wp-wp.php',
592				'data': {user: sae.userData.id, hash: sae.userData.hash, task: 'download', data: 'coursePaymentPlanPDF'}
593			})
594		})
595		$('a.paymentPlanPDF').on('click', (event) => { // (gated) payment plan page
596			event.preventDefault()
597			$.ajax({
598				'url': '/api/wp-wp.php',
599				'data': {user: sae.userData.id, hash: sae.userData.hash, task: 'download', data: 'paymentPlanPDF'}
600			}).always(() => {
601				location.href = event.currentTarget.href
602			})
603		})
604	}
605
606	/* SAE-4462 */
607	(function( blogId, languageCode ) {
608		let correctLocaleString = getLocaleStringBasedOnCountryData( blogId, languageCode )
609		if( correctLocaleString !== false ) {
610			let htmlNode = document.querySelector( 'html' )
611			if( htmlNode.getAttribute( 'lang' ) !== correctLocaleString ) {
612				htmlNode.setAttribute( 'lang', correctLocaleString )
613			}
614		}
615	})( sae.siteData.blogId, sae.siteData.polylangLanguageCode )
616	/* SAE-4462 */
617
618
619	/* SAE-5580 */
620	if( document.querySelectorAll( '.text_intro-section [data-click-purpose=expand-preview]' ).length > 0 ) {
621		let textIntroPreviewTriggers = Array.from( document.querySelectorAll( '.text_intro-section [data-click-purpose=expand-preview]' ) )
622		textIntroPreviewTriggers.forEach( function( trigger ) {
623			trigger.addEventListener( 'click', function( event ) {
624				expandTextIntroPreview( event.target )
625			})
626		})
627	}
628	/* / SAE-5580 */
629
630});
631
632window.onload = function(e){
633	// set this class for tracking clicks on WA messenger people button
634	setTimeout(function(){
635		$('.MuiButtonBase-root').find('path').attr('class', 'messengerpeople');
636	}, 3000);
637
638};
639// source --> /wp-content/themes/sae-2021/resources/js/urijs.js 
640/*!
641 * URI.js - Mutating URLs
642 *
643 * Version: 1.19.10
644 *
645 * Author: Rodney Rehm
646 * Web: http://medialize.github.io/URI.js/
647 *
648 * Licensed under
649 *   MIT License http://www.opensource.org/licenses/mit-license
650 *
651 */
652(function (root, factory) {
653  'use strict';
654  // https://github.com/umdjs/umd/blob/master/returnExports.js
655  if (typeof module === 'object' && module.exports) {
656    // Node
657    module.exports = factory(require('./punycode'), require('./IPv6'), require('./SecondLevelDomains'));
658  } else if (typeof define === 'function' && define.amd) {
659    // AMD. Register as an anonymous module.
660    define(['./punycode', './IPv6', './SecondLevelDomains'], factory);
661  } else {
662    // Browser globals (root is window)
663    root.URI = factory(root.punycode, root.IPv6, root.SecondLevelDomains, root);
664  }
665}(this, function (punycode, IPv6, SLD, root) {
666  'use strict';
667  /*global location, escape, unescape */
668  // FIXME: v2.0.0 renamce non-camelCase properties to uppercase
669  /*jshint camelcase: false */
670
671  // save current URI variable, if any
672  var _URI = root && root.URI;
673
674  function URI(url, base) {
675    var _urlSupplied = arguments.length >= 1;
676    var _baseSupplied = arguments.length >= 2;
677
678    // Allow instantiation without the 'new' keyword
679    if (!(this instanceof URI)) {
680      if (_urlSupplied) {
681        if (_baseSupplied) {
682          return new URI(url, base);
683        }
684
685        return new URI(url);
686      }
687
688      return new URI();
689    }
690
691    if (url === undefined) {
692      if (_urlSupplied) {
693        throw new TypeError('undefined is not a valid argument for URI');
694      }
695
696      if (typeof location !== 'undefined') {
697        url = location.href + '';
698      } else {
699        url = '';
700      }
701    }
702
703    if (url === null) {
704      if (_urlSupplied) {
705        throw new TypeError('null is not a valid argument for URI');
706      }
707    }
708
709    this.href(url);
710
711    // resolve to base according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#constructor
712    if (base !== undefined) {
713      return this.absoluteTo(base);
714    }
715
716    return this;
717  }
718
719  function isInteger(value) {
720    return /^[0-9]+$/.test(value);
721  }
722
723  URI.version = '1.19.10';
724
725  var p = URI.prototype;
726  var hasOwn = Object.prototype.hasOwnProperty;
727
728  function escapeRegEx(string) {
729    // https://github.com/medialize/URI.js/commit/85ac21783c11f8ccab06106dba9735a31a86924d#commitcomment-821963
730    return string.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
731  }
732
733  function getType(value) {
734    // IE8 doesn't return [Object Undefined] but [Object Object] for undefined value
735    if (value === undefined) {
736      return 'Undefined';
737    }
738
739    return String(Object.prototype.toString.call(value)).slice(8, -1);
740  }
741
742  function isArray(obj) {
743    return getType(obj) === 'Array';
744  }
745
746  function filterArrayValues(data, value) {
747    var lookup = {};
748    var i, length;
749
750    if (getType(value) === 'RegExp') {
751      lookup = null;
752    } else if (isArray(value)) {
753      for (i = 0, length = value.length; i < length; i++) {
754        lookup[value[i]] = true;
755      }
756    } else {
757      lookup[value] = true;
758    }
759
760    for (i = 0, length = data.length; i < length; i++) {
761      /*jshint laxbreak: true */
762      var _match = lookup && lookup[data[i]] !== undefined
763        || !lookup && value.test(data[i]);
764      /*jshint laxbreak: false */
765      if (_match) {
766        data.splice(i, 1);
767        length--;
768        i--;
769      }
770    }
771
772    return data;
773  }
774
775  function arrayContains(list, value) {
776    var i, length;
777
778    // value may be string, number, array, regexp
779    if (isArray(value)) {
780      // Note: this can be optimized to O(n) (instead of current O(m * n))
781      for (i = 0, length = value.length; i < length; i++) {
782        if (!arrayContains(list, value[i])) {
783          return false;
784        }
785      }
786
787      return true;
788    }
789
790    var _type = getType(value);
791    for (i = 0, length = list.length; i < length; i++) {
792      if (_type === 'RegExp') {
793        if (typeof list[i] === 'string' && list[i].match(value)) {
794          return true;
795        }
796      } else if (list[i] === value) {
797        return true;
798      }
799    }
800
801    return false;
802  }
803
804  function arraysEqual(one, two) {
805    if (!isArray(one) || !isArray(two)) {
806      return false;
807    }
808
809    // arrays can't be equal if they have different amount of content
810    if (one.length !== two.length) {
811      return false;
812    }
813
814    one.sort();
815    two.sort();
816
817    for (var i = 0, l = one.length; i < l; i++) {
818      if (one[i] !== two[i]) {
819        return false;
820      }
821    }
822
823    return true;
824  }
825
826  function trimSlashes(text) {
827    var trim_expression = /^\/+|\/+$/g;
828    return text.replace(trim_expression, '');
829  }
830
831  URI._parts = function() {
832    return {
833      protocol: null,
834      username: null,
835      password: null,
836      hostname: null,
837      urn: null,
838      port: null,
839      path: null,
840      query: null,
841      fragment: null,
842      // state
843      preventInvalidHostname: URI.preventInvalidHostname,
844      duplicateQueryParameters: URI.duplicateQueryParameters,
845      escapeQuerySpace: URI.escapeQuerySpace
846    };
847  };
848  // state: throw on invalid hostname
849  // see https://github.com/medialize/URI.js/pull/345
850  // and https://github.com/medialize/URI.js/issues/354
851  URI.preventInvalidHostname = false;
852  // state: allow duplicate query parameters (a=1&a=1)
853  URI.duplicateQueryParameters = false;
854  // state: replaces + with %20 (space in query strings)
855  URI.escapeQuerySpace = true;
856  // static properties
857  URI.protocol_expression = /^[a-z][a-z0-9.+-]*$/i;
858  URI.idn_expression = /[^a-z0-9\._-]/i;
859  URI.punycode_expression = /(xn--)/i;
860  // well, 333.444.555.666 matches, but it sure ain't no IPv4 - do we care?
861  URI.ip4_expression = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
862  // credits to Rich Brown
863  // source: http://forums.intermapper.com/viewtopic.php?p=1096#1096
864  // specification: http://www.ietf.org/rfc/rfc4291.txt
865  URI.ip6_expression = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/;
866  // expression used is "gruber revised" (@gruber v2) determined to be the
867  // best solution in a regex-golf we did a couple of ages ago at
868  // * http://mathiasbynens.be/demo/url-regex
869  // * http://rodneyrehm.de/t/url-regex.html
870  URI.find_uri_expression = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/ig;
871  URI.findUri = {
872    // valid "scheme://" or "www."
873    start: /\b(?:([a-z][a-z0-9.+-]*:\/\/)|www\.)/gi,
874    // everything up to the next whitespace
875    end: /[\s\r\n]|$/,
876    // trim trailing punctuation captured by end RegExp
877    trim: /[`!()\[\]{};:'".,<>?«»“”„‘’]+$/,
878    // balanced parens inclusion (), [], {}, <>
879    parens: /(\([^\)]*\)|\[[^\]]*\]|\{[^}]*\}|<[^>]*>)/g,
880  };
881  URI.leading_whitespace_expression = /^[\x00-\x20\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/
882  // http://www.iana.org/assignments/uri-schemes.html
883  // http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Well-known_ports
884  URI.defaultPorts = {
885    http: '80',
886    https: '443',
887    ftp: '21',
888    gopher: '70',
889    ws: '80',
890    wss: '443'
891  };
892  // list of protocols which always require a hostname
893  URI.hostProtocols = [
894    'http',
895    'https'
896  ];
897
898  // allowed hostname characters according to RFC 3986
899  // ALPHA DIGIT "-" "." "_" "~" "!" "$" "&" "'" "(" ")" "*" "+" "," ";" "=" %encoded
900  // I've never seen a (non-IDN) hostname other than: ALPHA DIGIT . - _
901  URI.invalid_hostname_characters = /[^a-zA-Z0-9\.\-:_]/;
902  // map DOM Elements to their URI attribute
903  URI.domAttributes = {
904    'a': 'href',
905    'blockquote': 'cite',
906    'link': 'href',
907    'base': 'href',
908    'script': 'src',
909    'form': 'action',
910    'img': 'src',
911    'area': 'href',
912    'iframe': 'src',
913    'embed': 'src',
914    'source': 'src',
915    'track': 'src',
916    'input': 'src', // but only if type="image"
917    'audio': 'src',
918    'video': 'src'
919  };
920  URI.getDomAttribute = function(node) {
921    if (!node || !node.nodeName) {
922      return undefined;
923    }
924
925    var nodeName = node.nodeName.toLowerCase();
926    // <input> should only expose src for type="image"
927    if (nodeName === 'input' && node.type !== 'image') {
928      return undefined;
929    }
930
931    return URI.domAttributes[nodeName];
932  };
933
934  function escapeForDumbFirefox36(value) {
935    // https://github.com/medialize/URI.js/issues/91
936    return escape(value);
937  }
938
939  // encoding / decoding according to RFC3986
940  function strictEncodeURIComponent(string) {
941    // see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURIComponent
942    return encodeURIComponent(string)
943      .replace(/[!'()*]/g, escapeForDumbFirefox36)
944      .replace(/\*/g, '%2A');
945  }
946  URI.encode = strictEncodeURIComponent;
947  URI.decode = decodeURIComponent;
948  URI.iso8859 = function() {
949    URI.encode = escape;
950    URI.decode = unescape;
951  };
952  URI.unicode = function() {
953    URI.encode = strictEncodeURIComponent;
954    URI.decode = decodeURIComponent;
955  };
956  URI.characters = {
957    pathname: {
958      encode: {
959        // RFC3986 2.1: For consistency, URI producers and normalizers should
960        // use uppercase hexadecimal digits for all percent-encodings.
961        expression: /%(24|26|2B|2C|3B|3D|3A|40)/ig,
962        map: {
963          // -._~!'()*
964          '%24': '$',
965          '%26': '&',
966          '%2B': '+',
967          '%2C': ',',
968          '%3B': ';',
969          '%3D': '=',
970          '%3A': ':',
971          '%40': '@'
972        }
973      },
974      decode: {
975        expression: /[\/\?#]/g,
976        map: {
977          '/': '%2F',
978          '?': '%3F',
979          '#': '%23'
980        }
981      }
982    },
983    reserved: {
984      encode: {
985        // RFC3986 2.1: For consistency, URI producers and normalizers should
986        // use uppercase hexadecimal digits for all percent-encodings.
987        expression: /%(21|23|24|26|27|28|29|2A|2B|2C|2F|3A|3B|3D|3F|40|5B|5D)/ig,
988        map: {
989          // gen-delims
990          '%3A': ':',
991          '%2F': '/',
992          '%3F': '?',
993          '%23': '#',
994          '%5B': '[',
995          '%5D': ']',
996          '%40': '@',
997          // sub-delims
998          '%21': '!',
999          '%24': '$',
1000          '%26': '&',
1001          '%27': '\'',
1002          '%28': '(',
1003          '%29': ')',
1004          '%2A': '*',
1005          '%2B': '+',
1006          '%2C': ',',
1007          '%3B': ';',
1008          '%3D': '='
1009        }
1010      }
1011    },
1012    urnpath: {
1013      // The characters under `encode` are the characters called out by RFC 2141 as being acceptable
1014      // for usage in a URN. RFC2141 also calls out "-", ".", and "_" as acceptable characters, but
1015      // these aren't encoded by encodeURIComponent, so we don't have to call them out here. Also
1016      // note that the colon character is not featured in the encoding map; this is because URI.js
1017      // gives the colons in URNs semantic meaning as the delimiters of path segements, and so it
1018      // should not appear unencoded in a segment itself.
1019      // See also the note above about RFC3986 and capitalalized hex digits.
1020      encode: {
1021        expression: /%(21|24|27|28|29|2A|2B|2C|3B|3D|40)/ig,
1022        map: {
1023          '%21': '!',
1024          '%24': '$',
1025          '%27': '\'',
1026          '%28': '(',
1027          '%29': ')',
1028          '%2A': '*',
1029          '%2B': '+',
1030          '%2C': ',',
1031          '%3B': ';',
1032          '%3D': '=',
1033          '%40': '@'
1034        }
1035      },
1036      // These characters are the characters called out by RFC2141 as "reserved" characters that
1037      // should never appear in a URN, plus the colon character (see note above).
1038      decode: {
1039        expression: /[\/\?#:]/g,
1040        map: {
1041          '/': '%2F',
1042          '?': '%3F',
1043          '#': '%23',
1044          ':': '%3A'
1045        }
1046      }
1047    }
1048  };
1049  URI.encodeQuery = function(string, escapeQuerySpace) {
1050    var escaped = URI.encode(string + '');
1051    if (escapeQuerySpace === undefined) {
1052      escapeQuerySpace = URI.escapeQuerySpace;
1053    }
1054
1055    return escapeQuerySpace ? escaped.replace(/%20/g, ' ') : escaped;
1056  };
1057  URI.decodeQuery = function(string, escapeQuerySpace) {
1058    string += '';
1059    if (escapeQuerySpace === undefined) {
1060      escapeQuerySpace = URI.escapeQuerySpace;
1061    }
1062
1063    try {
1064      return URI.decode(escapeQuerySpace ? string.replace(/\+/g, '%20') : string);
1065    } catch(e) {
1066      // we're not going to mess with weird encodings,
1067      // give up and return the undecoded original string
1068      // see https://github.com/medialize/URI.js/issues/87
1069      // see https://github.com/medialize/URI.js/issues/92
1070      return string;
1071    }
1072  };
1073  // generate encode/decode path functions
1074  var _parts = {'encode':'encode', 'decode':'decode'};
1075  var _part;
1076  var generateAccessor = function(_group, _part) {
1077    return function(string) {
1078      try {
1079        return URI[_part](string + '').replace(URI.characters[_group][_part].expression, function(c) {
1080          return URI.characters[_group][_part].map[c];
1081        });
1082      } catch (e) {
1083        // we're not going to mess with weird encodings,
1084        // give up and return the undecoded original string
1085        // see https://github.com/medialize/URI.js/issues/87
1086        // see https://github.com/medialize/URI.js/issues/92
1087        return string;
1088      }
1089    };
1090  };
1091
1092  for (_part in _parts) {
1093    URI[_part + 'PathSegment'] = generateAccessor('pathname', _parts[_part]);
1094    URI[_part + 'UrnPathSegment'] = generateAccessor('urnpath', _parts[_part]);
1095  }
1096
1097  var generateSegmentedPathFunction = function(_sep, _codingFuncName, _innerCodingFuncName) {
1098    return function(string) {
1099      // Why pass in names of functions, rather than the function objects themselves? The
1100      // definitions of some functions (but in particular, URI.decode) will occasionally change due
1101      // to URI.js having ISO8859 and Unicode modes. Passing in the name and getting it will ensure
1102      // that the functions we use here are "fresh".
1103      var actualCodingFunc;
1104      if (!_innerCodingFuncName) {
1105        actualCodingFunc = URI[_codingFuncName];
1106      } else {
1107        actualCodingFunc = function(string) {
1108          return URI[_codingFuncName](URI[_innerCodingFuncName](string));
1109        };
1110      }
1111
1112      var segments = (string + '').split(_sep);
1113
1114      for (var i = 0, length = segments.length; i < length; i++) {
1115        segments[i] = actualCodingFunc(segments[i]);
1116      }
1117
1118      return segments.join(_sep);
1119    };
1120  };
1121
1122  // This takes place outside the above loop because we don't want, e.g., encodeUrnPath functions.
1123  URI.decodePath = generateSegmentedPathFunction('/', 'decodePathSegment');
1124  URI.decodeUrnPath = generateSegmentedPathFunction(':', 'decodeUrnPathSegment');
1125  URI.recodePath = generateSegmentedPathFunction('/', 'encodePathSegment', 'decode');
1126  URI.recodeUrnPath = generateSegmentedPathFunction(':', 'encodeUrnPathSegment', 'decode');
1127
1128  URI.encodeReserved = generateAccessor('reserved', 'encode');
1129
1130  URI.parse = function(string, parts) {
1131    var pos;
1132    if (!parts) {
1133      parts = {
1134        preventInvalidHostname: URI.preventInvalidHostname
1135      };
1136    }
1137
1138    string = string.replace(URI.leading_whitespace_expression, '')
1139
1140    // [protocol"://"[username[":"password]"@"]hostname[":"port]"/"?][path]["?"querystring]["#"fragment]
1141
1142    // extract fragment
1143    pos = string.indexOf('#');
1144    if (pos > -1) {
1145      // escaping?
1146      parts.fragment = string.substring(pos + 1) || null;
1147      string = string.substring(0, pos);
1148    }
1149
1150    // extract query
1151    pos = string.indexOf('?');
1152    if (pos > -1) {
1153      // escaping?
1154      parts.query = string.substring(pos + 1) || null;
1155      string = string.substring(0, pos);
1156    }
1157
1158    // slashes and backslashes have lost all meaning for the web protocols (https, http, wss, ws)
1159    string = string.replace(/^(https?|ftp|wss?)?:+[/\\]*/i, '$1://');
1160
1161    // extract protocol
1162    if (string.substring(0, 2) === '//') {
1163      // relative-scheme
1164      parts.protocol = null;
1165      string = string.substring(2);
1166      // extract "user:pass@host:port"
1167      string = URI.parseAuthority(string, parts);
1168    } else {
1169      pos = string.indexOf(':');
1170      if (pos > -1) {
1171        parts.protocol = string.substring(0, pos) || null;
1172        if (parts.protocol && !parts.protocol.match(URI.protocol_expression)) {
1173          // : may be within the path
1174          parts.protocol = undefined;
1175        } else if (string.substring(pos + 1, pos + 3).replace(/\\/g, '/') === '//') {
1176          string = string.substring(pos + 3);
1177
1178          // extract "user:pass@host:port"
1179          string = URI.parseAuthority(string, parts);
1180        } else {
1181          string = string.substring(pos + 1);
1182          parts.urn = true;
1183        }
1184      }
1185    }
1186
1187    // what's left must be the path
1188    parts.path = string;
1189
1190    // and we're done
1191    return parts;
1192  };
1193  URI.parseHost = function(string, parts) {
1194    if (!string) {
1195      string = '';
1196    }
1197
1198    // Copy chrome, IE, opera backslash-handling behavior.
1199    // Back slashes before the query string get converted to forward slashes
1200    // See: https://github.com/joyent/node/blob/386fd24f49b0e9d1a8a076592a404168faeecc34/lib/url.js#L115-L124
1201    // See: https://code.google.com/p/chromium/issues/detail?id=25916
1202    // https://github.com/medialize/URI.js/pull/233
1203    string = string.replace(/\\/g, '/');
1204
1205    // extract host:port
1206    var pos = string.indexOf('/');
1207    var bracketPos;
1208    var t;
1209
1210    if (pos === -1) {
1211      pos = string.length;
1212    }
1213
1214    if (string.charAt(0) === '[') {
1215      // IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6
1216      // I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts
1217      // IPv6+port in the format [2001:db8::1]:80 (for the time being)
1218      bracketPos = string.indexOf(']');
1219      parts.hostname = string.substring(1, bracketPos) || null;
1220      parts.port = string.substring(bracketPos + 2, pos) || null;
1221      if (parts.port === '/') {
1222        parts.port = null;
1223      }
1224    } else {
1225      var firstColon = string.indexOf(':');
1226      var firstSlash = string.indexOf('/');
1227      var nextColon = string.indexOf(':', firstColon + 1);
1228      if (nextColon !== -1 && (firstSlash === -1 || nextColon < firstSlash)) {
1229        // IPv6 host contains multiple colons - but no port
1230        // this notation is actually not allowed by RFC 3986, but we're a liberal parser
1231        parts.hostname = string.substring(0, pos) || null;
1232        parts.port = null;
1233      } else {
1234        t = string.substring(0, pos).split(':');
1235        parts.hostname = t[0] || null;
1236        parts.port = t[1] || null;
1237      }
1238    }
1239
1240    if (parts.hostname && string.substring(pos).charAt(0) !== '/') {
1241      pos++;
1242      string = '/' + string;
1243    }
1244
1245    if (parts.preventInvalidHostname) {
1246      URI.ensureValidHostname(parts.hostname, parts.protocol);
1247    }
1248
1249    if (parts.port) {
1250      URI.ensureValidPort(parts.port);
1251    }
1252
1253    return string.substring(pos) || '/';
1254  };
1255  URI.parseAuthority = function(string, parts) {
1256    string = URI.parseUserinfo(string, parts);
1257    return URI.parseHost(string, parts);
1258  };
1259  URI.parseUserinfo = function(string, parts) {
1260    // extract username:password
1261    var _string = string
1262    var firstBackSlash = string.indexOf('\\');
1263    if (firstBackSlash !== -1) {
1264      string = string.replace(/\\/g, '/')
1265    }
1266    var firstSlash = string.indexOf('/');
1267    var pos = string.lastIndexOf('@', firstSlash > -1 ? firstSlash : string.length - 1);
1268    var t;
1269
1270    // authority@ must come before /path or \path
1271    if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) {
1272      t = string.substring(0, pos).split(':');
1273      parts.username = t[0] ? URI.decode(t[0]) : null;
1274      t.shift();
1275      parts.password = t[0] ? URI.decode(t.join(':')) : null;
1276      string = _string.substring(pos + 1);
1277    } else {
1278      parts.username = null;
1279      parts.password = null;
1280    }
1281
1282    return string;
1283  };
1284  URI.parseQuery = function(string, escapeQuerySpace) {
1285    if (!string) {
1286      return {};
1287    }
1288
1289    // throw out the funky business - "?"[name"="value"&"]+
1290    string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, '');
1291
1292    if (!string) {
1293      return {};
1294    }
1295
1296    var items = {};
1297    var splits = string.split('&');
1298    var length = splits.length;
1299    var v, name, value;
1300
1301    for (var i = 0; i < length; i++) {
1302      v = splits[i].split('=');
1303      name = URI.decodeQuery(v.shift(), escapeQuerySpace);
1304      // no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters
1305      value = v.length ? URI.decodeQuery(v.join('='), escapeQuerySpace) : null;
1306
1307      if (name === '__proto__') {
1308        // ignore attempt at exploiting JavaScript internals
1309        continue;
1310      } else if (hasOwn.call(items, name)) {
1311        if (typeof items[name] === 'string' || items[name] === null) {
1312          items[name] = [items[name]];
1313        }
1314
1315        items[name].push(value);
1316      } else {
1317        items[name] = value;
1318      }
1319    }
1320
1321    return items;
1322  };
1323
1324  URI.build = function(parts) {
1325    var t = '';
1326    var requireAbsolutePath = false
1327
1328    if (parts.protocol) {
1329      t += parts.protocol + ':';
1330    }
1331
1332    if (!parts.urn && (t || parts.hostname)) {
1333      t += '//';
1334      requireAbsolutePath = true
1335    }
1336
1337    t += (URI.buildAuthority(parts) || '');
1338
1339    if (typeof parts.path === 'string') {
1340      if (parts.path.charAt(0) !== '/' && requireAbsolutePath) {
1341        t += '/';
1342      }
1343
1344      t += parts.path;
1345    }
1346
1347    if (typeof parts.query === 'string' && parts.query) {
1348      t += '?' + parts.query;
1349    }
1350
1351    if (typeof parts.fragment === 'string' && parts.fragment) {
1352      t += '#' + parts.fragment;
1353    }
1354    return t;
1355  };
1356  URI.buildHost = function(parts) {
1357    var t = '';
1358
1359    if (!parts.hostname) {
1360      return '';
1361    } else if (URI.ip6_expression.test(parts.hostname)) {
1362      t += '[' + parts.hostname + ']';
1363    } else {
1364      t += parts.hostname;
1365    }
1366
1367    if (parts.port) {
1368      t += ':' + parts.port;
1369    }
1370
1371    return t;
1372  };
1373  URI.buildAuthority = function(parts) {
1374    return URI.buildUserinfo(parts) + URI.buildHost(parts);
1375  };
1376  URI.buildUserinfo = function(parts) {
1377    var t = '';
1378
1379    if (parts.username) {
1380      t += URI.encode(parts.username);
1381    }
1382
1383    if (parts.password) {
1384      t += ':' + URI.encode(parts.password);
1385    }
1386
1387    if (t) {
1388      t += '@';
1389    }
1390
1391    return t;
1392  };
1393  URI.buildQuery = function(data, duplicateQueryParameters, escapeQuerySpace) {
1394    // according to http://tools.ietf.org/html/rfc3986 or http://labs.apache.org/webarch/uri/rfc/rfc3986.html
1395    // being »-._~!$&'()*+,;=:@/?« %HEX and alnum are allowed
1396    // the RFC explicitly states ?/foo being a valid use case, no mention of parameter syntax!
1397    // URI.js treats the query string as being application/x-www-form-urlencoded
1398    // see http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type
1399
1400    var t = '';
1401    var unique, key, i, length;
1402    for (key in data) {
1403      if (key === '__proto__') {
1404        // ignore attempt at exploiting JavaScript internals
1405        continue;
1406      } else if (hasOwn.call(data, key)) {
1407        if (isArray(data[key])) {
1408          unique = {};
1409          for (i = 0, length = data[key].length; i < length; i++) {
1410            if (data[key][i] !== undefined && unique[data[key][i] + ''] === undefined) {
1411              t += '&' + URI.buildQueryParameter(key, data[key][i], escapeQuerySpace);
1412              if (duplicateQueryParameters !== true) {
1413                unique[data[key][i] + ''] = true;
1414              }
1415            }
1416          }
1417        } else if (data[key] !== undefined) {
1418          t += '&' + URI.buildQueryParameter(key, data[key], escapeQuerySpace);
1419        }
1420      }
1421    }
1422
1423    return t.substring(1);
1424  };
1425  URI.buildQueryParameter = function(name, value, escapeQuerySpace) {
1426    // http://www.w3.org/TR/REC-html40/interact/forms.html#form-content-type -- application/x-www-form-urlencoded
1427    // don't append "=" for null values, according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#url-parameter-serialization
1428    return URI.encodeQuery(name, escapeQuerySpace) + (value !== null ? '=' + URI.encodeQuery(value, escapeQuerySpace) : '');
1429  };
1430
1431  URI.addQuery = function(data, name, value) {
1432    if (typeof name === 'object') {
1433      for (var key in name) {
1434        if (hasOwn.call(name, key)) {
1435          URI.addQuery(data, key, name[key]);
1436        }
1437      }
1438    } else if (typeof name === 'string') {
1439      if (data[name] === undefined) {
1440        data[name] = value;
1441        return;
1442      } else if (typeof data[name] === 'string') {
1443        data[name] = [data[name]];
1444      }
1445
1446      if (!isArray(value)) {
1447        value = [value];
1448      }
1449
1450      data[name] = (data[name] || []).concat(value);
1451    } else {
1452      throw new TypeError('URI.addQuery() accepts an object, string as the name parameter');
1453    }
1454  };
1455
1456  URI.setQuery = function(data, name, value) {
1457    if (typeof name === 'object') {
1458      for (var key in name) {
1459        if (hasOwn.call(name, key)) {
1460          URI.setQuery(data, key, name[key]);
1461        }
1462      }
1463    } else if (typeof name === 'string') {
1464      data[name] = value === undefined ? null : value;
1465    } else {
1466      throw new TypeError('URI.setQuery() accepts an object, string as the name parameter');
1467    }
1468  };
1469
1470  URI.removeQuery = function(data, name, value) {
1471    var i, length, key;
1472
1473    if (isArray(name)) {
1474      for (i = 0, length = name.length; i < length; i++) {
1475        data[name[i]] = undefined;
1476      }
1477    } else if (getType(name) === 'RegExp') {
1478      for (key in data) {
1479        if (name.test(key)) {
1480          data[key] = undefined;
1481        }
1482      }
1483    } else if (typeof name === 'object') {
1484      for (key in name) {
1485        if (hasOwn.call(name, key)) {
1486          URI.removeQuery(data, key, name[key]);
1487        }
1488      }
1489    } else if (typeof name === 'string') {
1490      if (value !== undefined) {
1491        if (getType(value) === 'RegExp') {
1492          if (!isArray(data[name]) && value.test(data[name])) {
1493            data[name] = undefined;
1494          } else {
1495            data[name] = filterArrayValues(data[name], value);
1496          }
1497        } else if (data[name] === String(value) && (!isArray(value) || value.length === 1)) {
1498          data[name] = undefined;
1499        } else if (isArray(data[name])) {
1500          data[name] = filterArrayValues(data[name], value);
1501        }
1502      } else {
1503        data[name] = undefined;
1504      }
1505    } else {
1506      throw new TypeError('URI.removeQuery() accepts an object, string, RegExp as the first parameter');
1507    }
1508  };
1509  URI.hasQuery = function(data, name, value, withinArray) {
1510    switch (getType(name)) {
1511      case 'String':
1512        // Nothing to do here
1513        break;
1514
1515      case 'RegExp':
1516        for (var key in data) {
1517          if (hasOwn.call(data, key)) {
1518            if (name.test(key) && (value === undefined || URI.hasQuery(data, key, value))) {
1519              return true;
1520            }
1521          }
1522        }
1523
1524        return false;
1525
1526      case 'Object':
1527        for (var _key in name) {
1528          if (hasOwn.call(name, _key)) {
1529            if (!URI.hasQuery(data, _key, name[_key])) {
1530              return false;
1531            }
1532          }
1533        }
1534
1535        return true;
1536
1537      default:
1538        throw new TypeError('URI.hasQuery() accepts a string, regular expression or object as the name parameter');
1539    }
1540
1541    switch (getType(value)) {
1542      case 'Undefined':
1543        // true if exists (but may be empty)
1544        return name in data; // data[name] !== undefined;
1545
1546      case 'Boolean':
1547        // true if exists and non-empty
1548        var _booly = Boolean(isArray(data[name]) ? data[name].length : data[name]);
1549        return value === _booly;
1550
1551      case 'Function':
1552        // allow complex comparison
1553        return !!value(data[name], name, data);
1554
1555      case 'Array':
1556        if (!isArray(data[name])) {
1557          return false;
1558        }
1559
1560        var op = withinArray ? arrayContains : arraysEqual;
1561        return op(data[name], value);
1562
1563      case 'RegExp':
1564        if (!isArray(data[name])) {
1565          return Boolean(data[name] && data[name].match(value));
1566        }
1567
1568        if (!withinArray) {
1569          return false;
1570        }
1571
1572        return arrayContains(data[name], value);
1573
1574      case 'Number':
1575        value = String(value);
1576      /* falls through */
1577      case 'String':
1578        if (!isArray(data[name])) {
1579          return data[name] === value;
1580        }
1581
1582        if (!withinArray) {
1583          return false;
1584        }
1585
1586        return arrayContains(data[name], value);
1587
1588      default:
1589        throw new TypeError('URI.hasQuery() accepts undefined, boolean, string, number, RegExp, Function as the value parameter');
1590    }
1591  };
1592
1593
1594  URI.joinPaths = function() {
1595    var input = [];
1596    var segments = [];
1597    var nonEmptySegments = 0;
1598
1599    for (var i = 0; i < arguments.length; i++) {
1600      var url = new URI(arguments[i]);
1601      input.push(url);
1602      var _segments = url.segment();
1603      for (var s = 0; s < _segments.length; s++) {
1604        if (typeof _segments[s] === 'string') {
1605          segments.push(_segments[s]);
1606        }
1607
1608        if (_segments[s]) {
1609          nonEmptySegments++;
1610        }
1611      }
1612    }
1613
1614    if (!segments.length || !nonEmptySegments) {
1615      return new URI('');
1616    }
1617
1618    var uri = new URI('').segment(segments);
1619
1620    if (input[0].path() === '' || input[0].path().slice(0, 1) === '/') {
1621      uri.path('/' + uri.path());
1622    }
1623
1624    return uri.normalize();
1625  };
1626
1627  URI.commonPath = function(one, two) {
1628    var length = Math.min(one.length, two.length);
1629    var pos;
1630
1631    // find first non-matching character
1632    for (pos = 0; pos < length; pos++) {
1633      if (one.charAt(pos) !== two.charAt(pos)) {
1634        pos--;
1635        break;
1636      }
1637    }
1638
1639    if (pos < 1) {
1640      return one.charAt(0) === two.charAt(0) && one.charAt(0) === '/' ? '/' : '';
1641    }
1642
1643    // revert to last /
1644    if (one.charAt(pos) !== '/' || two.charAt(pos) !== '/') {
1645      pos = one.substring(0, pos).lastIndexOf('/');
1646    }
1647
1648    return one.substring(0, pos + 1);
1649  };
1650
1651  URI.withinString = function(string, callback, options) {
1652    options || (options = {});
1653    var _start = options.start || URI.findUri.start;
1654    var _end = options.end || URI.findUri.end;
1655    var _trim = options.trim || URI.findUri.trim;
1656    var _parens = options.parens || URI.findUri.parens;
1657    var _attributeOpen = /[a-z0-9-]=["']?$/i;
1658
1659    _start.lastIndex = 0;
1660    while (true) {
1661      var match = _start.exec(string);
1662      if (!match) {
1663        break;
1664      }
1665
1666      var start = match.index;
1667      if (options.ignoreHtml) {
1668        // attribut(e=["']?$)
1669        var attributeOpen = string.slice(Math.max(start - 3, 0), start);
1670        if (attributeOpen && _attributeOpen.test(attributeOpen)) {
1671          continue;
1672        }
1673      }
1674
1675      var end = start + string.slice(start).search(_end);
1676      var slice = string.slice(start, end);
1677      // make sure we include well balanced parens
1678      var parensEnd = -1;
1679      while (true) {
1680        var parensMatch = _parens.exec(slice);
1681        if (!parensMatch) {
1682          break;
1683        }
1684
1685        var parensMatchEnd = parensMatch.index + parensMatch[0].length;
1686        parensEnd = Math.max(parensEnd, parensMatchEnd);
1687      }
1688
1689      if (parensEnd > -1) {
1690        slice = slice.slice(0, parensEnd) + slice.slice(parensEnd).replace(_trim, '');
1691      } else {
1692        slice = slice.replace(_trim, '');
1693      }
1694
1695      if (slice.length <= match[0].length) {
1696        // the extract only contains the starting marker of a URI,
1697        // e.g. "www" or "http://"
1698        continue;
1699      }
1700
1701      if (options.ignore && options.ignore.test(slice)) {
1702        continue;
1703      }
1704
1705      end = start + slice.length;
1706      var result = callback(slice, start, end, string);
1707      if (result === undefined) {
1708        _start.lastIndex = end;
1709        continue;
1710      }
1711
1712      result = String(result);
1713      string = string.slice(0, start) + result + string.slice(end);
1714      _start.lastIndex = start + result.length;
1715    }
1716
1717    _start.lastIndex = 0;
1718    return string;
1719  };
1720
1721  URI.ensureValidHostname = function(v, protocol) {
1722    // Theoretically URIs allow percent-encoding in Hostnames (according to RFC 3986)
1723    // they are not part of DNS and therefore ignored by URI.js
1724
1725    var hasHostname = !!v; // not null and not an empty string
1726    var hasProtocol = !!protocol;
1727    var rejectEmptyHostname = false;
1728
1729    if (hasProtocol) {
1730      rejectEmptyHostname = arrayContains(URI.hostProtocols, protocol);
1731    }
1732
1733    if (rejectEmptyHostname && !hasHostname) {
1734      throw new TypeError('Hostname cannot be empty, if protocol is ' + protocol);
1735    } else if (v && v.match(URI.invalid_hostname_characters)) {
1736      // test punycode
1737      if (!punycode) {
1738        throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-:_] and Punycode.js is not available');
1739      }
1740      if (punycode.toASCII(v).match(URI.invalid_hostname_characters)) {
1741        throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-:_]');
1742      }
1743    }
1744  };
1745
1746  URI.ensureValidPort = function (v) {
1747    if (!v) {
1748      return;
1749    }
1750
1751    var port = Number(v);
1752    if (isInteger(port) && (port > 0) && (port < 65536)) {
1753      return;
1754    }
1755
1756    throw new TypeError('Port "' + v + '" is not a valid port');
1757  };
1758
1759  // noConflict
1760  URI.noConflict = function(removeAll) {
1761    if (removeAll) {
1762      var unconflicted = {
1763        URI: this.noConflict()
1764      };
1765
1766      if (root.URITemplate && typeof root.URITemplate.noConflict === 'function') {
1767        unconflicted.URITemplate = root.URITemplate.noConflict();
1768      }
1769
1770      if (root.IPv6 && typeof root.IPv6.noConflict === 'function') {
1771        unconflicted.IPv6 = root.IPv6.noConflict();
1772      }
1773
1774      if (root.SecondLevelDomains && typeof root.SecondLevelDomains.noConflict === 'function') {
1775        unconflicted.SecondLevelDomains = root.SecondLevelDomains.noConflict();
1776      }
1777
1778      return unconflicted;
1779    } else if (root.URI === this) {
1780      root.URI = _URI;
1781    }
1782
1783    return this;
1784  };
1785
1786  p.build = function(deferBuild) {
1787    if (deferBuild === true) {
1788      this._deferred_build = true;
1789    } else if (deferBuild === undefined || this._deferred_build) {
1790      this._string = URI.build(this._parts);
1791      this._deferred_build = false;
1792    }
1793
1794    return this;
1795  };
1796
1797  p.clone = function() {
1798    return new URI(this);
1799  };
1800
1801  p.valueOf = p.toString = function() {
1802    return this.build(false)._string;
1803  };
1804
1805
1806  function generateSimpleAccessor(_part){
1807    return function(v, build) {
1808      if (v === undefined) {
1809        return this._parts[_part] || '';
1810      } else {
1811        this._parts[_part] = v || null;
1812        this.build(!build);
1813        return this;
1814      }
1815    };
1816  }
1817
1818  function generatePrefixAccessor(_part, _key){
1819    return function(v, build) {
1820      if (v === undefined) {
1821        return this._parts[_part] || '';
1822      } else {
1823        if (v !== null) {
1824          v = v + '';
1825          if (v.charAt(0) === _key) {
1826            v = v.substring(1);
1827          }
1828        }
1829
1830        this._parts[_part] = v;
1831        this.build(!build);
1832        return this;
1833      }
1834    };
1835  }
1836
1837  p.protocol = generateSimpleAccessor('protocol');
1838  p.username = generateSimpleAccessor('username');
1839  p.password = generateSimpleAccessor('password');
1840  p.hostname = generateSimpleAccessor('hostname');
1841  p.port = generateSimpleAccessor('port');
1842  p.query = generatePrefixAccessor('query', '?');
1843  p.fragment = generatePrefixAccessor('fragment', '#');
1844
1845  p.search = function(v, build) {
1846    var t = this.query(v, build);
1847    return typeof t === 'string' && t.length ? ('?' + t) : t;
1848  };
1849  p.hash = function(v, build) {
1850    var t = this.fragment(v, build);
1851    return typeof t === 'string' && t.length ? ('#' + t) : t;
1852  };
1853
1854  p.pathname = function(v, build) {
1855    if (v === undefined || v === true) {
1856      var res = this._parts.path || (this._parts.hostname ? '/' : '');
1857      return v ? (this._parts.urn ? URI.decodeUrnPath : URI.decodePath)(res) : res;
1858    } else {
1859      if (this._parts.urn) {
1860        this._parts.path = v ? URI.recodeUrnPath(v) : '';
1861      } else {
1862        this._parts.path = v ? URI.recodePath(v) : '/';
1863      }
1864      this.build(!build);
1865      return this;
1866    }
1867  };
1868  p.path = p.pathname;
1869  p.href = function(href, build) {
1870    var key;
1871
1872    if (href === undefined) {
1873      return this.toString();
1874    }
1875
1876    this._string = '';
1877    this._parts = URI._parts();
1878
1879    var _URI = href instanceof URI;
1880    var _object = typeof href === 'object' && (href.hostname || href.path || href.pathname);
1881    if (href.nodeName) {
1882      var attribute = URI.getDomAttribute(href);
1883      href = href[attribute] || '';
1884      _object = false;
1885    }
1886
1887    // window.location is reported to be an object, but it's not the sort
1888    // of object we're looking for:
1889    // * location.protocol ends with a colon
1890    // * location.query != object.search
1891    // * location.hash != object.fragment
1892    // simply serializing the unknown object should do the trick
1893    // (for location, not for everything...)
1894    if (!_URI && _object && href.pathname !== undefined) {
1895      href = href.toString();
1896    }
1897
1898    if (typeof href === 'string' || href instanceof String) {
1899      this._parts = URI.parse(String(href), this._parts);
1900    } else if (_URI || _object) {
1901      var src = _URI ? href._parts : href;
1902      for (key in src) {
1903        if (key === 'query') { continue; }
1904        if (hasOwn.call(this._parts, key)) {
1905          this._parts[key] = src[key];
1906        }
1907      }
1908      if (src.query) {
1909        this.query(src.query, false);
1910      }
1911    } else {
1912      throw new TypeError('invalid input');
1913    }
1914
1915    this.build(!build);
1916    return this;
1917  };
1918
1919  // identification accessors
1920  p.is = function(what) {
1921    var ip = false;
1922    var ip4 = false;
1923    var ip6 = false;
1924    var name = false;
1925    var sld = false;
1926    var idn = false;
1927    var punycode = false;
1928    var relative = !this._parts.urn;
1929
1930    if (this._parts.hostname) {
1931      relative = false;
1932      ip4 = URI.ip4_expression.test(this._parts.hostname);
1933      ip6 = URI.ip6_expression.test(this._parts.hostname);
1934      ip = ip4 || ip6;
1935      name = !ip;
1936      sld = name && SLD && SLD.has(this._parts.hostname);
1937      idn = name && URI.idn_expression.test(this._parts.hostname);
1938      punycode = name && URI.punycode_expression.test(this._parts.hostname);
1939    }
1940
1941    switch (what.toLowerCase()) {
1942      case 'relative':
1943        return relative;
1944
1945      case 'absolute':
1946        return !relative;
1947
1948      // hostname identification
1949      case 'domain':
1950      case 'name':
1951        return name;
1952
1953      case 'sld':
1954        return sld;
1955
1956      case 'ip':
1957        return ip;
1958
1959      case 'ip4':
1960      case 'ipv4':
1961      case 'inet4':
1962        return ip4;
1963
1964      case 'ip6':
1965      case 'ipv6':
1966      case 'inet6':
1967        return ip6;
1968
1969      case 'idn':
1970        return idn;
1971
1972      case 'url':
1973        return !this._parts.urn;
1974
1975      case 'urn':
1976        return !!this._parts.urn;
1977
1978      case 'punycode':
1979        return punycode;
1980    }
1981
1982    return null;
1983  };
1984
1985  // component specific input validation
1986  var _protocol = p.protocol;
1987  var _port = p.port;
1988  var _hostname = p.hostname;
1989
1990  p.protocol = function(v, build) {
1991    if (v) {
1992      // accept trailing ://
1993      v = v.replace(/:(\/\/)?$/, '');
1994
1995      if (!v.match(URI.protocol_expression)) {
1996        throw new TypeError('Protocol "' + v + '" contains characters other than [A-Z0-9.+-] or doesn\'t start with [A-Z]');
1997      }
1998    }
1999
2000    return _protocol.call(this, v, build);
2001  };
2002  p.scheme = p.protocol;
2003  p.port = function(v, build) {
2004    if (this._parts.urn) {
2005      return v === undefined ? '' : this;
2006    }
2007
2008    if (v !== undefined) {
2009      if (v === 0) {
2010        v = null;
2011      }
2012
2013      if (v) {
2014        v += '';
2015        if (v.charAt(0) === ':') {
2016          v = v.substring(1);
2017        }
2018
2019        URI.ensureValidPort(v);
2020      }
2021    }
2022    return _port.call(this, v, build);
2023  };
2024  p.hostname = function(v, build) {
2025    if (this._parts.urn) {
2026      return v === undefined ? '' : this;
2027    }
2028
2029    if (v !== undefined) {
2030      var x = { preventInvalidHostname: this._parts.preventInvalidHostname };
2031      var res = URI.parseHost(v, x);
2032      if (res !== '/') {
2033        throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
2034      }
2035
2036      v = x.hostname;
2037      if (this._parts.preventInvalidHostname) {
2038        URI.ensureValidHostname(v, this._parts.protocol);
2039      }
2040    }
2041
2042    return _hostname.call(this, v, build);
2043  };
2044
2045  // compound accessors
2046  p.origin = function(v, build) {
2047    if (this._parts.urn) {
2048      return v === undefined ? '' : this;
2049    }
2050
2051    if (v === undefined) {
2052      var protocol = this.protocol();
2053      var authority = this.authority();
2054      if (!authority) {
2055        return '';
2056      }
2057
2058      return (protocol ? protocol + '://' : '') + this.authority();
2059    } else {
2060      var origin = URI(v);
2061      this
2062        .protocol(origin.protocol())
2063        .authority(origin.authority())
2064        .build(!build);
2065      return this;
2066    }
2067  };
2068  p.host = function(v, build) {
2069    if (this._parts.urn) {
2070      return v === undefined ? '' : this;
2071    }
2072
2073    if (v === undefined) {
2074      return this._parts.hostname ? URI.buildHost(this._parts) : '';
2075    } else {
2076      var res = URI.parseHost(v, this._parts);
2077      if (res !== '/') {
2078        throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
2079      }
2080
2081      this.build(!build);
2082      return this;
2083    }
2084  };
2085  p.authority = function(v, build) {
2086    if (this._parts.urn) {
2087      return v === undefined ? '' : this;
2088    }
2089
2090    if (v === undefined) {
2091      return this._parts.hostname ? URI.buildAuthority(this._parts) : '';
2092    } else {
2093      var res = URI.parseAuthority(v, this._parts);
2094      if (res !== '/') {
2095        throw new TypeError('Hostname "' + v + '" contains characters other than [A-Z0-9.-]');
2096      }
2097
2098      this.build(!build);
2099      return this;
2100    }
2101  };
2102  p.userinfo = function(v, build) {
2103    if (this._parts.urn) {
2104      return v === undefined ? '' : this;
2105    }
2106
2107    if (v === undefined) {
2108      var t = URI.buildUserinfo(this._parts);
2109      return t ? t.substring(0, t.length -1) : t;
2110    } else {
2111      if (v[v.length-1] !== '@') {
2112        v += '@';
2113      }
2114
2115      URI.parseUserinfo(v, this._parts);
2116      this.build(!build);
2117      return this;
2118    }
2119  };
2120  p.resource = function(v, build) {
2121    var parts;
2122
2123    if (v === undefined) {
2124      return this.path() + this.search() + this.hash();
2125    }
2126
2127    parts = URI.parse(v);
2128    this._parts.path = parts.path;
2129    this._parts.query = parts.query;
2130    this._parts.fragment = parts.fragment;
2131    this.build(!build);
2132    return this;
2133  };
2134
2135  // fraction accessors
2136  p.subdomain = function(v, build) {
2137    if (this._parts.urn) {
2138      return v === undefined ? '' : this;
2139    }
2140
2141    // convenience, return "www" from "www.example.org"
2142    if (v === undefined) {
2143      if (!this._parts.hostname || this.is('IP')) {
2144        return '';
2145      }
2146
2147      // grab domain and add another segment
2148      var end = this._parts.hostname.length - this.domain().length - 1;
2149      return this._parts.hostname.substring(0, end) || '';
2150    } else {
2151      var e = this._parts.hostname.length - this.domain().length;
2152      var sub = this._parts.hostname.substring(0, e);
2153      var replace = new RegExp('^' + escapeRegEx(sub));
2154
2155      if (v && v.charAt(v.length - 1) !== '.') {
2156        v += '.';
2157      }
2158
2159      if (v.indexOf(':') !== -1) {
2160        throw new TypeError('Domains cannot contain colons');
2161      }
2162
2163      if (v) {
2164        URI.ensureValidHostname(v, this._parts.protocol);
2165      }
2166
2167      this._parts.hostname = this._parts.hostname.replace(replace, v);
2168      this.build(!build);
2169      return this;
2170    }
2171  };
2172  p.domain = function(v, build) {
2173    if (this._parts.urn) {
2174      return v === undefined ? '' : this;
2175    }
2176
2177    if (typeof v === 'boolean') {
2178      build = v;
2179      v = undefined;
2180    }
2181
2182    // convenience, return "example.org" from "www.example.org"
2183    if (v === undefined) {
2184      if (!this._parts.hostname || this.is('IP')) {
2185        return '';
2186      }
2187
2188      // if hostname consists of 1 or 2 segments, it must be the domain
2189      var t = this._parts.hostname.match(/\./g);
2190      if (t && t.length < 2) {
2191        return this._parts.hostname;
2192      }
2193
2194      // grab tld and add another segment
2195      var end = this._parts.hostname.length - this.tld(build).length - 1;
2196      end = this._parts.hostname.lastIndexOf('.', end -1) + 1;
2197      return this._parts.hostname.substring(end) || '';
2198    } else {
2199      if (!v) {
2200        throw new TypeError('cannot set domain empty');
2201      }
2202
2203      if (v.indexOf(':') !== -1) {
2204        throw new TypeError('Domains cannot contain colons');
2205      }
2206
2207      URI.ensureValidHostname(v, this._parts.protocol);
2208
2209      if (!this._parts.hostname || this.is('IP')) {
2210        this._parts.hostname = v;
2211      } else {
2212        var replace = new RegExp(escapeRegEx(this.domain()) + '$');
2213        this._parts.hostname = this._parts.hostname.replace(replace, v);
2214      }
2215
2216      this.build(!build);
2217      return this;
2218    }
2219  };
2220  p.tld = function(v, build) {
2221    if (this._parts.urn) {
2222      return v === undefined ? '' : this;
2223    }
2224
2225    if (typeof v === 'boolean') {
2226      build = v;
2227      v = undefined;
2228    }
2229
2230    // return "org" from "www.example.org"
2231    if (v === undefined) {
2232      if (!this._parts.hostname || this.is('IP')) {
2233        return '';
2234      }
2235
2236      var pos = this._parts.hostname.lastIndexOf('.');
2237      var tld = this._parts.hostname.substring(pos + 1);
2238
2239      if (build !== true && SLD && SLD.list[tld.toLowerCase()]) {
2240        return SLD.get(this._parts.hostname) || tld;
2241      }
2242
2243      return tld;
2244    } else {
2245      var replace;
2246
2247      if (!v) {
2248        throw new TypeError('cannot set TLD empty');
2249      } else if (v.match(/[^a-zA-Z0-9-]/)) {
2250        if (SLD && SLD.is(v)) {
2251          replace = new RegExp(escapeRegEx(this.tld()) + '$');
2252          this._parts.hostname = this._parts.hostname.replace(replace, v);
2253        } else {
2254          throw new TypeError('TLD "' + v + '" contains characters other than [A-Z0-9]');
2255        }
2256      } else if (!this._parts.hostname || this.is('IP')) {
2257        throw new ReferenceError('cannot set TLD on non-domain host');
2258      } else {
2259        replace = new RegExp(escapeRegEx(this.tld()) + '$');
2260        this._parts.hostname = this._parts.hostname.replace(replace, v);
2261      }
2262
2263      this.build(!build);
2264      return this;
2265    }
2266  };
2267  p.directory = function(v, build) {
2268    if (this._parts.urn) {
2269      return v === undefined ? '' : this;
2270    }
2271
2272    if (v === undefined || v === true) {
2273      if (!this._parts.path && !this._parts.hostname) {
2274        return '';
2275      }
2276
2277      if (this._parts.path === '/') {
2278        return '/';
2279      }
2280
2281      var end = this._parts.path.length - this.filename().length - 1;
2282      var res = this._parts.path.substring(0, end) || (this._parts.hostname ? '/' : '');
2283
2284      return v ? URI.decodePath(res) : res;
2285
2286    } else {
2287      var e = this._parts.path.length - this.filename().length;
2288      var directory = this._parts.path.substring(0, e);
2289      var replace = new RegExp('^' + escapeRegEx(directory));
2290
2291      // fully qualifier directories begin with a slash
2292      if (!this.is('relative')) {
2293        if (!v) {
2294          v = '/';
2295        }
2296
2297        if (v.charAt(0) !== '/') {
2298          v = '/' + v;
2299        }
2300      }
2301
2302      // directories always end with a slash
2303      if (v && v.charAt(v.length - 1) !== '/') {
2304        v += '/';
2305      }
2306
2307      v = URI.recodePath(v);
2308      this._parts.path = this._parts.path.replace(replace, v);
2309      this.build(!build);
2310      return this;
2311    }
2312  };
2313  p.filename = function(v, build) {
2314    if (this._parts.urn) {
2315      return v === undefined ? '' : this;
2316    }
2317
2318    if (typeof v !== 'string') {
2319      if (!this._parts.path || this._parts.path === '/') {
2320        return '';
2321      }
2322
2323      var pos = this._parts.path.lastIndexOf('/');
2324      var res = this._parts.path.substring(pos+1);
2325
2326      return v ? URI.decodePathSegment(res) : res;
2327    } else {
2328      var mutatedDirectory = false;
2329
2330      if (v.charAt(0) === '/') {
2331        v = v.substring(1);
2332      }
2333
2334      if (v.match(/\.?\//)) {
2335        mutatedDirectory = true;
2336      }
2337
2338      var replace = new RegExp(escapeRegEx(this.filename()) + '$');
2339      v = URI.recodePath(v);
2340      this._parts.path = this._parts.path.replace(replace, v);
2341
2342      if (mutatedDirectory) {
2343        this.normalizePath(build);
2344      } else {
2345        this.build(!build);
2346      }
2347
2348      return this;
2349    }
2350  };
2351  p.suffix = function(v, build) {
2352    if (this._parts.urn) {
2353      return v === undefined ? '' : this;
2354    }
2355
2356    if (v === undefined || v === true) {
2357      if (!this._parts.path || this._parts.path === '/') {
2358        return '';
2359      }
2360
2361      var filename = this.filename();
2362      var pos = filename.lastIndexOf('.');
2363      var s, res;
2364
2365      if (pos === -1) {
2366        return '';
2367      }
2368
2369      // suffix may only contain alnum characters (yup, I made this up.)
2370      s = filename.substring(pos+1);
2371      res = (/^[a-z0-9%]+$/i).test(s) ? s : '';
2372      return v ? URI.decodePathSegment(res) : res;
2373    } else {
2374      if (v.charAt(0) === '.') {
2375        v = v.substring(1);
2376      }
2377
2378      var suffix = this.suffix();
2379      var replace;
2380
2381      if (!suffix) {
2382        if (!v) {
2383          return this;
2384        }
2385
2386        this._parts.path += '.' + URI.recodePath(v);
2387      } else if (!v) {
2388        replace = new RegExp(escapeRegEx('.' + suffix) + '$');
2389      } else {
2390        replace = new RegExp(escapeRegEx(suffix) + '$');
2391      }
2392
2393      if (replace) {
2394        v = URI.recodePath(v);
2395        this._parts.path = this._parts.path.replace(replace, v);
2396      }
2397
2398      this.build(!build);
2399      return this;
2400    }
2401  };
2402  p.segment = function(segment, v, build) {
2403    var separator = this._parts.urn ? ':' : '/';
2404    var path = this.path();
2405    var absolute = path.substring(0, 1) === '/';
2406    var segments = path.split(separator);
2407
2408    if (segment !== undefined && typeof segment !== 'number') {
2409      build = v;
2410      v = segment;
2411      segment = undefined;
2412    }
2413
2414    if (segment !== undefined && typeof segment !== 'number') {
2415      throw new Error('Bad segment "' + segment + '", must be 0-based integer');
2416    }
2417
2418    if (absolute) {
2419      segments.shift();
2420    }
2421
2422    if (segment < 0) {
2423      // allow negative indexes to address from the end
2424      segment = Math.max(segments.length + segment, 0);
2425    }
2426
2427    if (v === undefined) {
2428      /*jshint laxbreak: true */
2429      return segment === undefined
2430        ? segments
2431        : segments[segment];
2432      /*jshint laxbreak: false */
2433    } else if (segment === null || segments[segment] === undefined) {
2434      if (isArray(v)) {
2435        segments = [];
2436        // collapse empty elements within array
2437        for (var i=0, l=v.length; i < l; i++) {
2438          if (!v[i].length && (!segments.length || !segments[segments.length -1].length)) {
2439            continue;
2440          }
2441
2442          if (segments.length && !segments[segments.length -1].length) {
2443            segments.pop();
2444          }
2445
2446          segments.push(trimSlashes(v[i]));
2447        }
2448      } else if (v || typeof v === 'string') {
2449        v = trimSlashes(v);
2450        if (segments[segments.length -1] === '') {
2451          // empty trailing elements have to be overwritten
2452          // to prevent results such as /foo//bar
2453          segments[segments.length -1] = v;
2454        } else {
2455          segments.push(v);
2456        }
2457      }
2458    } else {
2459      if (v) {
2460        segments[segment] = trimSlashes(v);
2461      } else {
2462        segments.splice(segment, 1);
2463      }
2464    }
2465
2466    if (absolute) {
2467      segments.unshift('');
2468    }
2469
2470    return this.path(segments.join(separator), build);
2471  };
2472  p.segmentCoded = function(segment, v, build) {
2473    var segments, i, l;
2474
2475    if (typeof segment !== 'number') {
2476      build = v;
2477      v = segment;
2478      segment = undefined;
2479    }
2480
2481    if (v === undefined) {
2482      segments = this.segment(segment, v, build);
2483      if (!isArray(segments)) {
2484        segments = segments !== undefined ? URI.decode(segments) : undefined;
2485      } else {
2486        for (i = 0, l = segments.length; i < l; i++) {
2487          segments[i] = URI.decode(segments[i]);
2488        }
2489      }
2490
2491      return segments;
2492    }
2493
2494    if (!isArray(v)) {
2495      v = (typeof v === 'string' || v instanceof String) ? URI.encode(v) : v;
2496    } else {
2497      for (i = 0, l = v.length; i < l; i++) {
2498        v[i] = URI.encode(v[i]);
2499      }
2500    }
2501
2502    return this.segment(segment, v, build);
2503  };
2504
2505  // mutating query string
2506  var q = p.query;
2507  p.query = function(v, build) {
2508    if (v === true) {
2509      return URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
2510    } else if (typeof v === 'function') {
2511      var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
2512      var result = v.call(this, data);
2513      this._parts.query = URI.buildQuery(result || data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
2514      this.build(!build);
2515      return this;
2516    } else if (v !== undefined && typeof v !== 'string') {
2517      this._parts.query = URI.buildQuery(v, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
2518      this.build(!build);
2519      return this;
2520    } else {
2521      return q.call(this, v, build);
2522    }
2523  };
2524  p.setQuery = function(name, value, build) {
2525    var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
2526
2527    if (typeof name === 'string' || name instanceof String) {
2528      data[name] = value !== undefined ? value : null;
2529    } else if (typeof name === 'object') {
2530      for (var key in name) {
2531        if (hasOwn.call(name, key)) {
2532          data[key] = name[key];
2533        }
2534      }
2535    } else {
2536      throw new TypeError('URI.addQuery() accepts an object, string as the name parameter');
2537    }
2538
2539    this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
2540    if (typeof name !== 'string') {
2541      build = value;
2542    }
2543
2544    this.build(!build);
2545    return this;
2546  };
2547  p.addQuery = function(name, value, build) {
2548    var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
2549    URI.addQuery(data, name, value === undefined ? null : value);
2550    this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
2551    if (typeof name !== 'string') {
2552      build = value;
2553    }
2554
2555    this.build(!build);
2556    return this;
2557  };
2558  p.removeQuery = function(name, value, build) {
2559    var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
2560    URI.removeQuery(data, name, value);
2561    this._parts.query = URI.buildQuery(data, this._parts.duplicateQueryParameters, this._parts.escapeQuerySpace);
2562    if (typeof name !== 'string') {
2563      build = value;
2564    }
2565
2566    this.build(!build);
2567    return this;
2568  };
2569  p.hasQuery = function(name, value, withinArray) {
2570    var data = URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace);
2571    return URI.hasQuery(data, name, value, withinArray);
2572  };
2573  p.setSearch = p.setQuery;
2574  p.addSearch = p.addQuery;
2575  p.removeSearch = p.removeQuery;
2576  p.hasSearch = p.hasQuery;
2577
2578  // sanitizing URLs
2579  p.normalize = function() {
2580    if (this._parts.urn) {
2581      return this
2582        .normalizeProtocol(false)
2583        .normalizePath(false)
2584        .normalizeQuery(false)
2585        .normalizeFragment(false)
2586        .build();
2587    }
2588
2589    return this
2590      .normalizeProtocol(false)
2591      .normalizeHostname(false)
2592      .normalizePort(false)
2593      .normalizePath(false)
2594      .normalizeQuery(false)
2595      .normalizeFragment(false)
2596      .build();
2597  };
2598  p.normalizeProtocol = function(build) {
2599    if (typeof this._parts.protocol === 'string') {
2600      this._parts.protocol = this._parts.protocol.toLowerCase();
2601      this.build(!build);
2602    }
2603
2604    return this;
2605  };
2606  p.normalizeHostname = function(build) {
2607    if (this._parts.hostname) {
2608      if (this.is('IDN') && punycode) {
2609        this._parts.hostname = punycode.toASCII(this._parts.hostname);
2610      } else if (this.is('IPv6') && IPv6) {
2611        this._parts.hostname = IPv6.best(this._parts.hostname);
2612      }
2613
2614      this._parts.hostname = this._parts.hostname.toLowerCase();
2615      this.build(!build);
2616    }
2617
2618    return this;
2619  };
2620  p.normalizePort = function(build) {
2621    // remove port of it's the protocol's default
2622    if (typeof this._parts.protocol === 'string' && this._parts.port === URI.defaultPorts[this._parts.protocol]) {
2623      this._parts.port = null;
2624      this.build(!build);
2625    }
2626
2627    return this;
2628  };
2629  p.normalizePath = function(build) {
2630    var _path = this._parts.path;
2631    if (!_path) {
2632      return this;
2633    }
2634
2635    if (this._parts.urn) {
2636      this._parts.path = URI.recodeUrnPath(this._parts.path);
2637      this.build(!build);
2638      return this;
2639    }
2640
2641    if (this._parts.path === '/') {
2642      return this;
2643    }
2644
2645    _path = URI.recodePath(_path);
2646
2647    var _was_relative;
2648    var _leadingParents = '';
2649    var _parent, _pos;
2650
2651    // handle relative paths
2652    if (_path.charAt(0) !== '/') {
2653      _was_relative = true;
2654      _path = '/' + _path;
2655    }
2656
2657    // handle relative files (as opposed to directories)
2658    if (_path.slice(-3) === '/..' || _path.slice(-2) === '/.') {
2659      _path += '/';
2660    }
2661
2662    // resolve simples
2663    _path = _path
2664      .replace(/(\/(\.\/)+)|(\/\.$)/g, '/')
2665      .replace(/\/{2,}/g, '/');
2666
2667    // remember leading parents
2668    if (_was_relative) {
2669      _leadingParents = _path.substring(1).match(/^(\.\.\/)+/) || '';
2670      if (_leadingParents) {
2671        _leadingParents = _leadingParents[0];
2672      }
2673    }
2674
2675    // resolve parents
2676    while (true) {
2677      _parent = _path.search(/\/\.\.(\/|$)/);
2678      if (_parent === -1) {
2679        // no more ../ to resolve
2680        break;
2681      } else if (_parent === 0) {
2682        // top level cannot be relative, skip it
2683        _path = _path.substring(3);
2684        continue;
2685      }
2686
2687      _pos = _path.substring(0, _parent).lastIndexOf('/');
2688      if (_pos === -1) {
2689        _pos = _parent;
2690      }
2691      _path = _path.substring(0, _pos) + _path.substring(_parent + 3);
2692    }
2693
2694    // revert to relative
2695    if (_was_relative && this.is('relative')) {
2696      _path = _leadingParents + _path.substring(1);
2697    }
2698
2699    this._parts.path = _path;
2700    this.build(!build);
2701    return this;
2702  };
2703  p.normalizePathname = p.normalizePath;
2704  p.normalizeQuery = function(build) {
2705    if (typeof this._parts.query === 'string') {
2706      if (!this._parts.query.length) {
2707        this._parts.query = null;
2708      } else {
2709        this.query(URI.parseQuery(this._parts.query, this._parts.escapeQuerySpace));
2710      }
2711
2712      this.build(!build);
2713    }
2714
2715    return this;
2716  };
2717  p.normalizeFragment = function(build) {
2718    if (!this._parts.fragment) {
2719      this._parts.fragment = null;
2720      this.build(!build);
2721    }
2722
2723    return this;
2724  };
2725  p.normalizeSearch = p.normalizeQuery;
2726  p.normalizeHash = p.normalizeFragment;
2727
2728  p.iso8859 = function() {
2729    // expect unicode input, iso8859 output
2730    var e = URI.encode;
2731    var d = URI.decode;
2732
2733    URI.encode = escape;
2734    URI.decode = decodeURIComponent;
2735    try {
2736      this.normalize();
2737    } finally {
2738      URI.encode = e;
2739      URI.decode = d;
2740    }
2741    return this;
2742  };
2743
2744  p.unicode = function() {
2745    // expect iso8859 input, unicode output
2746    var e = URI.encode;
2747    var d = URI.decode;
2748
2749    URI.encode = strictEncodeURIComponent;
2750    URI.decode = unescape;
2751    try {
2752      this.normalize();
2753    } finally {
2754      URI.encode = e;
2755      URI.decode = d;
2756    }
2757    return this;
2758  };
2759
2760  p.readable = function() {
2761    var uri = this.clone();
2762    // removing username, password, because they shouldn't be displayed according to RFC 3986
2763    uri.username('').password('').normalize();
2764    var t = '';
2765    if (uri._parts.protocol) {
2766      t += uri._parts.protocol + '://';
2767    }
2768
2769    if (uri._parts.hostname) {
2770      if (uri.is('punycode') && punycode) {
2771        t += punycode.toUnicode(uri._parts.hostname);
2772        if (uri._parts.port) {
2773          t += ':' + uri._parts.port;
2774        }
2775      } else {
2776        t += uri.host();
2777      }
2778    }
2779
2780    if (uri._parts.hostname && uri._parts.path && uri._parts.path.charAt(0) !== '/') {
2781      t += '/';
2782    }
2783
2784    t += uri.path(true);
2785    if (uri._parts.query) {
2786      var q = '';
2787      for (var i = 0, qp = uri._parts.query.split('&'), l = qp.length; i < l; i++) {
2788        var kv = (qp[i] || '').split('=');
2789        q += '&' + URI.decodeQuery(kv[0], this._parts.escapeQuerySpace)
2790          .replace(/&/g, '%26');
2791
2792        if (kv[1] !== undefined) {
2793          q += '=' + URI.decodeQuery(kv[1], this._parts.escapeQuerySpace)
2794            .replace(/&/g, '%26');
2795        }
2796      }
2797      t += '?' + q.substring(1);
2798    }
2799
2800    t += URI.decodeQuery(uri.hash(), true);
2801    return t;
2802  };
2803
2804  // resolving relative and absolute URLs
2805  p.absoluteTo = function(base) {
2806    var resolved = this.clone();
2807    var properties = ['protocol', 'username', 'password', 'hostname', 'port'];
2808    var basedir, i, p;
2809
2810    if (this._parts.urn) {
2811      throw new Error('URNs do not have any generally defined hierarchical components');
2812    }
2813
2814    if (!(base instanceof URI)) {
2815      base = new URI(base);
2816    }
2817
2818    if (resolved._parts.protocol) {
2819      // Directly returns even if this._parts.hostname is empty.
2820      return resolved;
2821    } else {
2822      resolved._parts.protocol = base._parts.protocol;
2823    }
2824
2825    if (this._parts.hostname) {
2826      return resolved;
2827    }
2828
2829    for (i = 0; (p = properties[i]); i++) {
2830      resolved._parts[p] = base._parts[p];
2831    }
2832
2833    if (!resolved._parts.path) {
2834      resolved._parts.path = base._parts.path;
2835      if (!resolved._parts.query) {
2836        resolved._parts.query = base._parts.query;
2837      }
2838    } else {
2839      if (resolved._parts.path.substring(-2) === '..') {
2840        resolved._parts.path += '/';
2841      }
2842
2843      if (resolved.path().charAt(0) !== '/') {
2844        basedir = base.directory();
2845        basedir = basedir ? basedir : base.path().indexOf('/') === 0 ? '/' : '';
2846        resolved._parts.path = (basedir ? (basedir + '/') : '') + resolved._parts.path;
2847        resolved.normalizePath();
2848      }
2849    }
2850
2851    resolved.build();
2852    return resolved;
2853  };
2854  p.relativeTo = function(base) {
2855    var relative = this.clone().normalize();
2856    var relativeParts, baseParts, common, relativePath, basePath;
2857
2858    if (relative._parts.urn) {
2859      throw new Error('URNs do not have any generally defined hierarchical components');
2860    }
2861
2862    base = new URI(base).normalize();
2863    relativeParts = relative._parts;
2864    baseParts = base._parts;
2865    relativePath = relative.path();
2866    basePath = base.path();
2867
2868    if (relativePath.charAt(0) !== '/') {
2869      throw new Error('URI is already relative');
2870    }
2871
2872    if (basePath.charAt(0) !== '/') {
2873      throw new Error('Cannot calculate a URI relative to another relative URI');
2874    }
2875
2876    if (relativeParts.protocol === baseParts.protocol) {
2877      relativeParts.protocol = null;
2878    }
2879
2880    if (relativeParts.username !== baseParts.username || relativeParts.password !== baseParts.password) {
2881      return relative.build();
2882    }
2883
2884    if (relativeParts.protocol !== null || relativeParts.username !== null || relativeParts.password !== null) {
2885      return relative.build();
2886    }
2887
2888    if (relativeParts.hostname === baseParts.hostname && relativeParts.port === baseParts.port) {
2889      relativeParts.hostname = null;
2890      relativeParts.port = null;
2891    } else {
2892      return relative.build();
2893    }
2894
2895    if (relativePath === basePath) {
2896      relativeParts.path = '';
2897      return relative.build();
2898    }
2899
2900    // determine common sub path
2901    common = URI.commonPath(relativePath, basePath);
2902
2903    // If the paths have nothing in common, return a relative URL with the absolute path.
2904    if (!common) {
2905      return relative.build();
2906    }
2907
2908    var parents = baseParts.path
2909      .substring(common.length)
2910      .replace(/[^\/]*$/, '')
2911      .replace(/.*?\//g, '../');
2912
2913    relativeParts.path = (parents + relativeParts.path.substring(common.length)) || './';
2914
2915    return relative.build();
2916  };
2917
2918  // comparing URIs
2919  p.equals = function(uri) {
2920    var one = this.clone();
2921    var two = new URI(uri);
2922    var one_map = {};
2923    var two_map = {};
2924    var checked = {};
2925    var one_query, two_query, key;
2926
2927    one.normalize();
2928    two.normalize();
2929
2930    // exact match
2931    if (one.toString() === two.toString()) {
2932      return true;
2933    }
2934
2935    // extract query string
2936    one_query = one.query();
2937    two_query = two.query();
2938    one.query('');
2939    two.query('');
2940
2941    // definitely not equal if not even non-query parts match
2942    if (one.toString() !== two.toString()) {
2943      return false;
2944    }
2945
2946    // query parameters have the same length, even if they're permuted
2947    if (one_query.length !== two_query.length) {
2948      return false;
2949    }
2950
2951    one_map = URI.parseQuery(one_query, this._parts.escapeQuerySpace);
2952    two_map = URI.parseQuery(two_query, this._parts.escapeQuerySpace);
2953
2954    for (key in one_map) {
2955      if (hasOwn.call(one_map, key)) {
2956        if (!isArray(one_map[key])) {
2957          if (one_map[key] !== two_map[key]) {
2958            return false;
2959          }
2960        } else if (!arraysEqual(one_map[key], two_map[key])) {
2961          return false;
2962        }
2963
2964        checked[key] = true;
2965      }
2966    }
2967
2968    for (key in two_map) {
2969      if (hasOwn.call(two_map, key)) {
2970        if (!checked[key]) {
2971          // two contains a parameter not present in one
2972          return false;
2973        }
2974      }
2975    }
2976
2977    return true;
2978  };
2979
2980  // state
2981  p.preventInvalidHostname = function(v) {
2982    this._parts.preventInvalidHostname = !!v;
2983    return this;
2984  };
2985
2986  p.duplicateQueryParameters = function(v) {
2987    this._parts.duplicateQueryParameters = !!v;
2988    return this;
2989  };
2990
2991  p.escapeQuerySpace = function(v) {
2992    this._parts.escapeQuerySpace = !!v;
2993    return this;
2994  };
2995
2996  return URI;
2997}));
2998// source --> /wp-content/themes/sae-2021/resources/js/global.js?v=188 
2999	(function($){
3000
3001		$(function(){
3002
3003		var lastUpdate = $("meta[property='article:modified_time']").attr("content");
3004console.log('LASTUPDATE:'+lastUpdate);
3005		$('.footer_copyright-content').append('<span id="site-last-updated">Last update: '+lastUpdate+'</span>')
3006
3007		// autoSuggest -> mock the "delete value" function for input fields
3008		$('.wfAutosuggest').on('click', function(){
3009			$(this).parent().siblings('.fa.fa-times-circle.tt-clear').click();
3010		});
3011
3012		// reformat dates with spaces that salesforce accept them as a valid date
3013		$('.validate-datecal').on('focusout', function(){
3014			$(this).val($(this).val().trim().replaceAll(' ', '/'));
3015		});
3016
3017		/**
3018		 * Add get-parameters to a.href's.
3019		 */
3020		if(typeof URI === 'function'){
3021			/**
3022			 * Save get-parameters, to use in replaceAHrefs().
3023			 * But don't save array-get-parameters, these are only used on the current page.
3024			 * Don't save Search Filter params (fs_*) — they must stay on the target link, not accumulate.
3025			 * And don't save "p", "page" and "page_id" becaus they try to 
3025show another page and thats results in a 404.
3026			 * Hint: pages with reloading content elemens are removing get-parameters they dont use, but they do this later ╭∩╮(・◡・)╭∩╮
3027			 */
3028			let uriQueryData = new URI(location.href).query(true);
3029			for(let d in uriQueryData){
3030				if(d.slice(-2) === '[]' || d.slice(0, 3) === 'fs_' || d === 'p' || d === 'page' || d === 'page_id' || d === 'post_type'){
3031					delete (uriQueryData[d]);
3032				}
3033			}
3034
3035			/**
3036			 * Monitor if there are elements that change their content on user action.
3037			 * And replaceAHrefs() when new content is loaded.
3038			 */
3039			var elementsWithReloadingContent = [
3040				events = $('.events_filter-initial_results'),
3041				courses = $('.courses_filter-results'),
3042				search = $('#search_modal_results')
3043			];
3044
3045			// STEP 1 - save old events container data
3046			var oldDataAlgoliaData = [];
3047			for (let element of elementsWithReloadingContent) {
3048				if (element.length) {
3049					oldDataAlgoliaData[element.attr('class')] = element.html();
3050				}
3051			}
3052
3053			// STEP 2
3054			// Now we watch for new events container elements coming from Algolia
3055			const observer = new MutationObserver(() => {
3056				for (let element of elementsWithReloadingContent) {
3057					if (element.length) {
3058						let oldHtml = oldDataAlgoliaData[element.attr('class')] !== undefined ? oldDataAlgoliaData[element.attr('class')] : element.html();
3059						if (oldHtml != element.html()) {
3060							oldHtml = element.html();
3061							replaceAHrefs(element);
3062							// write new data back in data array
3063							oldDataAlgoliaData[element.attr('class')] = element.html();
3064						}
3065					}
3066				}
3067			});
3068
3069			// STEP 3 - Init Observer
3070			// Param 1 of observe() must be a Node - document is a valid Node and is looking for changes on the "page"
3071			observer.observe(document.getElementById('page'), {
3072				childList: true,
3073				subtree: true
3074			});
3075
3076			/*for(let element of elementsWithReloadingContent){
3077				if(element.length){
3078					let oldHtml = element.html();
3079					element.on('DOMNodeInserted', function(){
3080						if(oldHtml != element.html()){
3081							oldHtml = element.html();
3082							replaceAHrefs(element);
3083						}
3084					});
3085				}
3086			}*/
3087
3088			/**
3089			 * Add at all locally a.href's the saved get-parameters
3090			 *
3091			 * @param element jQuery Element whose content should be changed
3092			 */
3093			function replaceAHrefs(element){
3094				element.find('a').each(function(){
3095					let $a = $(this);
3096					let aHref = $a.attr('href');
3097					if(aHref){
3098						let href = new URI(aHref);
3099						let hostname = href.hostname();
3100						if(!hostname || hostname === location.hostname){
3101							href = href.addQuery(uriQueryData);
3102							href = aHref.slice(-1) === '#' ? href + '#' : href;
3103							$a.attr('href', href);
3104						}
3105					}
3106				});
3107			}
3108
3109			// do it on load
3110			replaceAHrefs($('body'));
3111		}
3112
3113
3114		// Cookie pref center triggers
3115		(function(){
3116			const countriesNotUsingCookieWidgetClickPrevention = [ 'ita' ]; // SAE-3070
3117			countriesNotUsingCookieWidgetClickPrevention.forEach( function( country) {
3118				if( window.location.href.indexOf( '/' + country + '/' ) === -1 ) {
3119					$('a[href="#cookiepolicy"], a[href$="/cookie-policy/"], a[href$="/cookie-policy"]').on('click', function (e) {
3120						e.preventDefault();
3121						if (window.location.href.indexOf('/gbr/') !== -1) {
3122							document.querySelector('button.CookiebotWidget-logo').click(); // SAE-2375
3123						} else {
3124							document.querySelector('#BorlabsCookieWidget .brlbs-cmpnt-widget').click();
3125						}
3126						return false;
3127					});
3128				}
3129			});
3130		}());
3131
3132
3133		// SAE-1910
3134		if(location.href.indexOf('/nld/') > -1){
3135			// home page
3136			$audioIcon = $('.discipline_tiles-container--desktop .discipline-theme--audio + img + div > svg').clone()
3137			$('.discipline_tiles-container--desktop .discipline-theme--emc + img + div').prepend($audioIcon)
3138			$audioIcon = $audioIcon.clone()
3139			$('.discipline_tiles-container--mobile .discipline-theme--emc + img + div').prepend($audioIcon)
3140console.debug($audioIcon)
3141			$filmIcon = $('.discipline_tiles-container--desktop .discipline-theme--film + img + div > svg').clone()
3142			$('.discipline_tiles-container--desktop .discipline-theme--film-2 + img + div').prepend($filmIcon)
3143			$filmIcon = $filmIcon.clone()
3144			$('.discipline_tiles-container--mobile .discipline-theme--film-2 + img + div').prepend($filmIcon)
3145console.debug($filmIcon)
3146			// courses page
3147			$audioIcon = $('.courses_filter-disciplines_tiles--desktop .discipline-theme--audio + img + div > svg').clone()
3148			$('.courses_filter-disciplines_tiles--desktop .discipline-theme--emc + img + div').prepend($audioIcon)
3149			$audioIcon = $audioIcon.clone()
3150			$('.courses_filter-disciplines_tiles--mobile .discipline-theme--emc + img + div').prepend($audioIcon)
3151console.debug($audioIcon)
3152			$filmIcon = $('.courses_filter-disciplines_tiles--desktop .discipline-theme--film + img + div > svg').clone()
3153			$('.courses_filter-disciplines_tiles--desktop .discipline-theme--film-2 + img + div').prepend($filmIcon)
3154			$filmIcon = $filmIcon.clone()
3155			$('.courses_filter-disciplines_tiles--mobile .discipline-theme--film-2 + img + div').prepend($filmIcon)
3156console.debug($filmIcon)
3157		}
3158
3159		// send user, after opening a student gallery page as link back to previous page "on close"
3160		if ($('.single-student_work-data').length){
3161			$('.modal-close').off('click').on('click', function(event){
3162				event.preventDefault();
3163				history.back(1);
3164				return false;
3165			});
3166		}
3167
3168			// SAE-3357 SPECIAL HANDLING for FRA Chat System which is not working, if a modal window is active
3169			if (location.href.indexOf('/fra/') > -1) {
3170				$('body').on('focus', '.crisp-client textarea', function () {
3171					if ($('.modal-close_wrapper').is(':visible')) {
3172						$('.modal-close_wrapper').hide();
3173					}
3174				});
3175				$('body').on('blur', '.crisp-client textarea', function () {
3176					$('.modal-close_wrapper').show();
3177				});
3178			}
3179
3180		});
3181
3182})(jQuery);

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.