/*
general edit javascript
jquery version
*/

/** @deprecated Do not use this anymore, use utils.js or utils.min.js */

/* global i18n */

// debug set
/*var FRONTEND_DEBUG = false;
var DEBUG = true;
if (!DEBUG) {
	$($H(window.console)).each(function(w) {
		window.console[w.key] = function() {};
	});
}*/

// open overlay boxes counter for z-index
var GL_OB_S = 100;
var GL_OB_BASE = 100;

/**
 * Gets html element or throws an error
 * @param {string} el_id Element ID to get
 * @returns {HTMLElement}
 * @throws Error
 * @deprecated use utils.js
 */
function loadEl(el_id)
{
	let el = document.getElementById(el_id);
	if (el === null) {
		throw new Error('Cannot find: ' + el_id);
	}
	return el;
}

/**
 * opens a popup window with winName and given features (string)
 * @param {String} theURL   the url
 * @param {String} winName  window name
 * @param {Object} features popup features
 * @deprecated use utils.js
 */
function pop(theURL, winName, features) // eslint-disable-line no-unused-vars
{
	winName = window.open(theURL, winName, features);
	winName.focus();
}

/**
 * automatically resize a text area based on the amount of lines in it
 * @param {string} ta_id element id
 * @deprecated use utils.js
 */
function expandTA(ta_id) // eslint-disable-line no-unused-vars
{
	var ta;
	// if a string comes, its a get by id, else use it as an element pass on
	if (!ta_id.length) {
		ta = ta_id;
	} else {
		ta = document.getElementById(ta_id);
	}
	var maxChars = ta.cols;
	var theRows = ta.value.split('\n');
	var numNewRows = 0;

	for ( var i = 0; i < theRows.length; i++ ) {
		if ((theRows[i].length+2) > maxChars) {
			numNewRows += Math.ceil( (theRows[i].length+2) / maxChars ) ;
		}
	}
	ta.rows = numNewRows + theRows.length;
}

/**
 * wrapper to get the real window size for the current browser window
 * @return {Object} object with width/height
 * @deprecated use utils.js
 */
function getWindowSize()
{
	var width, height;
	width = window.innerWidth || (window.document.documentElement.clientWidth || window.document.body.clientWidth);
	height = window.innerHeight || (window.document.documentElement.clientHeight || window.document.body.clientHeight);
	return {
		width: width,
		height: height
	};
}

/**
 * wrapper to get the correct scroll offset
 * @return {Object} object with x/y px
 * @deprecated use utils.js
 */
function getScrollOffset()
{
	var left, top;
	left = window.pageXOffset || (window.document.documentElement.scrollLeft || window.document.body.scrollLeft);
	top = window.pageYOffset || (window.document.documentElement.scrollTop || window.document.body.scrollTop);
	return {
		left: left,
		top: top
	};
}

/**
 * wrapper to get the correct scroll offset for opener page (from popup)
 * @return {Object} object with x/y px
 * @deprecated use utils.js
 */
function getScrollOffsetOpener() // eslint-disable-line no-unused-vars
{
	var left, top;
	left = opener.window.pageXOffset || (opener.document.documentElement.scrollLeft || opener.document.body.scrollLeft);
	top = opener.window.pageYOffset || (opener.document.documentElement.scrollTop || opener.document.body.scrollTop);
	return {
		left: left,
		top: top
	};
}

/**
 * centers div to current window size middle
 * @param {String}  id   element to center
 * @param {Boolean} left if true centers to the middle from the left
 * @param {Boolean} top  if true centers to the middle from the top
 * @deprecated use utils.js
 */
function setCenter(id, left, top)
{
	// get size of id
	var dimensions = {
		height: $('#' + id).height(),
		width: $('#' + id).width()
	};
	var type = $('#' + id).css('position');
	var viewport = getWindowSize();
	var offset = getScrollOffset();

	// console.log('Id %s, type: %s, dimensions %s x %s, viewport %s x %s', id, type, dimensions.width, dimensions.height, viewport.width, viewport.height);
	// console.log('Scrolloffset left: %s, top: %s', offset.left, offset.top);
	// console.log('Left: %s, Top: %s (%s)', parseInt((viewport.width / 2) - (dimensions.width / 2) + offset.left), parseInt((viewport.height / 2) - (dimensions.height / 2) + offset.top), parseInt((viewport.height / 2) - (dimensions.height / 2)));
	if (left) {
		$('#' + id).css({
			left: parseInt((viewport.width / 2) - (dimensions.width / 2) + offset.left) + 'px'
		});
	}
	if (top) {
		// if we have fixed, we do not add the offset, else it moves out of the screen
		var top_pos = type == 'fixed' ?
			parseInt((viewport.height / 2) - (dimensions.height / 2)) :
			parseInt((viewport.height / 2) - (dimensions.height / 2) + offset.top);
		$('#' + id).css({
			top: top_pos + 'px'
		});
	}
}

/**
 * goes to an element id position
 * @param {String} element            element id to move to
 * @param {Number} [offset=0]         offset from top, default is 0 (px)
 * @param {Number} [duration=500]     animation time, default 500ms
 * @param {String} [base='body,html'] base element for offset scroll
 * @deprecated use utils.js
 */
function goToPos(element, offset = 0, duration = 500, base = 'body,html') // eslint-disable-line no-unused-vars
{
	try {
		if ($('#' + element).length) {
			$(base).animate({
				scrollTop: $('#' + element).offset().top - offset
			}, duration);
		}
	} catch (err) {
		errorCatch(err);
	}
}

/**
 * go to element, scroll
 * non jquery
 * @param {string} target
 * @deprecated use utils.js
*/
function goTo(target) // eslint-disable-line no-unused-vars
{
	loadEl(target).scrollIntoView({
		behavior: 'smooth'
	});
}

/**
 * uses the i18n object created in the translation template
 * that is filled from gettext in PHP
 * @param  {String} string text to translate
 * @return {String}        translated text (based on PHP selected language)
 * @deprecated use utils.js
 */
function __(string)
{
	if (typeof i18n !== 'undefined' && isObject(i18n) && i18n[string]) {
		return i18n[string];
	} else {
		return string;
	}
}

/**
 * simple sprintf formater for replace
 * usage: "{0} is cool, {1} is not".format("Alpha", "Beta");
 * First, checks if it isn't implemented yet.
 * @param  {String} String.prototype.format string with elements to be replaced
 * @return {String}                         Formated string
 * @deprecated use utils.js
 */
if (!String.prototype.format) {
	String.prototype.format = function()
	{
		console.error('[DEPRECATED] use formatString');
		return formatString(this, arguments);
	};
}

/**
 * simple sprintf formater for replace
 * usage: "{0} is cool, {1} is not".format("Alpha", "Beta");
 * First, checks if it isn't implemented yet.
 * @param  {String} string String with {..} entries
 * @param  {...any} args   List of replacement
 * @returns {String}       Escaped string
 * @deprecated use utils.js
 */
function formatString(string, ...args)
{
	return string.replace(/{(\d+)}/g, function(match, number)
	{
		return typeof args[number] != 'undefined' ?
			args[number] :
			match
		;
	});
}

/**
 * round to digits (float)
 * @param  {Number}  Number.prototype.round Float type number to round
 * @param  {Number} prec                   Precision to round to
 * @return {Float}                         Rounded number
 * @deprecated use utils.js
 */
if (Number.prototype.round) {
	Number.prototype.round = function (prec) {
		console.error('[DEPRECATED] use roundPrecision');
		return roundPrecision(this, prec);
	};
}

/**
 * round to digits (float)
 * @param  {Number} number    Float type number to round
 * @param  {Number} precision Precision to round to
 * @return {Number}           Rounded number
 * @deprecated use utils.js
 */
function roundPrecision(number, precision)
{
	if (!isNaN(number) || !isNaN(precision)) {
		return number;
	}
	return Math.round(number * Math.pow(10, precision)) / Math.pow(10, precision);
}

/**
 * formats flat number 123456 to 123,456
 * @param  {Number} x number to be formated
 * @return {String}   formatted with , in thousands
 * @deprecated use utils.js
 */
function numberWithCommas(x) // eslint-disable-line no-unused-vars
{
	var parts = x.toString().split('.');
	parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
	return parts.join('.');
}

/**
 * converts line breaks to br
 * @param  {String} string any string
 * @return {String}        string with <br>
 * @deprecated use utils.js
 */
function convertLBtoBR(string) // eslint-disable-line no-unused-vars
{
	return string.replace(/(?:\r\n|\r|\n)/g, '<br>');
}

/**
 * escape HTML string
 * @param  {String} String.prototype.escapeHTML HTML data string to be escaped
 * @return {String}                             escaped string
 * @deprecated use utils.js
 */
if (!String.prototype.escapeHTML) {
	String.prototype.escapeHTML = function() {
		console.error('[DEPRECATED] use escapeHtml');
		return escapeHtml(this);
	};
}

/**
 * unescape a HTML encoded string
 * @param  {String} String.prototype.unescapeHTML data with escaped entries
 * @return {String}                               HTML formated string
 * @deprecated use utils.js
 */
if (!String.prototype.unescapeHTML) {
	String.prototype.unescapeHTML = function() {
		console.error('[DEPRECATED] use unescapeHtml');
		return unescapeHtml(this);
	};
}

/**
 * Escapes HTML in string
 * @param {String} string Text to escape HTML in
 * @returns {String}
 * @deprecated use utils.js
 */
function escapeHtml(string)
{
	return string.replace(/[&<>"'/]/g, function (s) {
		var entityMap = {
			'&': '&amp;',
			'<': '&lt;',
			'>': '&gt;',
			'"': '&quot;',
			'\'': '&#39;',
			'/': '&#x2F;'
		};

		return entityMap[s];
	});
}

/**
 * Unescape a HTML encoded string
 * @param {String} string Text to unescape HTML in
 * @returns {String}
 * @deprecated use utils.js
 */
function unescapeHtml(string)
{
	return string.replace(/&[#\w]+;/g, function (s) {
		var entityMap = {
			'&amp;': '&',
			'&lt;': '<',
			'&gt;': '>',
			'&quot;': '"',
			'&#39;': '\'',
			'&#x2F;': '/'
		};

		return entityMap[s];
	});
}

/**
 * returns current timestamp (unix timestamp)
 * @return {Number} timestamp (in milliseconds)
 * @deprecated use utils.js
 */
function getTimestamp() // eslint-disable-line no-unused-vars
{
	var date = new Date();
	return date.getTime();
}

/**
 * dec2hex :: Integer -> String
 * i.e. 0-255 -> '00'-'ff'
 * @param  {Number} dec decimal string
 * @return {String}     hex encdoded number
 * @deprecated use utils.js
 */
function dec2hex(dec)
{
	return ('0' + dec.toString(16)).substr(-2);
}

/**
 * generateId :: Integer -> String
 * only works on mondern browsers
 * @param  {Number} len length of unique id string
 * @return {String}     random string in length of len
 * @deprecated use utils.js
 */
function generateId(len) // eslint-disable-line no-unused-vars
{
	var arr = new Uint8Array((len || 40) / 2);
	(window.crypto || window.msCrypto).getRandomValues(arr);
	return Array.from(arr, dec2hex).join('');
}

/**
 * creates a pseudo random string of 10 characters
 * works on all browsers
 * after many runs it will create duplicates
 * @return {String} not true random string
 * @deprecated use utils.js
 */
function randomIdF() // eslint-disable-line no-unused-vars
{
	return Math.random().toString(36).substring(2);
}

/**
 * generate a number between min/max
 * with min/max inclusive.
 * eg: 1,5 will create a number ranging from 1 o 5
 * @param  {Number} min minimum int number inclusive
 * @param  {Number} max maximumg int number inclusive
 * @return {Number}     Random number
 * @deprecated use utils.js
 */
function getRandomIntInclusive(min, max) // eslint-disable-line no-unused-vars
{
	min = Math.ceil(min);
	max = Math.floor(max);
	// The maximum is inclusive and the minimum is inclusive
	return Math.floor(Math.random() * (max - min + 1) + min);
}

/**
 * check if name is a function
 * @param  {string}  name Name of function to check if exists
 * @return {Boolean}      true/false
 * @deprecated use utils.js
 */
function isFunction(name) // eslint-disable-line no-unused-vars
{
	if (typeof window[name] !== 'undefined' &&
		typeof window[name] === 'function') {
		return true;
	} else {
		return false;
	}
}

/**
 * call a function by its string name
 * https://stackoverflow.com/a/359910
 * example: executeFunctionByName("My.Namespace.functionName", window, arguments);
 * @param  {string} functionName The function name or namespace + function
 * @param  {mixed}  context      context (window or first namespace)
 *                               hidden next are all the arguments
 * @return {mixed}               Return values from functon
 * @deprecated use utils.js
 */
function executeFunctionByName(functionName, context /*, args */) // eslint-disable-line no-unused-vars
{
	var args = Array.prototype.slice.call(arguments, 2);
	var namespaces = functionName.split('.');
	var func = namespaces.pop();
	for (var i = 0; i < namespaces.length; i++) {
		context = context[namespaces[i]];
	}
	return context[func].apply(context, args);
}

/**
 * checks if a variable is an object
 * @param  {Mixed}   val possible object
 * @return {Boolean}     true/false if it is an object or not
 * @deprecated use utils.js
 */
function isObject(val)
{
	if (val === null) {
		return false;
	}
	return ((typeof val === 'function') || (typeof val === 'object'));
}

/**
 * get the length of an object (entries)
 * @param  {Object} object object to check
 * @return {Number} number of entry
 * @deprecated use utils.js
 */
function getObjectCount(object)
{
	return Object.keys(object).length;
}

/**
 * checks if a key exists in a given object
 * @param  {String}  key    key name
 * @param  {Object}  object object to search key in
 * @return {Boolean}        true/false if key exists in object
 * @deprecated use utils.js
 */
function keyInObject(key, object)
{
	return Object.prototype.hasOwnProperty.call(object, key) ? true : false;
}

/**
 * returns matching key of value
 * @param  {Object} object object to search value in
 * @param  {Mixed}  value  any value (String, Number, etc)
 * @return {String}        the key found for the first matching value
 * @deprecated use utils.js
 */
function getKeyByValue(object, value) // eslint-disable-line no-unused-vars
{
	return Object.keys(object).find(key => object[key] === value);
	// return Object.keys(object).find(function (key) {
	// 	return object[key] === value;
	// });
}

/**
 * returns true if value is found in object with a key
 * @param  {Object}  object object to search value in
 * @param  {Mixed}   value  any value (String, Number, etc)
 * @return {Boolean}        true on value found, false on not found
 * @deprecated use utils.js
 */
function valueInObject(object, value) // eslint-disable-line no-unused-vars
{
	return Object.keys(object).find(key => object[key] === value) ? true : false;
	// return Object.keys(object).find(function (key) {
	// 	return object[key] === value;
	// }) ? true : false;
}

/**
 * true deep copy for Javascript objects
 * if Object.assign({}, obj) is not working (shallow)
 * or if JSON.parse(JSON.stringify(obj)) is failing
 * @param  {Object} inObject Object to copy
 * @return {Object}          Copied Object
 * @deprecated use utils.js
 */
function deepCopyFunction(inObject)
{
	var outObject, value, key;
	if (typeof inObject !== 'object' || inObject === null) {
		return inObject; // Return the value if inObject is not an object
	}
	// Create an array or object to hold the values
	outObject = Array.isArray(inObject) ? [] : {};
	// loop over ech entry in object
	for (key in inObject) {
		value = inObject[key];
		// Recursively (deep) copy for nested objects, including arrays
		outObject[key] = deepCopyFunction(value);
	}

	return outObject;
}

/**
 * checks if a DOM element actually exists
 * @param  {String}  id Element id to check for
 * @return {Boolean}    true if element exists, false on failure
 * @deprecated use utils.js
 */
function exists(id)
{
	return $('#' + id).length > 0 ? true : false;
}

/**
 * converts a int number into bytes with prefix in two decimals precision
 * currently precision is fixed, if dynamic needs check for max/min precision
 * @param  {Number} bytes bytes in int
 * @return {String}       string in GB/MB/KB
 * @deprecated use utils.js
 */
function formatBytes(bytes) // eslint-disable-line no-unused-vars
{
	var i = -1;
	do {
		bytes = bytes / 1024;
		i++;
	} while (bytes > 99);
	return parseFloat(Math.round(bytes * Math.pow(10, 2)) / Math.pow(10, 2)) +
		['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
}

/**
 * like formatBytes, but returns bytes for <1KB and not 0.n KB
 * @param  {Number} bytes bytes in int
 * @return {String}       string in GB/MB/KB
 * @deprecated use utils.js
 */
function formatBytesLong(bytes) // eslint-disable-line no-unused-vars
{
	var i = Math.floor(Math.log(bytes) / Math.log(1024));
	var sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
	return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i];
}

/**
 * Convert a string with B/K/M/etc into a byte number
 * @param  {String|Number} bytes Any string with B/K/M/etc
 * @return {String|Number}       A byte number, or original string as is
 * @deprecated use utils.js
 */
function stringByteFormat(bytes) // eslint-disable-line no-unused-vars
{
	// if anything not string return
	if (!(typeof bytes === 'string' || bytes instanceof String)) {
		return bytes;
	}
	// for pow exponent list
	let valid_units = 'bkmgtpezy';
	// valid string that can be converted
	let regex = /([\d.,]*)\s?(eb|pb|tb|gb|mb|kb|e|p|t|g|m|k|b)$/i;
	let matches = bytes.match(regex);
	// if nothing found, return original input
	if (matches !== null) {
		// remove all non valid entries outside numbers and .
		// convert to float number
		let m1 = parseFloat(matches[1].replace(/[^0-9.]/,''));
		// only get the FIRST letter from the size, convert it to lower case
		let m2 = matches[2].replace(/[^bkmgtpezy]/i, '').charAt(0).toLowerCase();
		if (m2) {
			// use the position in the valid unit list to do the math conversion
			bytes = m1 * Math.pow(1024, valid_units.indexOf(m2));
		}
	}
	return bytes;
}

/**
 * prints out error messages based on data available from the browser
 * @param {Object} err error from try/catch block
 * @deprecated use utils.js
 */
function errorCatch(err)
{
	// for FF & Chrome
	if (err.stack) {
		// only FF
		if (err.lineNumber) {
			console.error('ERROR[%s:%s] ', err.name, err.lineNumber, err);
		} else if (err.line) {
			// only Safari
			console.error('ERROR[%s:%s] ', err.name, err.line, err);
		} else {
			console.error('ERROR[%s] ', err.name, err);
		}
	} else if (err.number) {
		// IE
		console.error('ERROR[%s:%s] %s', err.name, err.number, err.message);
		console.error('ERROR[description] %s', err.description);
	} else {
		// the rest
		console.error('ERROR[%s] %s', err.name, err.message);
	}
}

/*************************************************************
 * OLD action indicator and overlay boxes calls
 * DO NOT USE
 * actionIndicator -> showActionIndicator
 * actionIndicator -> hideActionIndicator
 * actionIndicatorShow -> showActionIndicator
 * actionIndicatorHide -> hideActionIndicator
 * overlayBoxShow -> showOverlayBoxLayers
 * overlayBoxHide -> hideOverlayBoxLayers
 * setOverlayBox -> showOverlayBoxLayers
 * hideOverlayBox -> hideOverlayBoxLayers
 * ClearCall -> ClearCallActionBox
 * ***********************************************************/

/**
 * show or hide the "do" overlay
 * @param {String}  loc            location name for action indicator
 *                                 default empty. for console.log
 * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block
 * @deprecated use utils.js
 */
function actionIndicator(loc, overlay = true) // eslint-disable-line no-unused-vars
{
	if ($('#indicator').is(':visible')) {
		actionIndicatorHide(loc, overlay);
	} else {
		actionIndicatorShow(loc, overlay);
	}
}

/**
 * explicit show for action Indicator
 * instead of automatically show or hide, do on command show
 * @param {String}  loc            location name for action indicator
 *                                 default empty. for console.log
 * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block
 * @deprecated use utils.js
 */
function actionIndicatorShow(loc, overlay = true)
{
	// console.log('{Indicator}: SHOW [%s]', loc);
	if (!$('#indicator').is(':visible')) {
		if (!$('#indicator').hasClass('progress')) {
			$('#indicator').addClass('progress');
		}
		setCenter('indicator', true, true);
		$('#indicator').show();
	}
	if (overlay === true) {
		overlayBoxShow();
	}
}

/**
 * explicit hide for action Indicator
 * instead of automatically show or hide, do on command hide
 * @param {String}  loc            location name for action indicator
 *                                 default empty. for console.log
 * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block
 * @deprecated use utils.js
 */
function actionIndicatorHide(loc, overlay = true)
{
	// console.log('{Indicator}: HIDE [%s]', loc);
	$('#indicator').hide();
	if (overlay === true) {
		overlayBoxHide();
	}
}

/**
 * shows the overlay box or if already visible, bumps the zIndex to 100
 * @deprecated use utils.js
 */
function overlayBoxShow()
{
	// check if overlay box exists and if yes set the z-index to 100
	if ($('#overlayBox').is(':visible')) {
		$('#overlayBox').css('zIndex', '100');
	} else {
		$('#overlayBox').show();
		$('#overlayBox').css('zIndex', '98');
	}
}

/**
 * hides the overlay box or if zIndex is 100 bumps it down to previous level
 * @deprecated use utils.js
 */
function overlayBoxHide()
{
	// if the overlay box z-index is 100, do no hide, but set to 98
	if ($('#overlayBox').css('zIndex') >= 100) {
		$('#overlayBox').css('zIndex', '98');
	} else {
		$('#overlayBox').hide();
	}
}

/**
 * position the overlay block box and shows it
 * @deprecated use utils.js
 */
function setOverlayBox() // eslint-disable-line no-unused-vars
{
	if (!$('#overlayBox').is(':visible')) {
		$('#overlayBox').show();
	}
}

/**
 * opposite of set, always hides overlay box
 * @deprecated use utils.js
 */
function hideOverlayBox() // eslint-disable-line no-unused-vars
{
	if ($('#overlayBox').is(':visible')) {
		$('#overlayBox').hide();
	}
}

/**
 * the abort call, clears the action box and hides it and the overlay box
 * @deprecated use utils.js
 */
function ClearCall() // eslint-disable-line no-unused-vars
{
	$('#actionBox').html('');
	$('#actionBox').hide();
	$('#overlayBox').hide();
}

/*************************************************************
 * NEW action indicator and overlay box calls
 * USE THIS
 * ***********************************************************/

/**
 * show action indicator
 * - checks if not existing and add
 * - only shows if not visible (else ignore)
 * - overlaybox check is called and shown on a fixzed
 *   zIndex of 1000
 * - indicator is page centered
 * @param {String} loc ID string, only used for console log
 * @deprecated use utils.js
 */
function showActionIndicator(loc) // eslint-disable-line no-unused-vars
{
	// console.log('{Indicator}: SHOW [%s]', loc);
	// check if indicator element exists
	if ($('#indicator').length == 0) {
		var el = document.createElement('div');
		el.className = 'progress hide';
		el.id = 'indicator';
		$('body').append(el);
	} else if (!$('#indicator').hasClass('progress')) {
		// if I add a class it will not be hidden anymore
		// hide it
		$('#indicator').addClass('progress').hide();
	}
	// indicator not visible
	if (!$('#indicator').is(':visible')) {
		// check if overlay box element exits
		checkOverlayExists();
		// if not visible show
		if (!$('#overlayBox').is(':visible')) {
			$('#overlayBox').show();
		}
		// always set to 1000 zIndex to be top
		$('#overlayBox').css('zIndex', 1000);
		// show indicator
		$('#indicator').show();
		// center it
		setCenter('indicator', true, true);
	}
}

/**
 * hide action indicator, if it is visiable
 * If the global variable GL_OB_S is > GL_OB_BASE then
 * the overlayBox is not hidden but the zIndex
 * is set to this value
 * @param {String} loc ID string, only used for console log
 * @deprecated use utils.js
 */
function hideActionIndicator(loc) // eslint-disable-line no-unused-vars
{
	// console.log('{Indicator}: HIDE [%s]', loc);
	// check if indicator is visible
	if ($('#indicator').is(':visible')) {
		// hide indicator
		$('#indicator').hide();
		// if global overlay box count is > 0
		// then set it to this level and keep
		if (GL_OB_S > GL_OB_BASE) {
			$('#overlayBox').css('zIndex', GL_OB_S);
		} else {
			// else hide overlay box and set zIndex to 0
			$('#overlayBox').hide();
			$('#overlayBox').css('zIndex', GL_OB_BASE);
		}
	}
}

/**
 * checks if overlayBox exists, if not it is
 * added as hidden item at the body end
 * @deprecated use utils.js
 */
function checkOverlayExists()
{
	// check if overlay box exists, if not create it
	if ($('#overlayBox').length == 0) {
		var el = document.createElement('div');
		el.className = 'overlayBoxElement hide';
		el.id = 'overlayBox';
		$('body').append(el);
	}
}

/**
 * show overlay box
 * if not visible show and set zIndex to 10 (GL_OB_BASE)
 * if visible, add +1 to the GL_OB_S variable and
 * up zIndex by this value
 * @deprecated use utils.js
 */
function showOverlayBoxLayers(el_id) // eslint-disable-line no-unused-vars
{
	// console.log('SHOW overlaybox: %s', GL_OB_S);
	// if overlay box is not visible show and set zIndex to 0
	if (!$('#overlayBox').is(':visible')) {
		$('#overlayBox').show();
		$('#overlayBox').css('zIndex', GL_OB_BASE);
		// also set start variable to 0
		GL_OB_S = GL_OB_BASE;
	}
	// up the overlay box counter by 1
	GL_OB_S ++;
	// set zIndex
	$('#overlayBox').css('zIndex', GL_OB_S);
	// if element given raise zIndex and show
	if (el_id) {
		if ($('#' + el_id).length > 0) {
			$('#' + el_id).css('zIndex', GL_OB_S + 1);
			$('#' + el_id).show();
		}
	}
	// console.log('SHOW overlaybox NEW zIndex: %s', $('#overlayBox').css('zIndex'));
}

/**
 * hide overlay box
 * lower GL_OB_S value by -1
 * if we are 10 (GL_OB_BASE) or below hide the overlayIndex
 * and set zIndex and GL_OB_S to 0
 * else just set zIndex to the new GL_OB_S value
 * @param {String} el_id Target to hide layer
 * @deprecated use utils.js
 */
function hideOverlayBoxLayers(el_id='')
{
	// console.log('HIDE overlaybox: %s', GL_OB_S);
	// remove on layer
	GL_OB_S --;
	// if 0 or lower (overflow) hide it and
	// set zIndex to 0
	if (GL_OB_S <= GL_OB_BASE) {
		GL_OB_S = GL_OB_BASE;
		$('#overlayBox').hide();
		$('#overlayBox').css('zIndex', GL_OB_BASE);
	} else {
		// if OB_S > 0 then set new zIndex
		$('#overlayBox').css('zIndex', GL_OB_S);
	}
	if (el_id) {
		$('#' + el_id).hide();
		$('#' + el_id).css('zIndex', 0);
	}
	// console.log('HIDE overlaybox NEW zIndex: %s', $('#overlayBox').css('zIndex'));
}

/**
 * only for single action box
 * @deprecated use utils.js
 */
function clearCallActionBox() // eslint-disable-line no-unused-vars
{
	$('#actionBox').html('');
	$('#actionBox').hide();
	hideOverlayBoxLayers();
}

// *** DOM MANAGEMENT FUNCTIONS
/**
 * reates object for DOM element creation flow
 * @param  {String} tag          must set tag (div, span, etc)
 * @param  {String} [id='']      optional set for id, if input, select will be used for name
 * @param  {String} [content=''] text content inside, is skipped if sub elements exist
 * @param  {Array}  [css=[]]     array for css tags
 * @param  {Object} [options={}] anything else (value, placeholder, OnClick, style)
 * @return {Object}              created element as an object
 * @deprecated use utils.js
 */
function cel(tag, id = '', content = '', css = [], options = {})
{
	return {
		tag: tag,
		id: id,
		name: options.name, // override name if set [name gets ignored in tree build anyway]
		content: content,
		css: css,
		options: options,
		sub: []
	};
}

/**
 * attach a cel created object to another to create a basic DOM tree
 * @param  {Object} base    object where to attach/search
 * @param  {Object} attach  the object to be attached
 * @param  {String} [id=''] optional id, if given search in base for this id and attach there
 * @return {Object}         "none", technically there is no return needed as it is global attach
 * @deprecated use utils.js
 */
function ael(base, attach, id = '')
{
	if (id) {
		// base id match already
		if (base.id == id) {
			// base.sub.push(Object.assign({}, attach));
			base.sub.push(deepCopyFunction(attach));
		} else {
			// sub check
			if (isObject(base.sub) && base.sub.length > 0) {
				for (var i = 0; i < base.sub.length; i ++) {
					// recursive call to sub element
					ael(base.sub[i], attach, id);
				}
			}
		}
	} else {
		// base.sub.push(Object.assign({}, attach));
		base.sub.push(deepCopyFunction(attach));
	}
	return base;
}

/**
 * directly attach n elements to one master base element
 * this type does not support attach with optional id
 * @param  {Object}    base   object to where we attach the elements
 * @param  {...Object} attach attach 1..n: attach directly to the base element those attachments
 * @return {Object}           "none", technically there is no return needed, global attach
 * @deprecated use utils.js
 */
function aelx(base, ...attach)
{
	for (var i = 0; i < attach.length; i ++) {
		// base.sub.push(Object.assign({}, attach[i]));
		base.sub.push(deepCopyFunction(attach[i]));
	}
	return base;
}

/**
 * same as aelx, but instead of using objects as parameters
 * get an array of objects to attach
 * @param  {Object} base   object to where we attach the elements
 * @param  {Array}  attach array of objects to attach
 * @return {Object}        "none", technically there is no return needed, global attach
 * @deprecated use utils.js
 */
function aelxar(base, attach) // eslint-disable-line no-unused-vars
{
	for (var i = 0; i < attach.length; i ++) {
		// base.sub.push(Object.assign({}, attach[i]));
		base.sub.push(deepCopyFunction(attach[i]));
	}
	return base;
}

/**
 * resets the sub elements of the base element given
 * @param  {Object} base cel created element
 * @return {Object}      returns reset base element
 * @deprecated use utils.js
 */
function rel(base) // eslint-disable-line no-unused-vars
{
	base.sub = [];
	return base;
}

/**
 * searches and removes style from css array
 * @param  {Object} _element element to work one
 * @param  {String} css      style sheet to remove (name)
 * @return {Object}          returns full element
 * @deprecated use utils.js
 */
function rcssel(_element, css)
{
	var css_index = _element.css.indexOf(css);
	if (css_index > -1) {
		_element.css.splice(css_index, 1);
	}
	return _element;
}

/**
 * adds a new style sheet to the element given
 * @param  {Object} _element element to work on
 * @param  {String} css      style sheet to add (name)
 * @return {Object}         returns full element
 * @deprecated use utils.js
 */
function acssel(_element, css)
{
	var css_index = _element.css.indexOf(css);
	if (css_index == -1) {
		_element.css.push(css);
	}
	return _element;
}

/**
 * removes one css and adds another
 * is a wrapper around rcssel/acssel
 * @param  {Object} _element element to work on
 * @param  {String} rcss     style to remove (name)
 * @param  {String} acss     style to add (name)
 * @return {Object}          returns full element
 * @deprecated use utils.js
 */
function scssel(_element, rcss, acss) // eslint-disable-line no-unused-vars
{
	rcssel(_element, rcss);
	acssel(_element, acss);
}

/**
 * parses the object tree created with cel/ael and converts it into an HTML string
 * that can be inserted into the page
 * @param  {Object} tree object tree with dom element declarations
 * @return {String}      HTML string that can be used as innerHTML
 * @deprecated use utils.js
 */
function phfo(tree)
{
	let name_elements = [
		'button',
		'fieldset',
		'form',
		'iframe',
		'input',
		'map',
		'meta',
		'object',
		'output',
		'param',
		'select',
		'textarea',
	];
	let skip_options = [
		'id',
		'name',
		'class',
	];
	let no_close = [
		'input',
		'br',
		'img',
		'hr',
		'area',
		'col',
		'keygen',
		'wbr',
		'track',
		'source',
		'param',
		'command',
		// only in header
		'base',
		'meta',
		'link',
		'embed',
	];
	// holds the elements
	var content = [];
	// main part line
	var line = '<' + tree.tag;
	var i;
	// first id, if set
	if (tree.id) {
		line += ' id="' + tree.id + '"';
		// if anything input (input, textarea, select then add name too)
		if (name_elements.includes(tree.tag)) {
			line += ' name="' + (tree.name ? tree.name : tree.id) + '"';
		}
	}
	// second CSS
	if (isObject(tree.css) && tree.css.length > 0) {
		line += ' class="';
		for (i = 0; i < tree.css.length; i ++) {
			line += tree.css[i] + ' ';
		}
		// strip last space
		line = line.slice(0, -1);
		line += '"';
	}
	// options is anything key = "data"
	if (isObject(tree.options)) {
		// ignores id, name, class as key
		for (const [key, item] of Object.entries(tree.options)) {
			if (!skip_options.includes(key)) {
				line += ' ' + key + '="' + item + '"';
			}
		}
	}
	// finish open tag
	line += '>';
	// push finished line
	content.push(line);
	// dive into sub tree to attach sub nodes
	// NOTES: we can have content (text) AND sub nodes at the same level
	// CONTENT (TEXT) takes preference over SUB NODE in order
	if (isObject(tree.sub) && tree.sub.length > 0) {
		if (tree.content) {
			content.push(tree.content);
		}
		for (i = 0; i < tree.sub.length; i ++) {
			content.push(phfo(tree.sub[i]));
		}
	} else if (tree.content) {
		content.push(tree.content);
	}
	// if not input, image or br, then close
	if (
		!no_close.includes(tree.tag)
	) {
		content.push('</' + tree.tag + '>');
	}
	// combine to string
	return content.join('');
}

/**
 * Create HTML elements from array list
 * as a flat element without master object file
 * Is like tree.sub call
 * @param  {Array}  list Array of cel created objects
 * @return {String}      HTML String
 * @deprecated use utils.js
 */
function phfa(list) // eslint-disable-line no-unused-vars
{
	var content = [];
	for (var i = 0; i < list.length; i ++) {
		content.push(phfo(list[i]));
	}
	return content.join('');
}
// *** DOM MANAGEMENT FUNCTIONS

// BLOCK: html wrappers for quickly creating html data blocks

/**
 * NOTE: OLD FORMAT which misses multiple block set
 * creates an select/options drop down block.
 * the array needs to be key -> value format.
 * key is for the option id and value is for the data output
 * @param  {String}  name                  name/id
 * @param  {Object}  data                  array for the options
 * @param  {String}  [selected='']         selected item uid
 * @param  {Boolean} [options_only=false]  if this is true, it will not print the select part
 * @param  {Boolean} [return_string=false] return as string and not as element
 * @param  {String}  [sort='']             if empty as is, else allowed 'keys',
 *                                         'values' all others are ignored
 * @return {String}                        html with build options block
 * @deprecated use utils.js
 */
function html_options(name, data, selected = '', options_only = false, return_string = false, sort = '') // eslint-disable-line no-unused-vars
{
	// wrapper to new call
	return html_options_block(
		name, data, selected, 0, options_only, return_string, sort
	);
}

/**
 * NOTE: USE THIS CALL, the above one is deprecated
 * creates an select/options drop down block.
 * the array needs to be key -> value format.
 * key is for the option id and value is for the data output
 * @param  {String}  name                  name/id
 * @param  {Object}  data                  array for the options
 * @param  {String}  [selected='']         selected item uid
 * @param  {Number}  [multiple=0]          if this is 1 or larger, the drop down
 *                                         will be turned into multiple select
 *                                         the number sets the size value unless it is 1,
 *                                         then it is default
 * @param  {Boolean} [options_only=false]  if this is true, it will not print the select part
 * @param  {Boolean} [return_string=false] return as string and not as element
 * @param  {String}  [sort='']             if empty as is, else allowed 'keys',
 *                                         'values' all others are ignored
 * @param  {String}  [onchange='']         onchange trigger call, default unset
 * @return {String}                        html with build options block
 * @deprecated use utils.js
 */
function html_options_block(
	name, data, selected = '', multiple = 0, options_only = false, return_string = false, sort = '', onchange = ''
) {
	var content = [];
	var element_select;
	var select_options = {};
	var element_option;
	var data_list = []; // for sorted output
	var value;
	var options = {};
	// var option;
	if (multiple > 0) {
		select_options.multiple = '';
		if (multiple > 1) {
			select_options.size = multiple;
		}
	}
	if (onchange) {
		select_options.OnChange = onchange;
	}
	// set outside select, gets stripped on return if options only is true
	element_select = cel('select', name, '', [], select_options);
	// console.log('Call for %s, options: %s', name, options_only);
	if (sort == 'keys') {
		data_list = Object.keys(data).sort();
	} else if (sort == 'values') {
		data_list = Object.keys(data).sort((a, b) => ('' + data[a]).localeCompare(data[b]));
	} else {
		data_list = Object.keys(data);
	}
	// console.log('ORDER: %s', data_list);
	// use the previously sorted list
	// for (const [key, value] of Object.entries(data)) {
	for (const key of data_list) {
		value = data[key];
		// console.log('create [%s] options: key: %s, value: %s', name, key, value);
		// basic options init
		options = {
			'label': value,
			'value': key,
			'selected': ''
		};
		// add selected if matching
		if (multiple == 0 && !Array.isArray(selected) && selected == key) {
			options.selected = '';
		}
		// for multiple, we match selected as array
		if (multiple == 1 && Array.isArray(selected) && selected.indexOf(key) != -1) {
			options.selected = '';
		}
		// create the element option
		element_option = cel('option', '', value, [], options);
		// attach it to the select element
		ael(element_select, element_option);
	}
	// if with select part, convert to text
	if (!options_only) {
		if (return_string) {
			content.push(phfo(element_select));
			return content.join('');
		} else {
			return element_select;
		}
	} else {
		// strip select part
		if (return_string) {
			for (var i = 0; i < element_select.sub.length; i ++) {
				content.push(phfo(element_select.sub[i]));
			}
			return content.join('');
		} else {
			return element_select.sub;
		}
	}
}

/**
 *  refills a select box with options and keeps the selected
 * @param {String} name      name/id
 * @param {Object} data      array of options
 * @param {String} [sort=''] if empty as is, else allowed 'keys', 'values'
 * @deprecated use utils.js
 *                            all others are ignored
 */
function html_options_refill(name, data, sort = '') // eslint-disable-line no-unused-vars
{
	var element_option;
	var option_selected;
	var data_list = []; // for sorted output
	var value;
	// skip if not exists
	if (document.getElementById(name)) {
		// console.log('Call for %s, options: %s', name, options_only);
		if (sort == 'keys') {
			data_list = Object.keys(data).sort();
		} else if (sort == 'values') {
			data_list = Object.keys(data).sort((a, b) => ('' + data[a]).localeCompare(data[b]));
		} else {
			data_list = Object.keys(data);
		}
		// first read in existing ones from the options and get the selected one
		[].forEach.call(document.querySelectorAll('#' + name + ' :checked'), function(elm) {
			option_selected = elm.value;
		});
		loadEl(name).innerHTML = '';
		for (const key of data_list) {
			value = data[key];
			// console.log('add [%s]  options: key: %s, value: %s', name, key, value);
			element_option = document.createElement('option');
			element_option.label = value;
			element_option.value = key;
			element_option.innerHTML = value;
			if (key == option_selected) {
				element_option.selected = true;
			}
			loadEl(name).appendChild(element_option);
		}
	}
}

/**
 * parses a query string from window.location.search.substring(1)
 * ALTERNATIVE CODE
 * var url = new URL(window.location.href);
 * param_uid = url.searchParams.get('uid');
 * @param  {String}        [query='']      the query string to parse
 *                                         if not set will auto fill
 * @param  {String}        [return_key=''] if set only returns this key entry
 *                                         or empty for none
 * @return {Object|String}                 parameter entry list
 * @deprecated use utils.js
 */
function parseQueryString(query = '', return_key = '') // eslint-disable-line no-unused-vars
{
	if (!query) {
		query = window.location.search.substring(1);
	}
	var vars = query.split('&');
	var query_string = {};
	for (var i = 0; i < vars.length; i++) {
		var pair = vars[i].split('=');
		var key = decodeURIComponent(pair[0]);
		var value = decodeURIComponent(pair[1]);
		// skip over run if there is nothing
		if (!key || value === 'undefined') {
			continue;
		}
		// If first entry with this name
		if (typeof query_string[key] === 'undefined') {
			query_string[key] = decodeURIComponent(value);
			// If second entry with this name
		} else if (typeof query_string[key] === 'string') {
			var arr = [query_string[key], decodeURIComponent(value)];
			query_string[key] = arr;
			// If third or later entry with this name
		} else {
			query_string[key].push(decodeURIComponent(value));
		}
	}
	if (return_key) {
		if (keyInObject(return_key, query_string)) {
			return query_string[return_key];
		} else {
			return '';
		}
	} else {
		return query_string;
	}
}

/**
 * searches query parameters for entry and returns data either as string or array
 * if no search is given the whole parameters are returned as an object
 * if a parameter is set several times it will be returned as an array
 * if search parameter set and nothing found and empty string is returned
 * if no parametes exist and no serach is set and empty object is returned
 * @param  {String}        [search='']    if set searches for this entry, if empty
 *                                        all parameters are returned
 * @param  {String}        [query='']     different query string to parse, if not
 *                                        set (default) the current window href is used
 * @param  {Boolean}       [single=false] if set to true then only the first found
 *                                        will be returned
 * @return {Object|Array|String}          if search is empty, object, if search is set
 *                                        and only one entry, then string, else array
 *                                        unless single is true
 * @deprecated use utils.js
 */
function getQueryStringParam(search = '', query = '', single = false) // eslint-disable-line no-unused-vars
{
	if (!query) {
		query = window.location.href;
	}
	const url = new URL(query);
	let param = null;
	if (search) {
		let _params = url.searchParams.getAll(search);
		if (_params.length == 1 || single === true) {
			param = _params[0];
		} else if (_params.length > 1) {
			param = _params;
		}
	} else {
		// will be object, so declare it one
		param = {};
		// loop over paramenters
		for (const [key] of url.searchParams.entries()) {
			// check if not yet set
			if (typeof param[key] === 'undefined') {
				// get the parameters multiple
				let _params = url.searchParams.getAll(key);
				// if 1 set as string, else attach array as is
				param[key] = _params.length < 2 || single === true ?
					_params[0] :
					_params;
			}
		}
	}
	return param;
}

// *** MASTER logout call
/**
 * submits basic data for form logout
 * @deprecated use utils.js
 */
function loginLogout() // eslint-disable-line no-unused-vars
{
	const form = document.createElement('form');
	form.method = 'post';
	const hiddenField = document.createElement('input');
	hiddenField.type = 'hidden';
	hiddenField.name = 'login_logout';
	hiddenField.value = 'Logout';
	form.appendChild(hiddenField);
	document.body.appendChild(form);
	form.submit();
}

/**
 * create login string and logout button elements
 * @param {String} login_string             the login string to show on the left
 * @param {String} [header_id='mainHeader'] the target for the main element block
 *                                          if not set mainHeader is assumed
 *                                          this is the target div for the "loginRow"
 * @deprecated use utils.js
 */
function createLoginRow(login_string, header_id = 'mainHeader') // eslint-disable-line no-unused-vars
{
	// if header does not exist, we do nothing
	if (exists(header_id)) {
		// that row must exist already, if not it must be the first in the "mainHeader"
		if (!exists('loginRow')) {
			$('#' + header_id).html(phfo(cel('div', 'loginRow', '', ['loginRow', 'flx-spbt'])));
		}
		// clear out just in case for first entry
		// fill with div name & login/logout button
		$('#loginRow').html(phfo(cel('div', 'loginRow-name', login_string)));
		$('#loginRow').append(phfo(cel('div', 'loginRow-info', '')));
		$('#loginRow').append(phfo(
			aelx(
				// outer div
				cel('div', 'loginRow-logout'),
				// inner element
				cel('input', 'logout', '', [], {
					value: __('Logout'),
					type: 'button',
					onClick: 'loginLogout()'
				})
			)
		));
	}
}

/**
 * create the top nav menu that switches physical between pages
 * (edit access data based)
 * @param {Object} nav_menu                 the built nav menu with highlight info
 * @param {String} [header_id='mainHeader'] the target for the main element block
 *                                          if not set mainHeader is assumed
 *                                          this is the target div for the "menuRow"
 * @deprecated use utils.js
 */
function createNavMenu(nav_menu, header_id = 'mainHeader') // eslint-disable-line no-unused-vars
{
	// must be an object
	if (isObject(nav_menu) && getObjectCount(nav_menu) > 1) {
		// do we have more than one entry, if not, do not show (single page)
		if (!exists('menuRow')) {
			$('#' + header_id).html(phfo(cel('div', 'menuRow', '', ['menuRow', 'flx-s'])));
		}
		var content = [];
		$.each(nav_menu, function(key, item) {
			// key is number
			// item is object with entries
			if (key != 0) {
				content.push(phfo(cel('div', '', '&middot;', ['pd-2'])));
			}
			// ignore item.popup for now
			if (item.enabled) {
				// set selected based on window.location.href as the php set will not work
				if (window.location.href.indexOf(item.url) != -1) {
					item.selected = 1;
				}
				// create the entry
				content.push(phfo(
					aelx(
						cel('div'),
						cel('a', '', item.name, ['pd-2'].concat(item.selected ? 'highlight': ''), {
							href: item.url
						})
					)
				));
			}
		});
		$('#menuRow').html(content.join(''));
	} else {
		$('#menuRow').hide();
	}
}

/* END */
