From c22e68f19a63ea1e7ab547ca9c1339c8f9fe3872 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 20 May 2025 12:13:04 +0900 Subject: [PATCH 01/20] Phive update --- .phive/phars.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.phive/phars.xml b/.phive/phars.xml index 57263c64..3c6bd04e 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,9 +1,9 @@ - - - - + + + + From ffff65a76dbab14db8741506af88043683e1d59d Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 29 May 2025 11:23:16 +0900 Subject: [PATCH 02/20] Core JavaSCript libs update --- www/admin/layout/javascript/utils.js | 66 +++++++++++++++++--- www/admin/layout/javascript/utils.min.js | 4 +- www/admin/layout/javascript/utils.min.js.map | 6 +- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/www/admin/layout/javascript/utils.js b/www/admin/layout/javascript/utils.js index 52124baa..c6c46618 100644 --- a/www/admin/layout/javascript/utils.js +++ b/www/admin/layout/javascript/utils.js @@ -34,6 +34,17 @@ function executeFunctionByName(functionName, context) { } return context[func].apply(context, args); } +function runFunction(name) { + var args = Array.prototype.slice.call(arguments, 1); + runFunctionArgsArray(name, args); +} +function runFunctionArgsArray(name, args) { + var fn = window[name]; + if (typeof fn !== "function") { + return; + } + fn.apply(window, args); +} function isObject(val) { if (val === null) { return false; @@ -659,6 +670,31 @@ function getQueryStringParam(search = "", query = "", single = false) { } return param; } +function hasUrlParameter(key) { + var urlParams = new URLSearchParams(window.location.search); + return urlParams.has(key); +} +function getUrlParameter(key) { + var urlParams = new URLSearchParams(window.location.search); + return urlParams.get(key); +} +function updateUrlParameter(key, value, reload = false) { + const url = new URL(window.location.href); + url.searchParams.set(key, value); + const newUrl = url.toString(); + window.history.pushState({ path: newUrl }, "", newUrl); + if (reload) { + window.location.reload(); + } +} +function removeUrlParameter(key, reload = false) { + const url = new URL(window.location.href); + url.searchParams.delete(key); + window.history.pushState({}, "", url.toString()); + if (reload) { + window.location.reload(); + } +} // src/utils/LoginLogout.mjs function loginLogout() { @@ -979,7 +1015,7 @@ var ActionBox = class { $("#overlayBox").css("zIndex", this.zIndex.base); } $("#overlayBox").show(); - if (!keyInObject(target_id, this.zIndex.boxes)) { + if (!objectKeyExists(this.zIndex.boxes, target_id)) { this.zIndex.boxes[target_id] = this.zIndex.max; this.zIndex.max += 10; } else if (this.zIndex.boxes[target_id] + 10 < this.zIndex.max) { @@ -1005,7 +1041,7 @@ var ActionBox = class { if (!exists(target_id)) { return; } - if (keyInObject(target_id, this.action_box_storage) && clean === true) { + if (objectKeyExists(this.action_box_storage, target_id) && clean === true) { this.action_box_storage[target_id] = {}; } if (clean === true) { @@ -1043,15 +1079,15 @@ var ActionBox = class { * @param {Object} [settings={}] Optional settings, eg style sheets */ createActionBox(target_id = "actionBox", title = "", contents = {}, headers = {}, settings = {}, show_close = true) { - if (!keyInObject(target_id, this.action_box_storage)) { + if (!objectKeyExists(this.action_box_storage, target_id)) { this.action_box_storage[target_id] = {}; } let header_css = []; - if (keyInObject("header_css", settings)) { + if (objectKeyExists(settings, "header_css")) { header_css = settings.header_css; } let action_box_css = []; - if (keyInObject("action_box_css", settings)) { + if (objectKeyExists(settings, "action_box_css")) { action_box_css = settings.action_box_css; } let elements = []; @@ -1082,14 +1118,14 @@ var ActionBox = class { ) )); if (getObjectCount(headers) > 0) { - if (keyInObject("raw_string", headers)) { + if (objectKeyExists(headers, "raw_string")) { elements.push(headers.raw_string); } else { elements.push(this.hec.phfo(headers)); } } if (getObjectCount(contents) > 0) { - if (keyInObject("raw_string", contents)) { + if (objectKeyExists(contents, "raw_string")) { elements.push(contents.raw_string); } else { elements.push(this.hec.phfo(contents)); @@ -1517,12 +1553,24 @@ function html_options_block2(name, data, selected = "", multiple = 0, options_on function html_options_refill2(name, data, sort = "") { html_options_refill(name, data, sort); } -function parseQueryString2(query = "", return_key = "") { - return parseQueryString(query, return_key); +function parseQueryString2(query = "", return_key = "", single = false) { + return parseQueryString(query, return_key, single); } function getQueryStringParam2(search = "", query = "", single = false) { return getQueryStringParam(search, query, single); } +function updateUrlParameter2(key, value, reload = false) { + return updateUrlParameter(key, value, reload); +} +function removeUrlParameter2(key, reload = false) { + return removeUrlParameter(key, reload); +} +function hasUrlParameter2(key) { + return hasUrlParameter(key); +} +function getUrlParameter2(key) { + return getUrlParameter(key); +} function loginLogout2() { loginLogout(); } diff --git a/www/admin/layout/javascript/utils.min.js b/www/admin/layout/javascript/utils.min.js index 782dc1ce..062537e4 100644 --- a/www/admin/layout/javascript/utils.min.js +++ b/www/admin/layout/javascript/utils.min.js @@ -1,3 +1,3 @@ -function errorCatch(err){err.stack?err.lineNumber?console.error("ERROR[%s:%s] ",err.name,err.lineNumber,err):err.line?console.error("ERROR[%s:%s] ",err.name,err.line,err):console.error("ERROR[%s] ",err.name,err):err.number?(console.error("ERROR[%s:%s] %s",err.name,err.number,err.message),console.error("ERROR[description] %s",err.description)):console.error("ERROR[%s] %s",err.name,err.message)}function isFunction(name){return typeof window[name]<"u"&&typeof window[name]=="function"}function executeFunctionByName(functionName,context){var args=Array.prototype.slice.call(arguments,2),namespaces=functionName.split("."),func=namespaces.pop();if(func==null)throw new Error("Cannot get function from namespaces: "+functionName);for(var i=0;iobject[key]===value)??""}function valueInObject(object,value){return objectValueExists(object,value)}function objectValueExists(object,value){return!!Object.keys(object).find(key=>object[key]===value)}function deepCopyFunction(inObject){var outObject,value,key;if(typeof inObject!="object"||inObject===null)return inObject;outObject=Array.isArray(inObject)?[]:{};for(key in inObject)value=inObject[key],outObject[key]=deepCopyFunction(value);return outObject}function loadEl(el_id){let el=document.getElementById(el_id);if(el===null)throw new Error("Cannot find: "+el_id);return el}function pop(theURL,winName,features){let __winName=window.open(theURL,winName,features);__winName?.focus()}function expandTA(ta_id){let ta=this.loadEl(ta_id);if(ta instanceof HTMLElement&&ta.getAttribute("type")!=="textarea")throw new Error("Element is not a textarea: "+ta_id);let maxChars=parseInt(ta.getAttribute("cols")??"0"),ta_value=ta.getAttribute("value"),theRows=[];ta_value!=null&&(theRows=ta_value.split(` -`));for(var numNewRows=0,i=0;imaxChars&&(numNewRows+=Math.ceil((theRows[i].length+2)/maxChars));ta.setAttribute("row",(numNewRows+theRows.length).toString())}function exists(id){return $("#"+id).length>0}var HtmlElementCreator=class{cel(tag,id="",content="",css=[],options={}){return{tag,id,name:options.name,content,css,options,sub:[]}}ael(base,attach,id=""){if(id){if(base.id==id)base.sub.push(deepCopyFunction(attach));else if(isObject(base.sub)&&base.sub.length>0)for(var i=0;i-1&&_element.css.splice(css_index,1),_element}acssel(_element,css){var css_index=_element.css.indexOf(css);return css_index==-1&&_element.css.push(css),_element}scssel(_element,rcss,acss){return this.rcssel(_element,rcss),this.acssel(_element,acss),_element}phfo(tree){let name_elements=["button","fieldset","form","iframe","input","map","meta","object","output","param","select","textarea"],skip_options=["id","name","class"],no_close=["input","br","img","hr","area","col","keygen","wbr","track","source","param","command","base","meta","link","embed"];var content=[],line="<"+tree.tag,i;if(tree.id&&(line+=' id="'+tree.id+'"',name_elements.includes(tree.tag)&&(line+=' name="'+(tree.name?tree.name:tree.id)+'"')),isObject(tree.css)&&tree.css.length>0){for(line+=' class="',i=0;i0)for(tree.content&&content.push(tree.content),i=0;i"),content.join("")}phfa(list){for(var content=[],i=0;i"'/]/g,function(s){var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};return entityMap[s]})}function unescapeHtml(string){return string.replace(/&[#\w]+;/g,function(s){var entityMap={"&":"&","<":"<",">":">",""":'"',"'":"'","/":"/"};return entityMap[s]})}function html_options(name,data,selected="",options_only=!1,return_string=!1,sort=""){return this.html_options_block(name,data,selected,0,options_only,return_string,sort)}function html_options_block(name,data,selected="",multiple=0,options_only=!1,return_string=!1,sort="",onchange=""){var content=[],element_select,select_options={},element_option,data_list=[],value,options={};multiple>0&&(select_options.multiple="",multiple>1&&(select_options.size=multiple)),onchange&&(select_options.OnChange=onchange),element_select=dom.cel("select",name,"",[],select_options),sort=="keys"?data_list=Object.keys(data).sort():sort=="values"?data_list=Object.keys(data).sort((a,b)=>(""+data[a]).localeCompare(data[b])):data_list=Object.keys(data);for(let key of data_list)value=data[key],options={label:value,value:key,selected:""},multiple==0&&!Array.isArray(selected)&&selected==key&&(options.selected=""),multiple==1&&Array.isArray(selected)&&selected.indexOf(key)!=-1&&(options.selected=""),element_option=dom.cel("option","",value,[],options),dom.ael(element_select,element_option);if(options_only)if(return_string){for(var i=0;i(""+data[a]).localeCompare(data[b])):data_list=Object.keys(data),[].forEach.call(document.querySelectorAll("#"+name+" :checked"),function(elm){option_selected=elm.value}),loadEl(name).innerHTML="";for(let key of data_list)value=data[key],element_option=document.createElement("option"),element_option.label=value,element_option.value=key,element_option.innerHTML=value,key==option_selected&&(element_option.selected=!0),loadEl(name).appendChild(element_option)}}function dec2hex(dec){return("0x"+dec.toString(16)).substring(-2)}function getRandomIntInclusive(min,max){return min=Math.ceil(min),max=Math.floor(max),Math.floor(Math.random()*(max-min+1)+min)}function roundPrecision(number,precision){return isNaN(number)||isNaN(precision)?number:Math.round(number*Math.pow(10,precision))/Math.pow(10,precision)}function formatString(string,...args){return string.replace(/{(\d+)}/g,function(match,number){return typeof args[number]<"u"?args[number]:match})}function numberWithCommas(number){var parts=number.toString().split(".");return parts[0]=parts[0].replace(/\B(?=(\d{3})+(?!\d))/g,","),parts.join(".")}function convertLBtoBR(string){return string.replace(/(?:\r\n|\r|\n)/g,"
")}function getTimestamp(){var date=new Date;return date.getTime()}function generateId(len){var arr=new Uint8Array((len||40)/2);return(window.crypto||window.msCrypto).getRandomValues(arr),Array.from(arr,self.dec2hex).join("")}function randomIdF(){return Math.random().toString(36).substring(2)}function getWindowSize(){var width,height;return width=window.innerWidth||window.document.documentElement.clientWidth||window.document.body.clientWidth,height=window.innerHeight||window.document.documentElement.clientHeight||window.document.body.clientHeight,{width,height}}function getScrollOffset(){var left,top;return left=window.pageXOffset||window.document.documentElement.scrollLeft||window.document.body.scrollLeft,top=window.pageYOffset||window.document.documentElement.scrollTop||window.document.body.scrollTop,{left,top}}function getScrollOffsetOpener(){var left,top;return left=opener.window.pageXOffset||opener.document.documentElement.scrollLeft||opener.document.body.scrollLeft,top=opener.window.pageYOffset||opener.document.documentElement.scrollTop||opener.document.body.scrollTop,{left,top}}function setCenter(id,left,top){var dimensions={height:$("#"+id).height()??0,width:$("#"+id).width()??0},type=$("#"+id).css("position"),viewport=this.getWindowSize(),offset=this.getScrollOffset();if(left&&$("#"+id).css({left:viewport.width/2-dimensions.width/2+offset.left+"px"}),top){var top_pos=type=="fixed"?viewport.height/2-dimensions.height/2:viewport.height/2-dimensions.height/2+offset.top;$("#"+id).css({top:top_pos+"px"})}}function goToPos(element,offset=0,duration=500,base="body,html"){try{let element_offset=$("#"+element).offset();if(element_offset==null)return;$("#"+element).length&&$(base).animate({scrollTop:element_offset.top-offset},duration)}catch(err){errorCatch(err)}}function goTo(target){loadEl(target).scrollIntoView({behavior:"smooth"})}function formatBytes(bytes){var i=-1;if(typeof bytes=="bigint"&&(bytes=Number(bytes)),isNaN(bytes))return bytes.toString();do bytes=bytes/1024,i++;while(bytes>99);return Math.round(bytes*Math.pow(10,2))/Math.pow(10,2)+["kB","MB","GB","TB","PB","EB"][i]}function formatBytesLong(bytes){if(typeof bytes=="bigint"&&(bytes=Number(bytes)),isNaN(bytes))return bytes.toString();let negative=!1;bytes<0&&(negative=!0,bytes*=-1);var i=Math.floor(Math.log(bytes)/Math.log(1024)),sizes=["B","KB","MB","GB","TB","PB","EB","ZB","YB"];return(negative?"-":"")+((bytes/Math.pow(1024,i)).toFixed(2)+" "+sizes[i]).toString()}function stringByteFormat(bytes,raw=!1){if(!(typeof bytes=="string"||bytes instanceof String))return bytes.toString();let valid_units="bkmgtpezy",regex=/([\d.,]*)\s?(eb|pb|tb|gb|mb|kb|e|p|t|g|m|k|b)$/i,matches=bytes.match(regex);if(matches!==null){let m1=parseFloat(matches[1].replace(/[^0-9.]/,"")),m2=matches[2].replace(/[^bkmgtpezy]/i,"").charAt(0).toLowerCase();m2&&(bytes=m1*Math.pow(1024,valid_units.indexOf(m2)))}return raw?bytes:Math.round(bytes)}function parseQueryString(query="",return_key="",single=!1){return getQueryStringParam(return_key,query,single)}function getQueryStringParam(search="",query="",single=!1){query||(query=window.location.href);let url=new URL(query),param=null;if(search){let _params=url.searchParams.getAll(search);_params.length==1||single===!0?param=_params[0]:_params.length>1&&(param=_params)}else{param={};for(let[key]of url.searchParams.entries())if(typeof param[key]>"u"){let _params=url.searchParams.getAll(key);param[key]=_params.length<2||single===!0?_params[0]:_params}}return param}function loginLogout(){let form=document.createElement("form");form.method="post";let hiddenField=document.createElement("input");hiddenField.type="hidden",hiddenField.name="login_logout",hiddenField.value="Logout",form.appendChild(hiddenField),document.body.appendChild(form),form.submit()}function actionIndicator(loc,overlay=!0){$("#indicator").is(":visible")?this.actionIndicatorHide(loc,overlay):this.actionIndicatorShow(loc,overlay)}function actionIndicatorShow(loc,overlay=!0){$("#indicator").is(":visible")||($("#indicator").hasClass("progress")||$("#indicator").addClass("progress"),setCenter("indicator",!0,!0),$("#indicator").show()),overlay===!0&&this.overlayBoxShow()}function actionIndicatorHide(loc,overlay=!0){$("#indicator").hide(),overlay===!0&&overlayBoxHide()}function overlayBoxShow(){$("#overlayBox").is(":visible")?$("#overlayBox").css("zIndex","100"):($("#overlayBox").show(),$("#overlayBox").css("zIndex","98"))}function overlayBoxHide(){parseInt($("#overlayBox").css("zIndex"))>=100?$("#overlayBox").css("zIndex","98"):$("#overlayBox").hide()}function setOverlayBox(){$("#overlayBox").is(":visible")||$("#overlayBox").show()}function hideOverlayBox(){$("#overlayBox").is(":visible")&&$("#overlayBox").hide()}function ClearCall(){$("#actionBox").html(""),$("#actionBox").hide(),$("#overlayBox").hide()}var ActionIndicatorOverlayBox=class{#GL_OB_S=100;#GL_OB_BASE=100;showActionIndicator(loc){if($("#indicator").length==0){var el=document.createElement("div");el.className="progress hide",el.id="indicator",$("body").append(el)}else $("#indicator").hasClass("progress")||$("#indicator").addClass("progress").hide();$("#indicator").is(":visible")||(this.checkOverlayExists(),$("#overlayBox").is(":visible")||$("#overlayBox").show(),$("#overlayBox").css("zIndex",1e3),$("#indicator").show(),setCenter("indicator",!0,!0))}hideActionIndicator(loc){$("#indicator").is(":visible")&&($("#indicator").hide(),this.#GL_OB_S>this.#GL_OB_BASE?$("#overlayBox").css("zIndex",this.#GL_OB_S):($("#overlayBox").hide(),$("#overlayBox").css("zIndex",this.#GL_OB_BASE)))}checkOverlayExists(){if($("#overlayBox").length==0){var el=document.createElement("div");el.className="overlayBoxElement hide",el.id="overlayBox",$("body").append(el)}}showOverlayBoxLayers(el_id){$("#overlayBox").is(":visible")||($("#overlayBox").show(),$("#overlayBox").css("zIndex",this.#GL_OB_BASE),this.#GL_OB_S=this.#GL_OB_BASE),this.#GL_OB_S++,$("#overlayBox").css("zIndex",this.#GL_OB_S),el_id&&$("#"+el_id).length>0&&($("#"+el_id).css("zIndex",this.#GL_OB_S+1),$("#"+el_id).show())}hideOverlayBoxLayers(el_id=""){this.#GL_OB_S--,this.#GL_OB_S<=this.#GL_OB_BASE?(this.#GL_OB_S=this.#GL_OB_BASE,$("#overlayBox").hide(),$("#overlayBox").css("zIndex",this.#GL_OB_BASE)):$("#overlayBox").css("zIndex",this.#GL_OB_S),el_id&&($("#"+el_id).hide(),$("#"+el_id).css("zIndex",0))}clearCallActionBox(){$("#actionBox").html(""),$("#actionBox").hide(),this.hideOverlayBoxLayers()}};var l10nTranslation=class{#i18n={};constructor(i18n2){this.#i18n=i18n2}__(string){return typeof this.#i18n<"u"&&isObject(this.#i18n)&&this.#i18n[string]?this.#i18n[string]:string}};var ActionBox=class{zIndex={base:100,max:110,indicator:0,boxes:{},active:[],top:""};action_box_storage={};action_box_cache_timeout=10*60*1e3;hec;l10n;constructor(hec2,l10n2){this.hec=hec2,this.l10n=l10n2}showFillActionBox(target_id="actionBox",content="",action_box_css=[],override=0,content_override=0){this.fillActionBox(target_id,content,action_box_css),this.showActionBox(target_id,override,content_override)}fillActionBox(target_id="actionBox",content="",action_box_css=[]){exists(target_id)||$("#mainContainer").after(this.hec.phfo(this.hec.cel("div",target_id,"",["actionBoxElement","hide"].concat(action_box_css)))),$("#"+target_id).html(content)}adjustActionBox(target_id="actionBox",override=0,content_override=0){this.adjustActionBoxHeight(target_id,override,content_override),setCenter(target_id,!0,!0)}hideAllActionBoxes(){$('#actionBox, div[id^="actionBox-"].actionBoxElement').hide(),$("#overlayBox").hide()}hideActionBox(target_id="actionBox"){this.closeActionBoxFloat(target_id,!1)}showActionBox(target_id="actionBox",override=0,content_override=0,hide_all=!0){this.showActionBoxFloat(target_id,override,content_override,hide_all)}closeActionBox(target_id="actionBox",clean=!0){this.closeActionBoxFloat(target_id,clean)}showActionBoxFloat(target_id="actionBox",override=0,content_override=0,hide_all=!1){hide_all===!0&&this.hideAllActionBoxes(),exists("overlayBox")||($("body").prepend(this.hec.phfo(this.hec.cel("div","overlayBox","",["overlayBoxElement"]))),$("#overlayBox").css("zIndex",this.zIndex.base)),$("#overlayBox").show(),keyInObject(target_id,this.zIndex.boxes)?this.zIndex.boxes[target_id]+10({id:el.id,zIndex:$("#"+el.id).css("zIndex")})).get();if(visible_zIndexes.length>0){let max_zIndex=0,max_el_id="";for(let zIndex_el of visible_zIndexes)parseInt(zIndex_el.zIndex)>max_zIndex&&(max_zIndex=parseInt(zIndex_el.zIndex),max_el_id=zIndex_el.id);$("#overlayBox").css("zIndex",max_zIndex-1),this.zIndex.top=max_el_id}else $("#overlayBox").hide()}createActionBox(target_id="actionBox",title="",contents={},headers={},settings={},show_close=!0){keyInObject(target_id,this.action_box_storage)||(this.action_box_storage[target_id]={});let header_css=[];keyInObject("header_css",settings)&&(header_css=settings.header_css);let action_box_css=[];keyInObject("action_box_css",settings)&&(action_box_css=settings.action_box_css);let elements=[];elements.push(this.hec.phfo(this.hec.aelx(this.hec.cel("div",target_id+"_title","",["actionBoxTitle","flx-spbt"].concat(header_css)),...show_close===!0?[this.hec.cel("div","",title,["fs-b","w-80"]),this.hec.aelx(this.hec.cel("div",target_id+"_title_close_button","",["w-20","tar"]),this.hec.cel("input",target_id+"_title_close","",["button-close","fs-s"],{type:"button",value:this.l10n.__("Close"),OnClick:"closeActionBox('"+target_id+"', false);"}))]:[this.hec.cel("div","",title,["fs-b","w-100"])]))),getObjectCount(headers)>0&&(keyInObject("raw_string",headers)?elements.push(headers.raw_string):elements.push(this.hec.phfo(headers))),getObjectCount(contents)>0?keyInObject("raw_string",contents)?elements.push(contents.raw_string):elements.push(this.hec.phfo(contents)):elements.push(this.hec.phfo(this.hec.cel("div",target_id+"_content","",[]))),elements.push(this.hec.phfo(this.hec.aelx(this.hec.cel("div",target_id+"_footer","",["pd-5","flx-spbt"]),...show_close===!0?[this.hec.cel("div","","",["fs-b","w-80"]),this.hec.aelx(this.hec.cel("div",target_id+"_footer_close_button","",["tar","w-20"]),this.hec.cel("input",target_id+"_footer_close","",["button-close","fs-s"],{type:"button",value:this.l10n.__("Close"),OnClick:"closeActionBox('"+target_id+"', false);"}))]:[this.hec.cel("div","","",["fs-b","w-100"])]))),elements.push(this.hec.phfo(this.hec.cel("input",target_id+"-cache_time","",[],{type:"hidden",value:Date.now()}))),this.fillActionBox(target_id,elements.join(""),action_box_css)}adjustActionBoxHeight(target_id="actionBox",override=0,content_override=0){var new_height=0,dim={},abc_dim={},content_id="";switch(isNaN(override)&&(override=0),isNaN(content_override)&&(content_override=0),target_id){case"actionBox":content_id="action_box";break;case"actionBoxSub":content_id="action_box_sub";break;default:content_id=target_id;break}$.each([target_id,content_id+"_content"],function(i,v){$("#"+v).css({height:"",width:""})}),exists(content_id+"_title")&&(dim.height=$("#"+content_id+"_title").outerHeight(),console.log("Target: %s, Action box Title: %s",target_id,dim.height),new_height+=dim.height??0),exists(content_id+"_header")&&(dim.height=$("#"+content_id+"_header").outerHeight(),console.log("Target: %s, Action box Header: %s",target_id,dim.height),new_height+=dim.height??0),exists(content_id+"_content")&&(content_override>0?(console.log("Target: %s, Action box Content Override: %s",target_id,content_override),new_height+=content_override):(abc_dim.height=$("#"+content_id+"_content").outerHeight(),console.log("Target: %s, Action box Content: %s",target_id,abc_dim.height),new_height+=abc_dim.height??0)),exists(content_id+"_footer")&&(dim.height=$("#"+content_id+"_footer").outerHeight(),console.log("Target: %s, Action box Footer: %s",target_id,dim.height),new_height+=dim.height??0),new_height+=override;var viewport=getWindowSize();if(new_height>=viewport.height){exists(content_id+"_content")&&($("#"+content_id+"_content").hasClass("of-s-y")||$("#"+content_id+"_content").addClass("of-s-y")),console.log("Target: %s, Viewport: %s, ActionBox (NH): %s, ABcontent: %s, ABouter: %s",target_id,viewport.height,new_height,abc_dim.height,$("#"+target_id).outerHeight());var m_height=viewport.height-(new_height-(abc_dim.height??0));console.log("Target: %s, New ABcontent: %s",target_id,m_height),$("#"+content_id+"_content").css("height",m_height+"px"),new_height=new_height-(abc_dim.height??0)+m_height,console.log("Target: %s, New Hight: %s",target_id,new_height)}else exists(content_id+"_content")&&$("#"+content_id+"_content").hasClass("of-s-y")&&$("#"+content_id+"_content").removeClass("of-s-y");console.log("Target: %s, Action Box new height: %s px (override %s px, content override %s px), window height: %s px, Visible Height: %s px",target_id,new_height,override,content_override,viewport.height,$("#"+content_id).outerHeight()),$("#"+target_id).css("height",new_height+"px")}};var LoginNavMenu=class{hec;l10n;constructor(hec2,l10n2){this.hec=hec2,this.l10n=l10n2}createLoginRow(login_string,header_id="mainHeader"){exists(header_id)&&(exists("loginRow")||$("#"+header_id).html(this.hec.phfo(this.hec.cel("div","loginRow","",["loginRow","flx-spbt"]))),$("#loginRow").html(this.hec.phfo(this.hec.cel("div","loginRow-name",login_string))),$("#loginRow").append(this.hec.phfo(this.hec.cel("div","loginRow-info",""))),$("#loginRow").append(this.hec.phfo(this.hec.aelx(this.hec.cel("div","loginRow-logout"),this.hec.cel("input","logout","",[],{value:this.l10n.__("Logout"),type:"button",onClick:"loginLogout()"})))))}createNavMenu(nav_menu,header_id="mainHeader"){if(isObject(nav_menu)&&getObjectCount(nav_menu)>1){exists("menuRow")||$("#"+header_id).html(this.hec.phfo(this.hec.cel("div","menuRow","",["menuRow","flx-s"])));var content=[];$.each(nav_menu,function(key,item){key!=0&&content.push(this.hec.phfo(this.hec.cel("div","","·",["pd-2"]))),item.enabled&&(window.location.href.indexOf(item.url)!=-1&&(item.selected=1),content.push(this.hec.phfo(this.hec.aelx(this.hec.cel("div"),this.hec.cel("a","",item.name,["pd-2"].concat(item.selected?"highlight":""),{href:item.url})))))}),$("#menuRow").html(content.join(""))}else $("#menuRow").hide()}};var aiob=new ActionIndicatorOverlayBox,hec=new HtmlElementCreator,l10n=new l10nTranslation(typeof i18n>"u"?{}:i18n),ab=new ActionBox(hec,l10n),lnm=new LoginNavMenu(hec,l10n);String.prototype.format||(String.prototype.format=function(){return console.error("[DEPRECATED] use StringHelpers.formatString"),formatString(this,arguments)}),Number.prototype.round&&(Number.prototype.round=function(prec){return console.error("[DEPRECATED] use MathHelpers.roundPrecision"),roundPrecision(this,prec)}),String.prototype.escapeHTML||(String.prototype.escapeHTML=function(){return console.error("[DEPRECATED] use HtmlHelpers.escapeHtml"),escapeHtml(this)}),String.prototype.unescapeHTML||(String.prototype.unescapeHTML=function(){return console.error("[DEPRECATED] use HtmlHelpers.unescapeHtml"),unescapeHtml(this)});function escapeHtml2(string){return escapeHtml(string)}function roundPrecision2(number,prec){return roundPrecision(number,prec)}function formatString2(string,...args){return formatString(string,...args)}function unescapeHtml2(string){return unescapeHtml(string)}function loadEl2(el_id){return loadEl(el_id)}function pop2(theURL,winName,features){pop(theURL,winName,features)}function expandTA2(ta_id){expandTA(ta_id)}function getWindowSize2(){return getWindowSize()}function getScrollOffset2(){return getScrollOffset()}function getScrollOffsetOpener2(){return getScrollOffsetOpener()}function setCenter2(id,left,top){setCenter(id,left,top)}function goToPos2(element,offset=0,duration=500,base="body,html"){goToPos(element,offset,duration,base)}function goTo2(target){goTo(target)}function __(string){return l10n.__(string)}function numberWithCommas2(x){return numberWithCommas(x)}function convertLBtoBR2(string){return convertLBtoBR(string)}function getTimestamp2(){return getTimestamp()}function dec2hex2(dec){return dec2hex(dec)}function generateId2(len){return generateId(len)}function randomIdF2(){return randomIdF()}function getRandomIntInclusive2(min,max){return getRandomIntInclusive(min,max)}function isFunction2(name){return isFunction(name)}function executeFunctionByName2(functionName,context){return executeFunctionByName(functionName,context)}function isObject2(val){return isObject(val)}function getObjectCount2(object){return getObjectCount(object)}function keyInObject2(key,object){return keyInObject(key,object)}function getKeyByValue2(object,value){return getKeyByValue(object,value)}function valueInObject2(object,value){return valueInObject(object,value)}function deepCopyFunction2(inObject){return deepCopyFunction(inObject)}function exists2(id){return exists(id)}function formatBytes2(bytes){return formatBytes(bytes)}function formatBytesLong2(bytes){return formatBytesLong(bytes)}function stringByteFormat2(bytes){return stringByteFormat(bytes)}function errorCatch2(err){errorCatch(err)}function actionIndicator2(loc,overlay=!0){actionIndicator(loc,overlay)}function actionIndicatorShow2(loc,overlay=!0){actionIndicatorShow(loc,overlay)}function actionIndicatorHide2(loc,overlay=!0){actionIndicatorHide(loc,overlay)}function overlayBoxShow2(){overlayBoxShow()}function overlayBoxHide2(){overlayBoxHide()}function setOverlayBox2(){setOverlayBox()}function hideOverlayBox2(){hideOverlayBox()}function ClearCall2(){ClearCall()}function showActionIndicator(loc){aiob.showActionIndicator(loc)}function hideActionIndicator(loc){aiob.hideActionIndicator(loc)}function checkOverlayExists(){aiob.checkOverlayExists()}function showOverlayBoxLayers(el_id){aiob.showOverlayBoxLayers(el_id)}function hideOverlayBoxLayers(el_id=""){aiob.hideOverlayBoxLayers(el_id)}function clearCallActionBox(){aiob.clearCallActionBox()}function cel(tag,id="",content="",css=[],options={}){return hec.cel(tag,id,content,css,options)}function ael(base,attach,id=""){return hec.ael(base,attach,id)}function aelx(base,...attach){return hec.aelx(base,...attach)}function aelxar(base,attach){return hec.aelxar(base,attach)}function rel(base){return hec.rel(base)}function rcssel(_element,css){return hec.rcssel(_element,css)}function acssel(_element,css){return hec.acssel(_element,css)}function scssel(_element,rcss,acss){hec.scssel(_element,rcss,acss)}function phfo(tree){return hec.phfo(tree)}function phfa(list){return hec.phfa(list)}function html_options2(name,data,selected="",options_only=!1,return_string=!1,sort=""){return html_options(name,data,selected,options_only,return_string,sort)}function html_options_block2(name,data,selected="",multiple=0,options_only=!1,return_string=!1,sort="",onchange=""){return html_options_block(name,data,selected,multiple,options_only,return_string,sort,onchange)}function html_options_refill2(name,data,sort=""){html_options_refill(name,data,sort)}function parseQueryString2(query="",return_key=""){return parseQueryString(query,return_key)}function getQueryStringParam2(search="",query="",single=!1){return getQueryStringParam(search,query,single)}function loginLogout2(){loginLogout()}function createLoginRow(login_string,header_id="mainHeader"){lnm.createLoginRow(login_string,header_id)}function createNavMenu(nav_menu,header_id="mainHeader"){lnm.createNavMenu(nav_menu,header_id)}function showFillActionBox(target_id="actionBox",content="",action_box_css=[],override=0,content_override=0){ab.showFillActionBox(target_id,content,action_box_css,override,content_override)}function fillActionBox(target_id="actionBox",content="",action_box_css=[]){ab.fillActionBox(target_id,content,action_box_css)}function adjustActionBox(target_id="actionBox",override=0,content_override=0){ab.adjustActionBox(target_id,override,content_override)}function hideAllActionBoxes(){ab.hideAllActionBoxes()}function hideActionBox(target_id="actionBox"){ab.hideActionBox(target_id)}function showActionBox(target_id="actionBox",override=0,content_override=0,hide_all=!0){ab.showActionBox(target_id,override,content_override,hide_all)}function closeActionBox(target_id="actionBox",clean=!0){ab.closeActionBox(target_id,clean)}function showActionBoxFloat(target_id="actionBox",override=0,content_override=0,hide_all=!1){ab.showActionBoxFloat(target_id,override,content_override,hide_all)}function closeActionBoxFloat(target_id="actionBox",clean=!0){ab.closeActionBoxFloat(target_id,clean)}function createActionBox(target_id="actionBox",title="",contents={},headers={},settings={},show_close=!0){ab.createActionBox(target_id,title,contents,headers,settings,show_close)}function adjustActionBoxHeight(target_id="actionBox",override=0,content_override=0){ab.adjustActionBoxHeight(target_id,override,content_override)} +function errorCatch(err){err.stack?err.lineNumber?console.error("ERROR[%s:%s] ",err.name,err.lineNumber,err):err.line?console.error("ERROR[%s:%s] ",err.name,err.line,err):console.error("ERROR[%s] ",err.name,err):err.number?(console.error("ERROR[%s:%s] %s",err.name,err.number,err.message),console.error("ERROR[description] %s",err.description)):console.error("ERROR[%s] %s",err.name,err.message)}function isFunction(name){return typeof window[name]<"u"&&typeof window[name]=="function"}function executeFunctionByName(functionName,context){var args=Array.prototype.slice.call(arguments,2),namespaces=functionName.split("."),func=namespaces.pop();if(func==null)throw new Error("Cannot get function from namespaces: "+functionName);for(var i=0;iobject[key]===value)??""}function valueInObject(object,value){return objectValueExists(object,value)}function objectValueExists(object,value){return!!Object.keys(object).find(key=>object[key]===value)}function deepCopyFunction(inObject){var outObject,value,key;if(typeof inObject!="object"||inObject===null)return inObject;outObject=Array.isArray(inObject)?[]:{};for(key in inObject)value=inObject[key],outObject[key]=deepCopyFunction(value);return outObject}function loadEl(el_id){let el=document.getElementById(el_id);if(el===null)throw new Error("Cannot find: "+el_id);return el}function pop(theURL,winName,features){let __winName=window.open(theURL,winName,features);__winName?.focus()}function expandTA(ta_id){let ta=this.loadEl(ta_id);if(ta instanceof HTMLElement&&ta.getAttribute("type")!=="textarea")throw new Error("Element is not a textarea: "+ta_id);let maxChars=parseInt(ta.getAttribute("cols")??"0"),ta_value=ta.getAttribute("value"),theRows=[];ta_value!=null&&(theRows=ta_value.split(` +`));for(var numNewRows=0,i=0;imaxChars&&(numNewRows+=Math.ceil((theRows[i].length+2)/maxChars));ta.setAttribute("row",(numNewRows+theRows.length).toString())}function exists(id){return $("#"+id).length>0}var HtmlElementCreator=class{cel(tag,id="",content="",css=[],options={}){return{tag,id,name:options.name,content,css,options,sub:[]}}ael(base,attach,id=""){if(id){if(base.id==id)base.sub.push(deepCopyFunction(attach));else if(isObject(base.sub)&&base.sub.length>0)for(var i=0;i-1&&_element.css.splice(css_index,1),_element}acssel(_element,css){var css_index=_element.css.indexOf(css);return css_index==-1&&_element.css.push(css),_element}scssel(_element,rcss,acss){return this.rcssel(_element,rcss),this.acssel(_element,acss),_element}phfo(tree){let name_elements=["button","fieldset","form","iframe","input","map","meta","object","output","param","select","textarea"],skip_options=["id","name","class"],no_close=["input","br","img","hr","area","col","keygen","wbr","track","source","param","command","base","meta","link","embed"];var content=[],line="<"+tree.tag,i;if(tree.id&&(line+=' id="'+tree.id+'"',name_elements.includes(tree.tag)&&(line+=' name="'+(tree.name?tree.name:tree.id)+'"')),isObject(tree.css)&&tree.css.length>0){for(line+=' class="',i=0;i0)for(tree.content&&content.push(tree.content),i=0;i"),content.join("")}phfa(list){for(var content=[],i=0;i"'/]/g,function(s){var entityMap={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"};return entityMap[s]})}function unescapeHtml(string){return string.replace(/&[#\w]+;/g,function(s){var entityMap={"&":"&","<":"<",">":">",""":'"',"'":"'","/":"/"};return entityMap[s]})}function html_options(name,data,selected="",options_only=!1,return_string=!1,sort=""){return this.html_options_block(name,data,selected,0,options_only,return_string,sort)}function html_options_block(name,data,selected="",multiple=0,options_only=!1,return_string=!1,sort="",onchange=""){var content=[],element_select,select_options={},element_option,data_list=[],value,options={};multiple>0&&(select_options.multiple="",multiple>1&&(select_options.size=multiple)),onchange&&(select_options.OnChange=onchange),element_select=dom.cel("select",name,"",[],select_options),sort=="keys"?data_list=Object.keys(data).sort():sort=="values"?data_list=Object.keys(data).sort((a,b)=>(""+data[a]).localeCompare(data[b])):data_list=Object.keys(data);for(let key of data_list)value=data[key],options={label:value,value:key,selected:""},multiple==0&&!Array.isArray(selected)&&selected==key&&(options.selected=""),multiple==1&&Array.isArray(selected)&&selected.indexOf(key)!=-1&&(options.selected=""),element_option=dom.cel("option","",value,[],options),dom.ael(element_select,element_option);if(options_only)if(return_string){for(var i=0;i(""+data[a]).localeCompare(data[b])):data_list=Object.keys(data),[].forEach.call(document.querySelectorAll("#"+name+" :checked"),function(elm){option_selected=elm.value}),loadEl(name).innerHTML="";for(let key of data_list)value=data[key],element_option=document.createElement("option"),element_option.label=value,element_option.value=key,element_option.innerHTML=value,key==option_selected&&(element_option.selected=!0),loadEl(name).appendChild(element_option)}}function dec2hex(dec){return("0x"+dec.toString(16)).substring(-2)}function getRandomIntInclusive(min,max){return min=Math.ceil(min),max=Math.floor(max),Math.floor(Math.random()*(max-min+1)+min)}function roundPrecision(number,precision){return isNaN(number)||isNaN(precision)?number:Math.round(number*Math.pow(10,precision))/Math.pow(10,precision)}function formatString(string,...args){return string.replace(/{(\d+)}/g,function(match,number){return typeof args[number]<"u"?args[number]:match})}function numberWithCommas(number){var parts=number.toString().split(".");return parts[0]=parts[0].replace(/\B(?=(\d{3})+(?!\d))/g,","),parts.join(".")}function convertLBtoBR(string){return string.replace(/(?:\r\n|\r|\n)/g,"
")}function getTimestamp(){var date=new Date;return date.getTime()}function generateId(len){var arr=new Uint8Array((len||40)/2);return(window.crypto||window.msCrypto).getRandomValues(arr),Array.from(arr,self.dec2hex).join("")}function randomIdF(){return Math.random().toString(36).substring(2)}function getWindowSize(){var width,height;return width=window.innerWidth||window.document.documentElement.clientWidth||window.document.body.clientWidth,height=window.innerHeight||window.document.documentElement.clientHeight||window.document.body.clientHeight,{width,height}}function getScrollOffset(){var left,top;return left=window.pageXOffset||window.document.documentElement.scrollLeft||window.document.body.scrollLeft,top=window.pageYOffset||window.document.documentElement.scrollTop||window.document.body.scrollTop,{left,top}}function getScrollOffsetOpener(){var left,top;return left=opener.window.pageXOffset||opener.document.documentElement.scrollLeft||opener.document.body.scrollLeft,top=opener.window.pageYOffset||opener.document.documentElement.scrollTop||opener.document.body.scrollTop,{left,top}}function setCenter(id,left,top){var dimensions={height:$("#"+id).height()??0,width:$("#"+id).width()??0},type=$("#"+id).css("position"),viewport=this.getWindowSize(),offset=this.getScrollOffset();if(left&&$("#"+id).css({left:viewport.width/2-dimensions.width/2+offset.left+"px"}),top){var top_pos=type=="fixed"?viewport.height/2-dimensions.height/2:viewport.height/2-dimensions.height/2+offset.top;$("#"+id).css({top:top_pos+"px"})}}function goToPos(element,offset=0,duration=500,base="body,html"){try{let element_offset=$("#"+element).offset();if(element_offset==null)return;$("#"+element).length&&$(base).animate({scrollTop:element_offset.top-offset},duration)}catch(err){errorCatch(err)}}function goTo(target){loadEl(target).scrollIntoView({behavior:"smooth"})}function formatBytes(bytes){var i=-1;if(typeof bytes=="bigint"&&(bytes=Number(bytes)),isNaN(bytes))return bytes.toString();do bytes=bytes/1024,i++;while(bytes>99);return Math.round(bytes*Math.pow(10,2))/Math.pow(10,2)+["kB","MB","GB","TB","PB","EB"][i]}function formatBytesLong(bytes){if(typeof bytes=="bigint"&&(bytes=Number(bytes)),isNaN(bytes))return bytes.toString();let negative=!1;bytes<0&&(negative=!0,bytes*=-1);var i=Math.floor(Math.log(bytes)/Math.log(1024)),sizes=["B","KB","MB","GB","TB","PB","EB","ZB","YB"];return(negative?"-":"")+((bytes/Math.pow(1024,i)).toFixed(2)+" "+sizes[i]).toString()}function stringByteFormat(bytes,raw=!1){if(!(typeof bytes=="string"||bytes instanceof String))return bytes.toString();let valid_units="bkmgtpezy",regex=/([\d.,]*)\s?(eb|pb|tb|gb|mb|kb|e|p|t|g|m|k|b)$/i,matches=bytes.match(regex);if(matches!==null){let m1=parseFloat(matches[1].replace(/[^0-9.]/,"")),m2=matches[2].replace(/[^bkmgtpezy]/i,"").charAt(0).toLowerCase();m2&&(bytes=m1*Math.pow(1024,valid_units.indexOf(m2)))}return raw?bytes:Math.round(bytes)}function parseQueryString(query="",return_key="",single=!1){return getQueryStringParam(return_key,query,single)}function getQueryStringParam(search="",query="",single=!1){query||(query=window.location.href);let url=new URL(query),param=null;if(search){let _params=url.searchParams.getAll(search);_params.length==1||single===!0?param=_params[0]:_params.length>1&&(param=_params)}else{param={};for(let[key]of url.searchParams.entries())if(typeof param[key]>"u"){let _params=url.searchParams.getAll(key);param[key]=_params.length<2||single===!0?_params[0]:_params}}return param}function hasUrlParameter(key){var urlParams=new URLSearchParams(window.location.search);return urlParams.has(key)}function getUrlParameter(key){var urlParams=new URLSearchParams(window.location.search);return urlParams.get(key)}function updateUrlParameter(key,value,reload=!1){let url=new URL(window.location.href);url.searchParams.set(key,value);let newUrl=url.toString();window.history.pushState({path:newUrl},"",newUrl),reload&&window.location.reload()}function removeUrlParameter(key,reload=!1){let url=new URL(window.location.href);url.searchParams.delete(key),window.history.pushState({},"",url.toString()),reload&&window.location.reload()}function loginLogout(){let form=document.createElement("form");form.method="post";let hiddenField=document.createElement("input");hiddenField.type="hidden",hiddenField.name="login_logout",hiddenField.value="Logout",form.appendChild(hiddenField),document.body.appendChild(form),form.submit()}function actionIndicator(loc,overlay=!0){$("#indicator").is(":visible")?this.actionIndicatorHide(loc,overlay):this.actionIndicatorShow(loc,overlay)}function actionIndicatorShow(loc,overlay=!0){$("#indicator").is(":visible")||($("#indicator").hasClass("progress")||$("#indicator").addClass("progress"),setCenter("indicator",!0,!0),$("#indicator").show()),overlay===!0&&this.overlayBoxShow()}function actionIndicatorHide(loc,overlay=!0){$("#indicator").hide(),overlay===!0&&overlayBoxHide()}function overlayBoxShow(){$("#overlayBox").is(":visible")?$("#overlayBox").css("zIndex","100"):($("#overlayBox").show(),$("#overlayBox").css("zIndex","98"))}function overlayBoxHide(){parseInt($("#overlayBox").css("zIndex"))>=100?$("#overlayBox").css("zIndex","98"):$("#overlayBox").hide()}function setOverlayBox(){$("#overlayBox").is(":visible")||$("#overlayBox").show()}function hideOverlayBox(){$("#overlayBox").is(":visible")&&$("#overlayBox").hide()}function ClearCall(){$("#actionBox").html(""),$("#actionBox").hide(),$("#overlayBox").hide()}var ActionIndicatorOverlayBox=class{#GL_OB_S=100;#GL_OB_BASE=100;showActionIndicator(loc){if($("#indicator").length==0){var el=document.createElement("div");el.className="progress hide",el.id="indicator",$("body").append(el)}else $("#indicator").hasClass("progress")||$("#indicator").addClass("progress").hide();$("#indicator").is(":visible")||(this.checkOverlayExists(),$("#overlayBox").is(":visible")||$("#overlayBox").show(),$("#overlayBox").css("zIndex",1e3),$("#indicator").show(),setCenter("indicator",!0,!0))}hideActionIndicator(loc){$("#indicator").is(":visible")&&($("#indicator").hide(),this.#GL_OB_S>this.#GL_OB_BASE?$("#overlayBox").css("zIndex",this.#GL_OB_S):($("#overlayBox").hide(),$("#overlayBox").css("zIndex",this.#GL_OB_BASE)))}checkOverlayExists(){if($("#overlayBox").length==0){var el=document.createElement("div");el.className="overlayBoxElement hide",el.id="overlayBox",$("body").append(el)}}showOverlayBoxLayers(el_id){$("#overlayBox").is(":visible")||($("#overlayBox").show(),$("#overlayBox").css("zIndex",this.#GL_OB_BASE),this.#GL_OB_S=this.#GL_OB_BASE),this.#GL_OB_S++,$("#overlayBox").css("zIndex",this.#GL_OB_S),el_id&&$("#"+el_id).length>0&&($("#"+el_id).css("zIndex",this.#GL_OB_S+1),$("#"+el_id).show())}hideOverlayBoxLayers(el_id=""){this.#GL_OB_S--,this.#GL_OB_S<=this.#GL_OB_BASE?(this.#GL_OB_S=this.#GL_OB_BASE,$("#overlayBox").hide(),$("#overlayBox").css("zIndex",this.#GL_OB_BASE)):$("#overlayBox").css("zIndex",this.#GL_OB_S),el_id&&($("#"+el_id).hide(),$("#"+el_id).css("zIndex",0))}clearCallActionBox(){$("#actionBox").html(""),$("#actionBox").hide(),this.hideOverlayBoxLayers()}};var l10nTranslation=class{#i18n={};constructor(i18n2){this.#i18n=i18n2}__(string){return typeof this.#i18n<"u"&&isObject(this.#i18n)&&this.#i18n[string]?this.#i18n[string]:string}};var ActionBox=class{zIndex={base:100,max:110,indicator:0,boxes:{},active:[],top:""};action_box_storage={};action_box_cache_timeout=10*60*1e3;hec;l10n;constructor(hec2,l10n2){this.hec=hec2,this.l10n=l10n2}showFillActionBox(target_id="actionBox",content="",action_box_css=[],override=0,content_override=0){this.fillActionBox(target_id,content,action_box_css),this.showActionBox(target_id,override,content_override)}fillActionBox(target_id="actionBox",content="",action_box_css=[]){exists(target_id)||$("#mainContainer").after(this.hec.phfo(this.hec.cel("div",target_id,"",["actionBoxElement","hide"].concat(action_box_css)))),$("#"+target_id).html(content)}adjustActionBox(target_id="actionBox",override=0,content_override=0){this.adjustActionBoxHeight(target_id,override,content_override),setCenter(target_id,!0,!0)}hideAllActionBoxes(){$('#actionBox, div[id^="actionBox-"].actionBoxElement').hide(),$("#overlayBox").hide()}hideActionBox(target_id="actionBox"){this.closeActionBoxFloat(target_id,!1)}showActionBox(target_id="actionBox",override=0,content_override=0,hide_all=!0){this.showActionBoxFloat(target_id,override,content_override,hide_all)}closeActionBox(target_id="actionBox",clean=!0){this.closeActionBoxFloat(target_id,clean)}showActionBoxFloat(target_id="actionBox",override=0,content_override=0,hide_all=!1){hide_all===!0&&this.hideAllActionBoxes(),exists("overlayBox")||($("body").prepend(this.hec.phfo(this.hec.cel("div","overlayBox","",["overlayBoxElement"]))),$("#overlayBox").css("zIndex",this.zIndex.base)),$("#overlayBox").show(),objectKeyExists(this.zIndex.boxes,target_id)?this.zIndex.boxes[target_id]+10({id:el.id,zIndex:$("#"+el.id).css("zIndex")})).get();if(visible_zIndexes.length>0){let max_zIndex=0,max_el_id="";for(let zIndex_el of visible_zIndexes)parseInt(zIndex_el.zIndex)>max_zIndex&&(max_zIndex=parseInt(zIndex_el.zIndex),max_el_id=zIndex_el.id);$("#overlayBox").css("zIndex",max_zIndex-1),this.zIndex.top=max_el_id}else $("#overlayBox").hide()}createActionBox(target_id="actionBox",title="",contents={},headers={},settings={},show_close=!0){objectKeyExists(this.action_box_storage,target_id)||(this.action_box_storage[target_id]={});let header_css=[];objectKeyExists(settings,"header_css")&&(header_css=settings.header_css);let action_box_css=[];objectKeyExists(settings,"action_box_css")&&(action_box_css=settings.action_box_css);let elements=[];elements.push(this.hec.phfo(this.hec.aelx(this.hec.cel("div",target_id+"_title","",["actionBoxTitle","flx-spbt"].concat(header_css)),...show_close===!0?[this.hec.cel("div","",title,["fs-b","w-80"]),this.hec.aelx(this.hec.cel("div",target_id+"_title_close_button","",["w-20","tar"]),this.hec.cel("input",target_id+"_title_close","",["button-close","fs-s"],{type:"button",value:this.l10n.__("Close"),OnClick:"closeActionBox('"+target_id+"', false);"}))]:[this.hec.cel("div","",title,["fs-b","w-100"])]))),getObjectCount(headers)>0&&(objectKeyExists(headers,"raw_string")?elements.push(headers.raw_string):elements.push(this.hec.phfo(headers))),getObjectCount(contents)>0?objectKeyExists(contents,"raw_string")?elements.push(contents.raw_string):elements.push(this.hec.phfo(contents)):elements.push(this.hec.phfo(this.hec.cel("div",target_id+"_content","",[]))),elements.push(this.hec.phfo(this.hec.aelx(this.hec.cel("div",target_id+"_footer","",["pd-5","flx-spbt"]),...show_close===!0?[this.hec.cel("div","","",["fs-b","w-80"]),this.hec.aelx(this.hec.cel("div",target_id+"_footer_close_button","",["tar","w-20"]),this.hec.cel("input",target_id+"_footer_close","",["button-close","fs-s"],{type:"button",value:this.l10n.__("Close"),OnClick:"closeActionBox('"+target_id+"', false);"}))]:[this.hec.cel("div","","",["fs-b","w-100"])]))),elements.push(this.hec.phfo(this.hec.cel("input",target_id+"-cache_time","",[],{type:"hidden",value:Date.now()}))),this.fillActionBox(target_id,elements.join(""),action_box_css)}adjustActionBoxHeight(target_id="actionBox",override=0,content_override=0){var new_height=0,dim={},abc_dim={},content_id="";switch(isNaN(override)&&(override=0),isNaN(content_override)&&(content_override=0),target_id){case"actionBox":content_id="action_box";break;case"actionBoxSub":content_id="action_box_sub";break;default:content_id=target_id;break}$.each([target_id,content_id+"_content"],function(i,v){$("#"+v).css({height:"",width:""})}),exists(content_id+"_title")&&(dim.height=$("#"+content_id+"_title").outerHeight(),console.log("Target: %s, Action box Title: %s",target_id,dim.height),new_height+=dim.height??0),exists(content_id+"_header")&&(dim.height=$("#"+content_id+"_header").outerHeight(),console.log("Target: %s, Action box Header: %s",target_id,dim.height),new_height+=dim.height??0),exists(content_id+"_content")&&(content_override>0?(console.log("Target: %s, Action box Content Override: %s",target_id,content_override),new_height+=content_override):(abc_dim.height=$("#"+content_id+"_content").outerHeight(),console.log("Target: %s, Action box Content: %s",target_id,abc_dim.height),new_height+=abc_dim.height??0)),exists(content_id+"_footer")&&(dim.height=$("#"+content_id+"_footer").outerHeight(),console.log("Target: %s, Action box Footer: %s",target_id,dim.height),new_height+=dim.height??0),new_height+=override;var viewport=getWindowSize();if(new_height>=viewport.height){exists(content_id+"_content")&&($("#"+content_id+"_content").hasClass("of-s-y")||$("#"+content_id+"_content").addClass("of-s-y")),console.log("Target: %s, Viewport: %s, ActionBox (NH): %s, ABcontent: %s, ABouter: %s",target_id,viewport.height,new_height,abc_dim.height,$("#"+target_id).outerHeight());var m_height=viewport.height-(new_height-(abc_dim.height??0));console.log("Target: %s, New ABcontent: %s",target_id,m_height),$("#"+content_id+"_content").css("height",m_height+"px"),new_height=new_height-(abc_dim.height??0)+m_height,console.log("Target: %s, New Hight: %s",target_id,new_height)}else exists(content_id+"_content")&&$("#"+content_id+"_content").hasClass("of-s-y")&&$("#"+content_id+"_content").removeClass("of-s-y");console.log("Target: %s, Action Box new height: %s px (override %s px, content override %s px), window height: %s px, Visible Height: %s px",target_id,new_height,override,content_override,viewport.height,$("#"+content_id).outerHeight()),$("#"+target_id).css("height",new_height+"px")}};var LoginNavMenu=class{hec;l10n;constructor(hec2,l10n2){this.hec=hec2,this.l10n=l10n2}createLoginRow(login_string,header_id="mainHeader"){exists(header_id)&&(exists("loginRow")||$("#"+header_id).html(this.hec.phfo(this.hec.cel("div","loginRow","",["loginRow","flx-spbt"]))),$("#loginRow").html(this.hec.phfo(this.hec.cel("div","loginRow-name",login_string))),$("#loginRow").append(this.hec.phfo(this.hec.cel("div","loginRow-info",""))),$("#loginRow").append(this.hec.phfo(this.hec.aelx(this.hec.cel("div","loginRow-logout"),this.hec.cel("input","logout","",[],{value:this.l10n.__("Logout"),type:"button",onClick:"loginLogout()"})))))}createNavMenu(nav_menu,header_id="mainHeader"){if(isObject(nav_menu)&&getObjectCount(nav_menu)>1){exists("menuRow")||$("#"+header_id).html(this.hec.phfo(this.hec.cel("div","menuRow","",["menuRow","flx-s"])));var content=[];$.each(nav_menu,function(key,item){key!=0&&content.push(this.hec.phfo(this.hec.cel("div","","·",["pd-2"]))),item.enabled&&(window.location.href.indexOf(item.url)!=-1&&(item.selected=1),content.push(this.hec.phfo(this.hec.aelx(this.hec.cel("div"),this.hec.cel("a","",item.name,["pd-2"].concat(item.selected?"highlight":""),{href:item.url})))))}),$("#menuRow").html(content.join(""))}else $("#menuRow").hide()}};var aiob=new ActionIndicatorOverlayBox,hec=new HtmlElementCreator,l10n=new l10nTranslation(typeof i18n>"u"?{}:i18n),ab=new ActionBox(hec,l10n),lnm=new LoginNavMenu(hec,l10n);String.prototype.format||(String.prototype.format=function(){return console.error("[DEPRECATED] use StringHelpers.formatString"),formatString(this,arguments)}),Number.prototype.round&&(Number.prototype.round=function(prec){return console.error("[DEPRECATED] use MathHelpers.roundPrecision"),roundPrecision(this,prec)}),String.prototype.escapeHTML||(String.prototype.escapeHTML=function(){return console.error("[DEPRECATED] use HtmlHelpers.escapeHtml"),escapeHtml(this)}),String.prototype.unescapeHTML||(String.prototype.unescapeHTML=function(){return console.error("[DEPRECATED] use HtmlHelpers.unescapeHtml"),unescapeHtml(this)});function escapeHtml2(string){return escapeHtml(string)}function roundPrecision2(number,prec){return roundPrecision(number,prec)}function formatString2(string,...args){return formatString(string,...args)}function unescapeHtml2(string){return unescapeHtml(string)}function loadEl2(el_id){return loadEl(el_id)}function pop2(theURL,winName,features){pop(theURL,winName,features)}function expandTA2(ta_id){expandTA(ta_id)}function getWindowSize2(){return getWindowSize()}function getScrollOffset2(){return getScrollOffset()}function getScrollOffsetOpener2(){return getScrollOffsetOpener()}function setCenter2(id,left,top){setCenter(id,left,top)}function goToPos2(element,offset=0,duration=500,base="body,html"){goToPos(element,offset,duration,base)}function goTo2(target){goTo(target)}function __(string){return l10n.__(string)}function numberWithCommas2(x){return numberWithCommas(x)}function convertLBtoBR2(string){return convertLBtoBR(string)}function getTimestamp2(){return getTimestamp()}function dec2hex2(dec){return dec2hex(dec)}function generateId2(len){return generateId(len)}function randomIdF2(){return randomIdF()}function getRandomIntInclusive2(min,max){return getRandomIntInclusive(min,max)}function isFunction2(name){return isFunction(name)}function executeFunctionByName2(functionName,context){return executeFunctionByName(functionName,context)}function isObject2(val){return isObject(val)}function getObjectCount2(object){return getObjectCount(object)}function keyInObject2(key,object){return keyInObject(key,object)}function getKeyByValue2(object,value){return getKeyByValue(object,value)}function valueInObject2(object,value){return valueInObject(object,value)}function deepCopyFunction2(inObject){return deepCopyFunction(inObject)}function exists2(id){return exists(id)}function formatBytes2(bytes){return formatBytes(bytes)}function formatBytesLong2(bytes){return formatBytesLong(bytes)}function stringByteFormat2(bytes){return stringByteFormat(bytes)}function errorCatch2(err){errorCatch(err)}function actionIndicator2(loc,overlay=!0){actionIndicator(loc,overlay)}function actionIndicatorShow2(loc,overlay=!0){actionIndicatorShow(loc,overlay)}function actionIndicatorHide2(loc,overlay=!0){actionIndicatorHide(loc,overlay)}function overlayBoxShow2(){overlayBoxShow()}function overlayBoxHide2(){overlayBoxHide()}function setOverlayBox2(){setOverlayBox()}function hideOverlayBox2(){hideOverlayBox()}function ClearCall2(){ClearCall()}function showActionIndicator(loc){aiob.showActionIndicator(loc)}function hideActionIndicator(loc){aiob.hideActionIndicator(loc)}function checkOverlayExists(){aiob.checkOverlayExists()}function showOverlayBoxLayers(el_id){aiob.showOverlayBoxLayers(el_id)}function hideOverlayBoxLayers(el_id=""){aiob.hideOverlayBoxLayers(el_id)}function clearCallActionBox(){aiob.clearCallActionBox()}function cel(tag,id="",content="",css=[],options={}){return hec.cel(tag,id,content,css,options)}function ael(base,attach,id=""){return hec.ael(base,attach,id)}function aelx(base,...attach){return hec.aelx(base,...attach)}function aelxar(base,attach){return hec.aelxar(base,attach)}function rel(base){return hec.rel(base)}function rcssel(_element,css){return hec.rcssel(_element,css)}function acssel(_element,css){return hec.acssel(_element,css)}function scssel(_element,rcss,acss){hec.scssel(_element,rcss,acss)}function phfo(tree){return hec.phfo(tree)}function phfa(list){return hec.phfa(list)}function html_options2(name,data,selected="",options_only=!1,return_string=!1,sort=""){return html_options(name,data,selected,options_only,return_string,sort)}function html_options_block2(name,data,selected="",multiple=0,options_only=!1,return_string=!1,sort="",onchange=""){return html_options_block(name,data,selected,multiple,options_only,return_string,sort,onchange)}function html_options_refill2(name,data,sort=""){html_options_refill(name,data,sort)}function parseQueryString2(query="",return_key="",single=!1){return parseQueryString(query,return_key,single)}function getQueryStringParam2(search="",query="",single=!1){return getQueryStringParam(search,query,single)}function updateUrlParameter2(key,value,reload=!1){return updateUrlParameter(key,value,reload)}function removeUrlParameter2(key,reload=!1){return removeUrlParameter(key,reload)}function hasUrlParameter2(key){return hasUrlParameter(key)}function getUrlParameter2(key){return getUrlParameter(key)}function loginLogout2(){loginLogout()}function createLoginRow(login_string,header_id="mainHeader"){lnm.createLoginRow(login_string,header_id)}function createNavMenu(nav_menu,header_id="mainHeader"){lnm.createNavMenu(nav_menu,header_id)}function showFillActionBox(target_id="actionBox",content="",action_box_css=[],override=0,content_override=0){ab.showFillActionBox(target_id,content,action_box_css,override,content_override)}function fillActionBox(target_id="actionBox",content="",action_box_css=[]){ab.fillActionBox(target_id,content,action_box_css)}function adjustActionBox(target_id="actionBox",override=0,content_override=0){ab.adjustActionBox(target_id,override,content_override)}function hideAllActionBoxes(){ab.hideAllActionBoxes()}function hideActionBox(target_id="actionBox"){ab.hideActionBox(target_id)}function showActionBox(target_id="actionBox",override=0,content_override=0,hide_all=!0){ab.showActionBox(target_id,override,content_override,hide_all)}function closeActionBox(target_id="actionBox",clean=!0){ab.closeActionBox(target_id,clean)}function showActionBoxFloat(target_id="actionBox",override=0,content_override=0,hide_all=!1){ab.showActionBoxFloat(target_id,override,content_override,hide_all)}function closeActionBoxFloat(target_id="actionBox",clean=!0){ab.closeActionBoxFloat(target_id,clean)}function createActionBox(target_id="actionBox",title="",contents={},headers={},settings={},show_close=!0){ab.createActionBox(target_id,title,contents,headers,settings,show_close)}function adjustActionBoxHeight(target_id="actionBox",override=0,content_override=0){ab.adjustActionBoxHeight(target_id,override,content_override)} //# sourceMappingURL=utils.min.js.map diff --git a/www/admin/layout/javascript/utils.min.js.map b/www/admin/layout/javascript/utils.min.js.map index 86cf0870..b31f6436 100644 --- a/www/admin/layout/javascript/utils.min.js.map +++ b/www/admin/layout/javascript/utils.min.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../../../src/utils/JavaScriptHelpers.mjs", "../../../src/utils/DomHelpers.mjs", "../../../src/utils/HtmlElementCreator.mjs", "../../../src/utils/HtmlHelpers.mjs", "../../../src/utils/MathHelpers.mjs", "../../../src/utils/StringHelpers.mjs", "../../../src/utils/DateTimeHelpers.mjs", "../../../src/utils/UniqIdGenerators.mjs", "../../../src/utils/ResizingAndMove.mjs", "../../../src/utils/FormatBytes.mjs", "../../../src/utils/UrlParser.mjs", "../../../src/utils/LoginLogout.mjs", "../../../src/utils/ActionIndicatorOverlayBox.mjs", "../../../src/utils/l10nTranslation.mjs", "../../../src/utils/ActionBox.mjs", "../../../src/utils/LoginNavMenu.mjs", "../../../src/utils.mjs"], - "sourcesContent": ["/*\nDescription: JavaScript Helpers\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport {\n\terrorCatch, isFunction, executeFunctionByName,\n\tisObject, getObjectCount,\n\tkeyInObject, objectKeyExists,\n\tgetKeyByValue, valueInObject, objectValueExists,\n\tdeepCopyFunction\n};\n\n/**\n * prints out error messages based on data available from the browser\n * @param {Object} err error from try/catch block\n */\nfunction errorCatch(err)\n{\n\t// for FF & Chrome\n\tif (err.stack) {\n\t\t// only FF\n\t\tif (err.lineNumber) {\n\t\t\tconsole.error('ERROR[%s:%s] ', err.name, err.lineNumber, err);\n\t\t} else if (err.line) {\n\t\t\t// only Safari\n\t\t\tconsole.error('ERROR[%s:%s] ', err.name, err.line, err);\n\t\t} else {\n\t\t\tconsole.error('ERROR[%s] ', err.name, err);\n\t\t}\n\t} else if (err.number) {\n\t\t// IE\n\t\tconsole.error('ERROR[%s:%s] %s', err.name, err.number, err.message);\n\t\tconsole.error('ERROR[description] %s', err.description);\n\t} else {\n\t\t// the rest\n\t\tconsole.error('ERROR[%s] %s', err.name, err.message);\n\t}\n}\n\n/**\n * check if name is a function\n * @param {string} name Name of function to check if exists\n * @return {Boolean} true/false\n */\nfunction isFunction(name)\n{\n\tif (typeof window[name] !== 'undefined' &&\n\t\ttypeof window[name] === 'function') {\n\t\treturn true;\n\t} else {\n\t\treturn false;\n\t}\n}\n\n/**\n * call a function by its string name\n * https://stackoverflow.com/a/359910\n * example: executeFunctionByName(\"My.Namespace.functionName\", window, arguments);\n * @param {string} functionName The function name or namespace + function\n * @param {any} context context (window or first namespace)\n * hidden next are all the arguments\n * @return {any} Return values from functon\n */\nfunction executeFunctionByName(functionName, context /*, args */)\n{\n\tvar args = Array.prototype.slice.call(arguments, 2);\n\tvar namespaces = functionName.split('.');\n\tvar func = namespaces.pop();\n\tif (func == undefined) {\n\t\tthrow new Error(\"Cannot get function from namespaces: \" + functionName);\n\t}\n\tfor (var i = 0; i < namespaces.length; i++) {\n\t\tcontext = context[namespaces[i]];\n\t}\n\treturn context[func].apply(context, args);\n}\n\n/**\n * checks if a variable is an object\n * @param {any} val possible object\n * @return {Boolean} true/false if it is an object or not\n */\nfunction isObject(val)\n{\n\tif (val === null) {\n\t\treturn false;\n\t}\n\treturn ((typeof val === 'function') || (typeof val === 'object'));\n}\n\n/**\n * get the length of an object (entries)\n * @param {Object} object object to check\n * @return {Number} number of entry, or -1 if not object\n */\nfunction getObjectCount(object)\n{\n\tif (!isObject(object)) {\n\t\treturn -1;\n\t}\n\treturn Object.keys(object).length;\n}\n\n/**\n * checks if a key exists in a given object\n * @param {String} key key name\n * @param {Object} object object to search key in\n * @return {Boolean} true/false if key exists in object\n * @deprecated Use objectKeyExists\n */\nfunction keyInObject(key, object)\n{\n\treturn objectKeyExists(object, key);\n}\n\n/**\n * This is the correct order and will superseed keyInObject\n * @param {Object} object object to search key in\n * @param {String} key key name\n * @returns {Boolean} true/false if key exists in object\n */\nfunction objectKeyExists(object, key)\n{\n\treturn Object.prototype.hasOwnProperty.call(object, key) ? true : false;\n}\n\n/**\n * returns matching key of value\n * @param {Object} object object to search value in\n * @param {any} value any value (String, Number, etc)\n * @return {String} the key found for the first matching value\n */\nfunction getKeyByValue(object, value)\n{\n\treturn Object.keys(object).find(key => object[key] === value) ?? '';\n}\n\n/**\n * returns true if value is found in object with a key\n * @param {Object} object object to search value in\n * @param {any} value any value (String, Number, etc)\n * @return {Boolean} true on value found, false on not found\n * @deprecated use objectValueExists\n */\nfunction valueInObject(object, value)\n{\n\treturn objectValueExists(object, value);\n}\n\n/**\n * returns true if value is found in object with a key\n * @param {Object} object object to search value in\n * @param {any} value any value (String, Number, etc)\n * @return {Boolean} true on value found, false on not found\n */\nfunction objectValueExists(object, value)\n{\n\treturn Object.keys(object).find(key => object[key] === value) ? true : false;\n}\n\n/**\n * true deep copy for Javascript objects\n * if Object.assign({}, obj) is not working (shallow)\n * or if JSON.parse(JSON.stringify(obj)) is failing\n * @param {Object} inObject Object to copy\n * @return {Object} Copied Object\n */\nfunction deepCopyFunction(inObject)\n{\n\tvar outObject, value, key;\n\tif (typeof inObject !== 'object' || inObject === null) {\n\t\t// Return the value if inObject is not an object\n\t\treturn inObject;\n\t}\n\t// Create an array or object to hold the values\n\toutObject = Array.isArray(inObject) ? [] : {};\n\t// loop over ech entry in object\n\tfor (key in inObject) {\n\t\tvalue = inObject[key];\n\t\t// Recursively (deep) copy for nested objects, including arrays\n\t\toutObject[key] = deepCopyFunction(value);\n\t}\n\n\treturn outObject;\n}\n\n// __END__\n", "/*\nDescription: DOM Helpers\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { loadEl, pop, expandTA, exists };\n\n/**\n * Gets html element or throws an error\n * @param {string} el_id Element ID to get\n * @returns {HTMLElement}\n * @throws Error\n */\nfunction loadEl(el_id)\n{\n\tlet el = document.getElementById(el_id);\n\tif (el === null) {\n\t\tthrow new Error('Cannot find: ' + el_id);\n\t}\n\treturn el;\n}\n\n/**\n * opens a popup window with winName and given features (string)\n * @param {String} theURL the url\n * @param {String} winName window name\n * @param {Object} features popup features\n */\nfunction pop(theURL, winName, features)\n{\n\tlet __winName = window.open(theURL, winName, features);\n\tif (__winName == null) {\n\t\treturn;\n\t}\n\t__winName.focus();\n}\n\n/**\n * automatically resize a text area based on the amount of lines in it\n * @param {string} ta_id element id\n */\nfunction expandTA(ta_id)\n{\n\tlet ta = this.loadEl(ta_id);\n\tif (ta instanceof HTMLElement && ta.getAttribute('type') !== \"textarea\") {\n\t\tthrow new Error(\"Element is not a textarea: \" + ta_id);\n\t}\n\tlet maxChars = parseInt(ta.getAttribute('cols') ?? \"0\");\n\tlet ta_value = ta.getAttribute('value');\n\tlet theRows = [];\n\tif (ta_value != null) {\n\t\ttheRows = ta_value.split('\\n');\n\t}\n\tvar numNewRows = 0;\n\n\tfor ( var i = 0; i < theRows.length; i++ ) {\n\t\tif ((theRows[i].length+2) > maxChars) {\n\t\t\tnumNewRows += Math.ceil( (theRows[i].length+2) / maxChars ) ;\n\t\t}\n\t}\n\tta.setAttribute('row', (numNewRows + theRows.length).toString());\n}\n\n/**\n * checks if a DOM element actually exists\n * @param {String} id Element id to check for\n * @return {Boolean} true if element exists, false on failure\n */\nfunction exists(id)\n{\n\treturn $('#' + id).length > 0 ? true : false;\n}\n\n// __END__\n", "/*\nDescription: DOM Management and HTML builder\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport {\n\tHtmlElementCreator,\n\t// deprecated name\n\tHtmlElementCreator as DomManagement\n};\nimport { deepCopyFunction, isObject } from './JavaScriptHelpers.mjs';\n\nclass HtmlElementCreator {\n\t/**\n\t * reates object for DOM element creation flow\n\t * @param {String} tag must set tag (div, span, etc)\n\t * @param {String} [id=''] optional set for id, if input, select will be used for name\n\t * @param {String} [content=''] text content inside, is skipped if sub elements exist\n\t * @param {Array} [css=[]] array for css tags\n\t * @param {Object} [options={}] anything else (value, placeholder, OnClick, style)\n\t * @return {Object} created element as an object\n\t */\n\tcel(tag, id = '', content = '', css = [], options = {})\n\t{\n\t\treturn {\n\t\t\ttag: tag,\n\t\t\tid: id,\n\t\t\t// override name if set, else id is used. Only for input/button\n\t\t\tname: options.name,\n\t\t\tcontent: content,\n\t\t\tcss: css,\n\t\t\toptions: options,\n\t\t\tsub: []\n\t\t};\n\t}\n\n\t/**\n\t * attach a cel created object to another to create a basic DOM tree\n\t * @param {Object} base object where to attach/search\n\t * @param {Object} attach the object to be attached\n\t * @param {String} [id=''] optional id, if given search in base for this id and attach there\n\t * @return {Object} \"none\", technically there is no return needed as it is global attach\n\t */\n\tael(base, attach, id = '')\n\t{\n\t\tif (id) {\n\t\t\t// base id match already\n\t\t\tif (base.id == id) {\n\t\t\t\tbase.sub.push(deepCopyFunction(attach));\n\t\t\t} else {\n\t\t\t\t// sub check\n\t\t\t\tif (isObject(base.sub) && base.sub.length > 0) {\n\t\t\t\t\tfor (var i = 0; i < base.sub.length; i ++) {\n\t\t\t\t\t\t// recursive call to sub element\n\t\t\t\t\t\tthis.ael(base.sub[i], attach, id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tbase.sub.push(deepCopyFunction(attach));\n\t\t}\n\t\treturn base;\n\t}\n\n\t/**\n\t * directly attach n elements to one master base element\n\t * this type does not support attach with optional id\n\t * @param {Object} base object to where we attach the elements\n\t * @param {...Object} attach attach 1..n: attach directly to the base element those attachments\n\t * @return {Object} \"none\", technically there is no return needed, global attach\n\t */\n\taelx(base, ...attach)\n\t{\n\t\tfor (var i = 0; i < attach.length; i ++) {\n\t\t\tbase.sub.push(deepCopyFunction(attach[i]));\n\t\t}\n\t\treturn base;\n\t}\n\n\t/**\n\t * same as aelx, but instead of using objects as parameters\n\t * get an array of objects to attach\n\t * @param {Object} base object to where we attach the elements\n\t * @param {Array} attach array of objects to attach\n\t * @return {Object} \"none\", technically there is no return needed, global attach\n\t */\n\taelxar(base, attach)\n\t{\n\t\tfor (var i = 0; i < attach.length; i ++) {\n\t\t\tbase.sub.push(deepCopyFunction(attach[i]));\n\t\t}\n\t\treturn base;\n\t}\n\n\t/**\n\t * resets the sub elements of the base element given\n\t * @param {Object} base cel created element\n\t * @return {Object} returns reset base element\n\t */\n\trel(base)\n\t{\n\t\tbase.sub = [];\n\t\treturn base;\n\t}\n\n\t/**\n\t * searches and removes style from css array\n\t * @param {Object} _element element to work one\n\t * @param {String} css style sheet to remove (name)\n\t * @return {Object} returns full element\n\t */\n\trcssel(_element, css)\n\t{\n\t\tvar css_index = _element.css.indexOf(css);\n\t\tif (css_index > -1) {\n\t\t\t_element.css.splice(css_index, 1);\n\t\t}\n\t\treturn _element;\n\t}\n\n\t/**\n\t * adds a new style sheet to the element given\n\t * @param {Object} _element element to work on\n\t * @param {String} css style sheet to add (name)\n\t * @return {Object} returns full element\n\t */\n\tacssel(_element, css)\n\t{\n\t\tvar css_index = _element.css.indexOf(css);\n\t\tif (css_index == -1) {\n\t\t\t_element.css.push(css);\n\t\t}\n\t\treturn _element;\n\t}\n\n\t/**\n\t * removes one css and adds another\n\t * is a wrapper around rcssel/acssel\n\t * @param {Object} _element element to work on\n\t * @param {String} rcss style to remove (name)\n\t * @param {String} acss style to add (name)\n\t * @return {Object} returns full element\n\t */\n\tscssel(_element, rcss, acss)\n\t{\n\t\tthis.rcssel(_element, rcss);\n\t\tthis.acssel(_element, acss);\n\t\treturn _element;\n\t}\n\n\t/**\n\t * parses the object tree created with cel/ael and converts it into an HTML string\n\t * that can be inserted into the page\n\t * @param {Object} tree object tree with dom element declarations\n\t * @return {String} HTML string that can be used as innerHTML\n\t */\n\tphfo(tree)\n\t{\n\t\tlet name_elements = [\n\t\t\t'button',\n\t\t\t'fieldset',\n\t\t\t'form',\n\t\t\t'iframe',\n\t\t\t'input',\n\t\t\t'map',\n\t\t\t'meta',\n\t\t\t'object',\n\t\t\t'output',\n\t\t\t'param',\n\t\t\t'select',\n\t\t\t'textarea',\n\t\t];\n\t\tlet skip_options = [\n\t\t\t'id',\n\t\t\t'name',\n\t\t\t'class',\n\t\t];\n\t\tlet no_close = [\n\t\t\t'input',\n\t\t\t'br',\n\t\t\t'img',\n\t\t\t'hr',\n\t\t\t'area',\n\t\t\t'col',\n\t\t\t'keygen',\n\t\t\t'wbr',\n\t\t\t'track',\n\t\t\t'source',\n\t\t\t'param',\n\t\t\t'command',\n\t\t\t// only in header\n\t\t\t'base',\n\t\t\t'meta',\n\t\t\t'link',\n\t\t\t'embed',\n\t\t];\n\t\t// holds the elements\n\t\tvar content = [];\n\t\t// main part line\n\t\tvar line = '<' + tree.tag;\n\t\tvar i;\n\t\t// first id, if set\n\t\tif (tree.id) {\n\t\t\tline += ' id=\"' + tree.id + '\"';\n\t\t\t// if anything input (input, textarea, select then add name too)\n\t\t\tif (name_elements.includes(tree.tag)) {\n\t\t\t\tline += ' name=\"' + (tree.name ? tree.name : tree.id) + '\"';\n\t\t\t}\n\t\t}\n\t\t// second CSS\n\t\tif (isObject(tree.css) && tree.css.length > 0) {\n\t\t\tline += ' class=\"';\n\t\t\tfor (i = 0; i < tree.css.length; i ++) {\n\t\t\t\tline += tree.css[i] + ' ';\n\t\t\t}\n\t\t\t// strip last space\n\t\t\tline = line.slice(0, -1);\n\t\t\tline += '\"';\n\t\t}\n\t\t// options is anything key = \"data\"\n\t\tif (isObject(tree.options)) {\n\t\t\t// ignores id, name, class as key\n\t\t\tfor (const [key, item] of Object.entries(tree.options)) {\n\t\t\t\tif (!skip_options.includes(key)) {\n\t\t\t\t\tline += ' ' + key + '=\"' + item + '\"';\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// finish open tag\n\t\tline += '>';\n\t\t// push finished line\n\t\tcontent.push(line);\n\t\t// dive into sub tree to attach sub nodes\n\t\t// NOTES: we can have content (text) AND sub nodes at the same level\n\t\t// CONTENT (TEXT) takes preference over SUB NODE in order\n\t\tif (isObject(tree.sub) && tree.sub.length > 0) {\n\t\t\tif (tree.content) {\n\t\t\t\tcontent.push(tree.content);\n\t\t\t}\n\t\t\tfor (i = 0; i < tree.sub.length; i ++) {\n\t\t\t\tcontent.push(this.phfo(tree.sub[i]));\n\t\t\t}\n\t\t} else if (tree.content) {\n\t\t\tcontent.push(tree.content);\n\t\t}\n\t\t// if not input, image or br, then close\n\t\tif (\n\t\t\t!no_close.includes(tree.tag)\n\t\t) {\n\t\t\tcontent.push('');\n\t\t}\n\t\t// combine to string\n\t\treturn content.join('');\n\t}\n\n\t/**\n\t * Create HTML elements from array list\n\t * as a flat element without master object file\n\t * Is like tree.sub call\n\t * @param {Array} list Array of cel created objects\n\t * @return {String} HTML String\n\t */\n\tphfa(list)\n\t{\n\t\tvar content = [];\n\t\tfor (var i = 0; i < list.length; i ++) {\n\t\t\tcontent.push(this.phfo(list[i]));\n\t\t}\n\t\treturn content.join('');\n\t}\n}\n\n// __EMD__\n", "/*\nDescription: HTML Helpers\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { escapeHtml, unescapeHtml, html_options, html_options_block, html_options_refill };\nimport { loadEl} from './DomHelpers.mjs';\nimport { DomManagement } from './HtmlElementCreator.mjs';\nlet dom = new DomManagement();\n\n/**\n * Escapes HTML in string\n * @param {String} string Text to escape HTML in\n * @returns {String}\n */\nfunction escapeHtml(string)\n{\n\treturn string.replace(/[&<>\"'/]/g, function (s) {\n\t\tvar entityMap = {\n\t\t\t'&': '&',\n\t\t\t'<': '<',\n\t\t\t'>': '>',\n\t\t\t'\"': '"',\n\t\t\t'\\'': ''',\n\t\t\t'/': '/'\n\t\t};\n\n\t\treturn entityMap[s];\n\t});\n}\n\n/**\n * Unescape a HTML encoded string\n * @param {String} string Text to unescape HTML in\n * @returns {String}\n */\nfunction unescapeHtml(string)\n{\n\treturn string.replace(/&[#\\w]+;/g, function (s) {\n\t\tvar entityMap = {\n\t\t\t'&': '&',\n\t\t\t'<': '<',\n\t\t\t'>': '>',\n\t\t\t'"': '\"',\n\t\t\t''': '\\'',\n\t\t\t'/': '/'\n\t\t};\n\n\t\treturn entityMap[s];\n\t});\n}\n\n// BLOCK: html wrappers for quickly creating html data blocks\n\n/**\n * NOTE: OLD FORMAT which misses multiple block set\n * creates an select/options drop down block.\n * the array needs to be key -> value format.\n * key is for the option id and value is for the data output\n * @param {String} name name/id\n * @param {Object} data array for the options\n * @param {String} [selected=''] selected item uid\n * @param {Boolean} [options_only=false] if this is true, it will not print the select part\n * @param {Boolean} [return_string=false] return as string and not as element\n * @param {String} [sort=''] if empty as is, else allowed 'keys',\n * 'values' all others are ignored\n * @return {String} html with build options block\n * @deprecated html_options_block\n */\nfunction html_options(name, data, selected = '', options_only = false, return_string = false, sort = '')\n{\n\t// wrapper to new call\n\treturn this.html_options_block(\n\t\tname, data, selected, 0, options_only, return_string, sort\n\t);\n}\n\n/**\n * NOTE: USE THIS CALL, the above one is deprecated\n * creates an select/options drop down block.\n * the array needs to be key -> value format.\n * key is for the option id and value is for the data output\n * @param {String} name name/id\n * @param {Object} data array for the options\n * @param {String} [selected=''] selected item uid\n * @param {Number} [multiple=0] if this is 1 or larger, the drop down\n * will be turned into multiple select\n * the number sets the size value unless it is 1,\n * then it is default\n * @param {Boolean} [options_only=false] if this is true, it will not print the select part\n * @param {Boolean} [return_string=false] return as string and not as element\n * @param {String} [sort=''] if empty as is, else allowed 'keys',\n * 'values' all others are ignored\n * @param {String} [onchange=''] onchange trigger call, default unset\n * @return {String} html with build options block\n */\nfunction html_options_block(\n\tname, data, selected = '', multiple = 0, options_only = false, return_string = false, sort = '', onchange = ''\n) {\n\tvar content = [];\n\tvar element_select;\n\tvar select_options = {};\n\tvar element_option;\n\tvar data_list = []; // for sorted output\n\tvar value;\n\tvar options = {};\n\t// var option;\n\tif (multiple > 0) {\n\t\tselect_options.multiple = '';\n\t\tif (multiple > 1) {\n\t\t\tselect_options.size = multiple;\n\t\t}\n\t}\n\tif (onchange) {\n\t\tselect_options.OnChange = onchange;\n\t}\n\t// set outside select, gets stripped on return if options only is true\n\telement_select = dom.cel('select', name, '', [], select_options);\n\t// console.log('Call for %s, options: %s', name, options_only);\n\tif (sort == 'keys') {\n\t\tdata_list = Object.keys(data).sort();\n\t} else if (sort == 'values') {\n\t\tdata_list = Object.keys(data).sort((a, b) => ('' + data[a]).localeCompare(data[b]));\n\t} else {\n\t\tdata_list = Object.keys(data);\n\t}\n\t// console.log('ORDER: %s', data_list);\n\t// use the previously sorted list\n\t// for (const [key, value] of Object.entries(data)) {\n\tfor (const key of data_list) {\n\t\tvalue = data[key];\n\t\t// console.log('create [%s] options: key: %s, value: %s', name, key, value);\n\t\t// basic options init\n\t\toptions = {\n\t\t\t'label': value,\n\t\t\t'value': key,\n\t\t\t'selected': ''\n\t\t};\n\t\t// add selected if matching\n\t\tif (multiple == 0 && !Array.isArray(selected) && selected == key) {\n\t\t\toptions.selected = '';\n\t\t}\n\t\t// for multiple, we match selected as array\n\t\tif (multiple == 1 && Array.isArray(selected) && selected.indexOf(key) != -1) {\n\t\t\toptions.selected = '';\n\t\t}\n\t\t// create the element option\n\t\telement_option = dom.cel('option', '', value, [], options);\n\t\t// attach it to the select element\n\t\tdom.ael(element_select, element_option);\n\t}\n\t// if with select part, convert to text\n\tif (!options_only) {\n\t\tif (return_string) {\n\t\t\tcontent.push(dom.phfo(element_select));\n\t\t\treturn content.join('');\n\t\t} else {\n\t\t\treturn element_select;\n\t\t}\n\t} else {\n\t\t// strip select part\n\t\tif (return_string) {\n\t\t\tfor (var i = 0; i < element_select.sub.length; i ++) {\n\t\t\t\tcontent.push(dom.phfo(element_select.sub[i]));\n\t\t\t}\n\t\t\treturn content.join('');\n\t\t} else {\n\t\t\treturn element_select.sub;\n\t\t}\n\t}\n}\n\n/**\n * refills a select box with options and keeps the selected\n * @param {String} name name/id\n * @param {Object} data array of options\n * @param {String} [sort=''] if empty as is, else allowed 'keys', 'values'\n * all others are ignored\n */\nfunction html_options_refill(name, data, sort = '')\n{\n\tvar element_option;\n\tvar option_selected;\n\tvar data_list = []; // for sorted output\n\tvar value;\n\t// skip if not exists\n\tif (document.getElementById(name)) {\n\t\t// console.log('Call for %s, options: %s', name, options_only);\n\t\tif (sort == 'keys') {\n\t\t\tdata_list = Object.keys(data).sort();\n\t\t} else if (sort == 'values') {\n\t\t\tdata_list = Object.keys(data).sort((a, b) => ('' + data[a]).localeCompare(data[b]));\n\t\t} else {\n\t\t\tdata_list = Object.keys(data);\n\t\t}\n\t\t// first read in existing ones from the options and get the selected one\n\t\t[].forEach.call(document.querySelectorAll('#' + name + ' :checked'), function(elm) {\n\t\t\toption_selected = elm.value;\n\t\t});\n\t\tloadEl(name).innerHTML = '';\n\t\tfor (const key of data_list) {\n\t\t\tvalue = data[key];\n\t\t\t// console.log('add [%s] options: key: %s, value: %s', name, key, value);\n\t\t\telement_option = document.createElement('option');\n\t\t\telement_option.label = value;\n\t\t\telement_option.value = key;\n\t\t\telement_option.innerHTML = value;\n\t\t\tif (key == option_selected) {\n\t\t\t\telement_option.selected = true;\n\t\t\t}\n\t\t\tloadEl(name).appendChild(element_option);\n\t\t}\n\t}\n}\n\n// __EMD__\n", "/*\nDescription: Math Helpers\nDate: 2025/3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { dec2hex, getRandomIntInclusive, roundPrecision };\n\n/**\n * dec2hex :: Integer -> String\n * i.e. 0-255 -> '00'-'ff'\n * @param {Number} dec decimal string\n * @return {String} hex encdoded number, prefix with 0x\n */\nfunction dec2hex(dec)\n{\n\treturn ('0x' + dec.toString(16)).substring(-2);\n}\n\n/**\n * generate a number between min/max\n * with min/max inclusive.\n * eg: 1,5 will create a number ranging from 1 o 5\n * @param {Number} min minimum int number inclusive\n * @param {Number} max maximum int number inclusive\n * @return {Number} Random number\n */\nfunction getRandomIntInclusive(min, max)\n{\n\tmin = Math.ceil(min);\n\tmax = Math.floor(max);\n\t// The maximum is inclusive and the minimum is inclusive\n\treturn Math.floor(Math.random() * (max - min + 1) + min);\n}\n\n/**\n * Round a number to precision\n * @param {Number} number Number to round\n * @param {Number} precision Precision value\n * @returns {Number}\n */\nfunction roundPrecision(number, precision)\n{\n\tif (isNaN(number) || isNaN(precision)) {\n\t\treturn number;\n\t}\n\treturn Math.round(number * Math.pow(10, precision)) / Math.pow(10, precision);\n}\n\n// __END__\n", "/*\nDescription: String Helpers\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { formatString, numberWithCommas, convertLBtoBR };\n\n/**\n * simple sprintf formater for replace\n * usage: formatString(\"{0} is cool, {1} is not\", \"Alpha\", \"Beta\");\n * First, checks if it isn't implemented yet.\n * @param {String} string String with {..} entries\n * @param {...any} args List of replacement\n * @returns {String} Escaped string\n */\nfunction formatString(string, ...args)\n{\n\treturn string.replace(/{(\\d+)}/g, function(match, number)\n\t{\n\t\treturn typeof args[number] != 'undefined' ?\n\t\t\targs[number] :\n\t\t\tmatch\n\t\t;\n\t});\n}\n/**\n * formats flat number 123456 to 123,456\n * @param {Number} number number to be formated\n * @return {String} formatted with , in thousands\n */\nfunction numberWithCommas(number)\n{\n\tvar parts = number.toString().split('.');\n\tparts[0] = parts[0].replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n\treturn parts.join('.');\n}\n\n/**\n * converts line breaks to br\n * @param {String} string any string\n * @return {String} string with
\n */\nfunction convertLBtoBR(string)\n{\n\treturn string.replace(/(?:\\r\\n|\\r|\\n)/g, '
');\n}\n\n// __END__\n", "/*\nDescription: Date Time functions\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { getTimestamp };\n\n/**\n * returns current timestamp (unix timestamp)\n * @return {Number} timestamp (in milliseconds)\n */\nfunction getTimestamp()\n{\n\tvar date = new Date();\n\treturn date.getTime();\n}\n\n// __END__\n", "/*\nDescription: Generate unique ids\nDate: 2025/3/7\nCreator: Clemens Schwaighofer\n*/\n\nexport { generateId, randomIdF };\n\n\n/**\n * generateId :: Integer -> String\n * only works on mondern browsers\n * @param {Number} len length of unique id string\n * @return {String} random string in length of len\n */\nfunction generateId(len)\n{\n\tvar arr = new Uint8Array((len || 40) / 2);\n\t(\n\t\twindow.crypto ||\n\t\t// @ts-ignore\n\t\twindow.msCrypto\n\t).getRandomValues(arr);\n\treturn Array.from(arr, self.dec2hex).join('');\n}\n\n/**\n * creates a pseudo random string of 10 or 11 characters\n * works on all browsers\n * after many runs it will create duplicates\n * NOTE: no idea why this sometimes returns 10 or 11\n * @return {String} not true random string\n */\nfunction randomIdF()\n{\n\treturn Math.random().toString(36).substring(2);\n}\n", "/*\nDescription: Resize and Move Javascript\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nimport { errorCatch} from './JavaScriptHelpers.mjs';\nimport { loadEl } from './DomHelpers.mjs';\nexport { getWindowSize, getScrollOffset, getScrollOffsetOpener, setCenter, goToPos, goTo };\n\n/**\n * wrapper to get the real window size for the current browser window\n * @return {Object} object with width/height\n */\nfunction getWindowSize()\n{\n\tvar width, height;\n\twidth = window.innerWidth || (window.document.documentElement.clientWidth || window.document.body.clientWidth);\n\theight = window.innerHeight || (window.document.documentElement.clientHeight || window.document.body.clientHeight);\n\treturn {\n\t\twidth: width,\n\t\theight: height\n\t};\n}\n\n/**\n * wrapper to get the correct scroll offset\n * @return {Object} object with x/y px\n */\nfunction getScrollOffset()\n{\n\tvar left, top;\n\tleft = window.pageXOffset || (window.document.documentElement.scrollLeft || window.document.body.scrollLeft);\n\ttop = window.pageYOffset || (window.document.documentElement.scrollTop || window.document.body.scrollTop);\n\treturn {\n\t\tleft: left,\n\t\ttop: top\n\t};\n}\n\n/**\n * wrapper to get the correct scroll offset for opener page (from popup)\n * @return {Object} object with x/y px\n */\nfunction getScrollOffsetOpener()\n{\n\tvar left, top;\n\tleft = opener.window.pageXOffset || (opener.document.documentElement.scrollLeft || opener.document.body.scrollLeft);\n\ttop = opener.window.pageYOffset || (opener.document.documentElement.scrollTop || opener.document.body.scrollTop);\n\treturn {\n\t\tleft: left,\n\t\ttop: top\n\t};\n}\n\n/**\n * centers div to current window size middle\n * @param {String} id element to center\n * @param {Boolean} left if true centers to the middle from the left\n * @param {Boolean} top if true centers to the middle from the top\n */\nfunction setCenter(id, left, top)\n{\n\t// get size of id\n\tvar dimensions = {\n\t\theight: $('#' + id).height() ?? 0,\n\t\twidth: $('#' + id).width() ?? 0\n\t};\n\tvar type = $('#' + id).css('position');\n\tvar viewport = this.getWindowSize();\n\tvar offset = this.getScrollOffset();\n\n\t// console.log('Id %s, type: %s, dimensions %s x %s, viewport %s x %s', id, type, dimensions.width, dimensions.height, viewport.width, viewport.height);\n\t// console.log('Scrolloffset left: %s, top: %s', offset.left, offset.top);\n\t// 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)));\n\tif (left) {\n\t\t$('#' + id).css({\n\t\t\tleft: (viewport.width / 2) - (dimensions.width / 2) + offset.left + 'px'\n\t\t});\n\t}\n\tif (top) {\n\t\t// if we have fixed, we do not add the offset, else it moves out of the screen\n\t\tvar top_pos = type == 'fixed' ?\n\t\t\t(viewport.height / 2) - (dimensions.height / 2) :\n\t\t\t(viewport.height / 2) - (dimensions.height / 2) + offset.top;\n\t\t$('#' + id).css({\n\t\t\ttop: top_pos + 'px'\n\t\t});\n\t}\n}\n\n/**\n * goes to an element id position\n * @param {String} element element id to move to\n * @param {Number} [offset=0] offset from top, default is 0 (px)\n * @param {Number} [duration=500] animation time, default 500ms\n * @param {String} [base='body,html'] base element for offset scroll\n */\nfunction goToPos(element, offset = 0, duration = 500, base = 'body,html')\n{\n\ttry {\n\t\tlet element_offset = $('#' + element).offset();\n\t\tif (element_offset == undefined) {\n\t\t\treturn;\n\t\t}\n\t\tif ($('#' + element).length) {\n\t\t\t$(base).animate({\n\t\t\t\tscrollTop: element_offset.top - offset\n\t\t\t}, duration);\n\t\t}\n\t} catch (err) {\n\t\terrorCatch(err);\n\t}\n}\n\n/**\n * go to element, scroll\n * non jquery\n * @param {string} target\n*/\nfunction goTo(target)\n{\n\tloadEl(target).scrollIntoView({\n\t\tbehavior: 'smooth'\n\t});\n}\n\n// __END__\n", "/*\nDescription: Byte string formatting\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { formatBytes, formatBytesLong, stringByteFormat };\n\n/**\n * converts a int number into bytes with prefix in two decimals precision\n * currently precision is fixed, if dynamic needs check for max/min precision\n * @param {Number|BigInt} bytes bytes in int\n * @return {String} string in GB/MB/KB\n */\nfunction formatBytes(bytes)\n{\n\tvar i = -1;\n\t// If this ia bigint -> convert to number, we need the decimals\n\tif (typeof bytes === \"bigint\") {\n\t\tbytes = Number(bytes);\n\t}\n\tif (isNaN(bytes)) {\n\t\treturn bytes.toString();\n\t}\n\tdo {\n\t\tbytes = bytes / 1024;\n\t\ti++;\n\t} while (bytes > 99);\n\treturn (\n\t\tMath.round(bytes * Math.pow(10, 2)) / Math.pow(10, 2)\n\t) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];\n}\n\n/**\n * like formatBytes, but returns bytes for <1KB and not 0.n KB\n * @param {Number|BigInt} bytes bytes in int\n * @return {String} string in GB/MB/KB\n */\nfunction formatBytesLong(bytes)\n{\n\t// If this ia bigint -> convert to number, we need the decimals\n\tif (typeof bytes === \"bigint\") {\n\t\tbytes = Number(bytes);\n\t}\n\tif (isNaN(bytes)) {\n\t\treturn bytes.toString();\n\t}\n\tlet negative = false;\n\tif (bytes < 0) {\n\t\tnegative = true;\n\t\tbytes *= -1;\n\t}\n\tvar i = Math.floor(Math.log(bytes) / Math.log(1024));\n\tvar sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];\n\treturn (negative ? '-' : '') + (\n\t\t(\n\t\t\tbytes /\n\t\t\tMath.pow(1024, i)\n\t\t).toFixed(2)\n\t\t// * 1 + ' ' + sizes[i]\n\t\t+ ' ' + sizes[i]\n\t).toString();\n}\n\n/**\n * Convert a string with B/K/M/etc into a byte number\n * @param {String|Number|Object} bytes Any string with B/K/M/etc\n * @param {Boolean} raw [default=false] Return not rounded values\n * @return {String|Number} A byte number, or original string as is\n */\nfunction stringByteFormat(bytes, raw=false)\n{\n\t// if anything not string return\n\tif (!(typeof bytes === 'string' || bytes instanceof String)) {\n\t\treturn bytes.toString();\n\t}\n\t// for pow exponent list\n\tlet valid_units = 'bkmgtpezy';\n\t// valid string that can be converted\n\tlet regex = /([\\d.,]*)\\s?(eb|pb|tb|gb|mb|kb|e|p|t|g|m|k|b)$/i;\n\tlet matches = bytes.match(regex);\n\t// if nothing found, return original input\n\tif (matches !== null) {\n\t\t// remove all non valid entries outside numbers and .\n\t\t// convert to float number\n\t\tlet m1 = parseFloat(matches[1].replace(/[^0-9.]/,''));\n\t\t// only get the FIRST letter from the size, convert it to lower case\n\t\tlet m2 = matches[2].replace(/[^bkmgtpezy]/i, '').charAt(0).toLowerCase();\n\t\tif (m2) {\n\t\t\t// use the position in the valid unit list to do the math conversion\n\t\t\tbytes = m1 * Math.pow(1024, valid_units.indexOf(m2));\n\t\t}\n\t}\n\t// if we want to have the raw data returned\n\tif (raw) {\n\t\treturn bytes;\n\t}\n\treturn Math.round(bytes);\n}\n\n// __END__\n", "/*\nDescription: HTML Helpers\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { parseQueryString, getQueryStringParam };\n\n/**\n * NOTE: this original code was wrong, now using URL and parsing through\n * getQueryStringParam\n * parses a query string from window.location.search.substring(1)\n * ALTERNATIVE CODE\n * var url = new URL(window.location.href);\n * param_uid = url.searchParams.get('uid');\n * @param {String} [query=''] the query string to parse\n * if not set will auto fill\n * @param {String} [return_key=''] if set only returns this key entry\n * or empty for none\n * @param {Boolean} [single=false] if set to true then only the first found\n * will be returned\n * @return {Object|String} parameter entry list\n */\nfunction parseQueryString(query = '', return_key = '', single = false)\n{\n\treturn getQueryStringParam(return_key, query, single);\n}\n\n/**\n * searches query parameters for entry and returns data either as string or array\n * if no search is given the whole parameters are returned as an object\n * if a parameter is set several times it will be returned as an array\n * if search parameter set and nothing found and empty string is returned\n * if no parametes exist and no serach is set and empty object is returned\n * @param {String} [search=''] if set searches for this entry, if empty\n * all parameters are returned\n * @param {String} [query=''] different query string to parse, if not\n * set (default) the current window href is used\n * @param {Boolean} [single=false] if set to true then only the first found\n * will be returned\n * @return {Object|Array|String} if search is empty, object, if search is set\n * and only one entry, then string, else array\n * unless single is true\n */\nfunction getQueryStringParam(search = '', query = '', single = false)\n{\n\tif (!query) {\n\t\tquery = window.location.href;\n\t}\n\tconst url = new URL(query);\n\tlet param = null;\n\tif (search) {\n\t\tlet _params = url.searchParams.getAll(search);\n\t\tif (_params.length == 1 || single === true) {\n\t\t\tparam = _params[0];\n\t\t} else if (_params.length > 1) {\n\t\t\tparam = _params;\n\t\t}\n\t} else {\n\t\t// will be object, so declare it one\n\t\tparam = {};\n\t\t// loop over paramenters\n\t\tfor (const [key] of url.searchParams.entries()) {\n\t\t\t// check if not yet set\n\t\t\tif (typeof param[key] === 'undefined') {\n\t\t\t\t// get the parameters multiple\n\t\t\t\tlet _params = url.searchParams.getAll(key);\n\t\t\t\t// if 1 set as string, else attach array as is\n\t\t\t\tparam[key] = _params.length < 2 || single === true ?\n\t\t\t\t\t_params[0] :\n\t\t\t\t\t_params;\n\t\t\t}\n\t\t}\n\t}\n\treturn param;\n}\n\n// __EMD__\n", "/*\nDescription: Login access and menu\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { loginLogout };\n\n/**\n * submits basic data for form logout\n */\nfunction loginLogout()\n{\n\tconst form = document.createElement('form');\n\tform.method = 'post';\n\tconst hiddenField = document.createElement('input');\n\thiddenField.type = 'hidden';\n\thiddenField.name = 'login_logout';\n\thiddenField.value = 'Logout';\n\tform.appendChild(hiddenField);\n\tdocument.body.appendChild(form);\n\tform.submit();\n}\n\n// __END__\n", "/*\nDescription: Action Indicator and Overlay\nDate: 2025/2/7\nCreator: Clemens Schwaighofer\n*/\n\nimport { setCenter } from './ResizingAndMove.mjs';\nexport {\n\tActionIndicatorOverlayBox,\n\tactionIndicator, actionIndicatorShow, actionIndicatorHide, overlayBoxShow,\n\toverlayBoxHide, setOverlayBox, hideOverlayBox, ClearCall\n};\n\n/*************************************************************\n * OLD action indicator and overlay boxes calls\n * DO NOT USE\n * actionIndicator -> showActionIndicator\n * actionIndicator -> hideActionIndicator\n * actionIndicatorShow -> showActionIndicator\n * actionIndicatorHide -> hideActionIndicator\n * overlayBoxShow -> showOverlayBoxLayers\n * overlayBoxHide -> hideOverlayBoxLayers\n * setOverlayBox -> showOverlayBoxLayers\n * hideOverlayBox -> hideOverlayBoxLayers\n * ClearCall -> clearCallActionBox\n * ***********************************************************/\n\n/**\n * show or hide the \"do\" overlay\n * @param {String} loc location name for action indicator\n * default empty. for console.log\n * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block\n * @deprecated showActionIndicator, hideActionIndicator\n */\nfunction actionIndicator(loc, overlay = true)\n{\n\tif ($('#indicator').is(':visible')) {\n\t\tthis.actionIndicatorHide(loc, overlay);\n\t} else {\n\t\tthis.actionIndicatorShow(loc, overlay);\n\t}\n}\n\n/**\n * explicit show for action Indicator\n * instead of automatically show or hide, do on command show\n * @param {String} loc location name for action indicator\n * default empty. for console.log\n * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block\n * @deprecated showActionIndicator, hideActionIndicator\n */\nfunction actionIndicatorShow(loc, overlay = true)\n{\n\t// console.log('{Indicator}: SHOW [%s]', loc);\n\tif (!$('#indicator').is(':visible')) {\n\t\tif (!$('#indicator').hasClass('progress')) {\n\t\t\t$('#indicator').addClass('progress');\n\t\t}\n\t\tsetCenter('indicator', true, true);\n\t\t$('#indicator').show();\n\t}\n\tif (overlay === true) {\n\t\tthis.overlayBoxShow();\n\t}\n}\n\n/**\n * explicit hide for action Indicator\n * instead of automatically show or hide, do on command hide\n * @param {String} loc location name for action indicator\n * default empty. for console.log\n * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block\n * @deprecated hideActionIndicator\n */\nfunction actionIndicatorHide(loc, overlay = true)\n{\n\t// console.log('{Indicator}: HIDE [%s]', loc);\n\t$('#indicator').hide();\n\tif (overlay === true) {\n\t\toverlayBoxHide();\n\t}\n}\n\n/**\n * shows the overlay box or if already visible, bumps the zIndex to 100\n * @deprecated showOverlayBoxLayers\n */\nfunction overlayBoxShow()\n{\n\t// check if overlay box exists and if yes set the z-index to 100\n\tif ($('#overlayBox').is(':visible')) {\n\t\t$('#overlayBox').css('zIndex', '100');\n\t} else {\n\t\t$('#overlayBox').show();\n\t\t$('#overlayBox').css('zIndex', '98');\n\t}\n}\n\n/**\n * hides the overlay box or if zIndex is 100 bumps it down to previous level\n * @deprecated hideOverlayBoxLayers\n */\nfunction overlayBoxHide()\n{\n\t// if the overlay box z-index is 100, do no hide, but set to 98\n\tif (parseInt($('#overlayBox').css('zIndex')) >= 100) {\n\t\t$('#overlayBox').css('zIndex', '98');\n\t} else {\n\t\t$('#overlayBox').hide();\n\t}\n}\n\n/**\n * position the overlay block box and shows it\n * @deprecated showOverlayBoxLayers\n */\nfunction setOverlayBox()\n{\n\tif (!$('#overlayBox').is(':visible')) {\n\t\t$('#overlayBox').show();\n\t}\n}\n\n/**\n * opposite of set, always hides overlay box\n * @deprecated hideOverlayBoxLayers\n */\nfunction hideOverlayBox()\n{\n\tif ($('#overlayBox').is(':visible')) {\n\t\t$('#overlayBox').hide();\n\t}\n}\n\n/**\n * the abort call, clears the action box and hides it and the overlay box\n * @deprecated clearCallActionBox\n */\nfunction ClearCall()\n{\n\t$('#actionBox').html('');\n\t$('#actionBox').hide();\n\t$('#overlayBox').hide();\n}\n\n/*\nThe below class will need the following CSS set\n\nProgress indicator (#indicator):\n.progress {\n\twidth: 100px;\n\theight: 100px;\n\tbackground: rgba(255, 255, 255, 0.6);\n\tborder: 20px solid rgba(255, 255, 255 ,0.25);\n\tborder-left-color: rgba(3, 155, 229 ,1);\n\tborder-top-color: rgba(3, 155, 229 ,1);\n\tborder-radius: 50%;\n\tdisplay: inline-block;\n\tanimation: progress-move 600ms infinite linear;\n\tleft: 0;\n\ttop: 0;\n\tposition: absolute;\n\tz-index: 1000;\n}\n@keyframes progress-move {\n\tto {\n\t\ttransform: rotate(1turn)\n\t}\n}\n\nOverlay box darken background (#overlayBox):\n.overlayBoxElement {\n\tbackground-color: rgba(0, 0, 0, 0.3);\n\theight: 100%;\n\tleft: 0;\n\tposition: fixed;\n\ttop: 0;\n\twidth: 100%;\n\tz-index: 98;\n}\n*/\n\nclass ActionIndicatorOverlayBox {\n\n\t// open overlay boxes counter for z-index\n\t#GL_OB_S = 100;\n\t#GL_OB_BASE = 100;\n\n\t/**\n\t * show action indicator\n\t * - checks if not existing and add\n\t * - only shows if not visible (else ignore)\n\t * - overlaybox check is called and shown on a fixzed\n\t * zIndex of 1000\n\t * - indicator is page centered\n\t * @param {String} loc ID string, only used for console log\n\t */\n\tshowActionIndicator(loc) // eslint-disable-line no-unused-vars\n\t{\n\t\t// console.log('{Indicator}: SHOW [%s]', loc);\n\t\t// check if indicator element exists\n\t\tif ($('#indicator').length == 0) {\n\t\t\tvar el = document.createElement('div');\n\t\t\tel.className = 'progress hide';\n\t\t\tel.id = 'indicator';\n\t\t\t$('body').append(el);\n\t\t} else if (!$('#indicator').hasClass('progress')) {\n\t\t\t// if I add a class it will not be hidden anymore\n\t\t\t// hide it\n\t\t\t$('#indicator').addClass('progress').hide();\n\t\t}\n\t\t// indicator not visible\n\t\tif (!$('#indicator').is(':visible')) {\n\t\t\t// check if overlay box element exits\n\t\t\tthis.checkOverlayExists();\n\t\t\t// if not visible show\n\t\t\tif (!$('#overlayBox').is(':visible')) {\n\t\t\t\t$('#overlayBox').show();\n\t\t\t}\n\t\t\t// always set to 1000 zIndex to be top\n\t\t\t$('#overlayBox').css('zIndex', 1000);\n\t\t\t// show indicator\n\t\t\t$('#indicator').show();\n\t\t\t// center it\n\t\t\tsetCenter('indicator', true, true);\n\t\t}\n\t}\n\n\t/**\n\t * hide action indicator, if it is visiable\n\t * If the global variable GL_OB_S is > GL_OB_BASE then\n\t * the overlayBox is not hidden but the zIndex\n\t * is set to this value\n\t * @param {String} loc ID string, only used for console log\n\t */\n\thideActionIndicator(loc) // eslint-disable-line no-unused-vars\n\t{\n\t\t// console.log('{Indicator}: HIDE [%s]', loc);\n\t\t// check if indicator is visible\n\t\tif ($('#indicator').is(':visible')) {\n\t\t\t// hide indicator\n\t\t\t$('#indicator').hide();\n\t\t\t// if global overlay box count is > 0\n\t\t\t// then set it to this level and keep\n\t\t\tif (this.#GL_OB_S > this.#GL_OB_BASE) {\n\t\t\t\t$('#overlayBox').css('zIndex', this.#GL_OB_S);\n\t\t\t} else {\n\t\t\t\t// else hide overlay box and set zIndex to 0\n\t\t\t\t$('#overlayBox').hide();\n\t\t\t\t$('#overlayBox').css('zIndex', this.#GL_OB_BASE);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * checks if overlayBox exists, if not it is\n\t * added as hidden item at the body end\n\t */\n\tcheckOverlayExists()\n\t{\n\t\t// check if overlay box exists, if not create it\n\t\tif ($('#overlayBox').length == 0) {\n\t\t\tvar el = document.createElement('div');\n\t\t\tel.className = 'overlayBoxElement hide';\n\t\t\tel.id = 'overlayBox';\n\t\t\t$('body').append(el);\n\t\t}\n\t}\n\n\t/**\n\t * show overlay box\n\t * if not visible show and set zIndex to 10 (GL_OB_BASE)\n\t * if visible, add +1 to the GL_OB_S variable and\n\t * up zIndex by this value\n\t */\n\tshowOverlayBoxLayers(el_id)\n\t{\n\t\t// console.log('SHOW overlaybox: %s', GL_OB_S);\n\t\t// if overlay box is not visible show and set zIndex to 0\n\t\tif (!$('#overlayBox').is(':visible')) {\n\t\t\t$('#overlayBox').show();\n\t\t\t$('#overlayBox').css('zIndex', this.#GL_OB_BASE);\n\t\t\t// also set start variable to 0\n\t\t\tthis.#GL_OB_S = this.#GL_OB_BASE;\n\t\t}\n\t\t// up the overlay box counter by 1\n\t\tthis.#GL_OB_S ++;\n\t\t// set zIndex\n\t\t$('#overlayBox').css('zIndex', this.#GL_OB_S);\n\t\t// if element given raise zIndex and show\n\t\tif (el_id) {\n\t\t\tif ($('#' + el_id).length > 0) {\n\t\t\t\t$('#' + el_id).css('zIndex', this.#GL_OB_S + 1);\n\t\t\t\t$('#' + el_id).show();\n\t\t\t}\n\t\t}\n\t\t// console.log('SHOW overlaybox NEW zIndex: %s', $('#overlayBox').css('zIndex'));\n\t}\n\n\t/**\n\t * hide overlay box\n\t * lower GL_OB_S value by -1\n\t * if we are 10 (GL_OB_BASE) or below hide the overlayIndex\n\t * and set zIndex and GL_OB_S to 0\n\t * else just set zIndex to the new GL_OB_S value\n\t * @param {String} el_id Target to hide layer\n\t */\n\thideOverlayBoxLayers(el_id='')\n\t{\n\t\t// console.log('HIDE overlaybox: %s', GL_OB_S);\n\t\t// remove on layer\n\t\tthis.#GL_OB_S --;\n\t\t// if 0 or lower (overflow) hide it and\n\t\t// set zIndex to 0\n\t\tif (this.#GL_OB_S <= this.#GL_OB_BASE) {\n\t\t\tthis.#GL_OB_S = this.#GL_OB_BASE;\n\t\t\t$('#overlayBox').hide();\n\t\t\t$('#overlayBox').css('zIndex', this.#GL_OB_BASE);\n\t\t} else {\n\t\t\t// if OB_S > 0 then set new zIndex\n\t\t\t$('#overlayBox').css('zIndex', this.#GL_OB_S);\n\t\t}\n\t\tif (el_id) {\n\t\t\t$('#' + el_id).hide();\n\t\t\t$('#' + el_id).css('zIndex', 0);\n\t\t}\n\t\t// console.log('HIDE overlaybox NEW zIndex: %s', $('#overlayBox').css('zIndex'));\n\t}\n\n\t/**\n\t * only for single action box\n\t */\n\tclearCallActionBox()\n\t{\n\t\t$('#actionBox').html('');\n\t\t$('#actionBox').hide();\n\t\tthis.hideOverlayBoxLayers();\n\t}\n}\n\n\n// __END__\n", "/*\nDescription: Translation call\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { l10nTranslation };\nimport { isObject } from './JavaScriptHelpers.mjs';\n\nclass l10nTranslation {\n\n\t#i18n = {};\n\n\tconstructor(i18n) {\n\t\tthis.#i18n = i18n;\n\n\t}\n\t/**\n\t * uses the i18n object created in the translation template\n\t * that is filled from gettext in PHP\n\t * @param {String} string text to translate\n\t * @return {String} translated text (based on PHP selected language)\n\t */\n\t__(string)\n\t{\n\t\tif (typeof this.#i18n !== 'undefined' && isObject(this.#i18n) && this.#i18n[string]) {\n\t\t\treturn this.#i18n[string];\n\t\t} else {\n\t\t\treturn string;\n\t\t}\n\t}\n}\n\n// __END__\n", "/*\nDescription: Action Box handling\nDate: 2025/3/7\nCreator: Clemens Schwaighofer\n*/\n\nexport { ActionBox };\nimport { keyInObject, getObjectCount } from './JavaScriptHelpers.mjs';\nimport { exists } from './DomHelpers.mjs';\nimport { setCenter, getWindowSize } from './ResizingAndMove.mjs';\n\nclass ActionBox {\n\n\t// open overlay boxes counter for z-index\n\tzIndex = {\n\t\tbase: 100,\n\t\tmax: 110,\n\t\tindicator: 0,\n\t\tboxes: {},\n\t\tactive: [],\n\t\ttop: ''\n\t};\n\t// general action box storage\n\taction_box_storage = {};\n\t// set to 10 min (*60 for seconds, *1000 for microseconds)\n\taction_box_cache_timeout = 10 * 60 * 1000;\n\n\thec;\n\tl10n;\n\n\t/**\n\t * action box creator\n\t * @param {Object} hec HtmlElementCreator\n\t * @param {Object} l10n l10nTranslation\n\t */\n\tconstructor(hec, l10n)\n\t{\n\t\tthis.hec = hec;\n\t\tthis.l10n = l10n;\n\t}\n\n\t/**\n\t * Show an action box\n\t * @param {string} [target_id='actionBox'] where to attach content to, if not exists, create new\n\t * @param {string} [content=''] content to add to the box\n\t * @param {array} [action_box_css=[]] additional css elements for the action box\n\t * @param {number} [override=0] override size adjust\n\t * @param {number} [content_override=0] override content size adjust\n\t */\n\tshowFillActionBox(target_id = 'actionBox', content = '', action_box_css = [], override = 0, content_override = 0)\n\t{\n\t\t// fill content\n\t\tthis.fillActionBox(target_id, content, action_box_css);\n\t\t// show the box\n\t\tthis.showActionBox(target_id, override, content_override);\n\t}\n\n\t/**\n\t * Fill action box with content, create it if it does not existgs\n\t * @param {string} [target_id='actionBox'] where to attach content to, if not exists, create new\n\t * @param {string} [content=''] content to add to the box\n\t * @param {array} [action_box_css=[]] additional css elements for the action box\n\t */\n\tfillActionBox(target_id = 'actionBox', content = '', action_box_css = [])\n\t{\n\t\t// show action box, calc height + center\n\t\tif (!exists(target_id)) {\n\t\t\t// add at the bottom\n\t\t\t$('#mainContainer').after(\n\t\t\t\tthis.hec.phfo(this.hec.cel('div', target_id, '', ['actionBoxElement', 'hide'].concat(action_box_css)))\n\t\t\t);\n\t\t}\n\t\t// add the info box\n\t\t$('#' + target_id).html(content);\n\t}\n\n\t/**\n\t * Adjust the size of the action box\n\t * @param {string} [target_id='actionBox'] which actionBox to work on\n\t * @param {number} [override=0] override size adjust\n\t * @param {number} [content_override=0] override content size adjust\n\t */\n\tadjustActionBox(target_id = 'actionBox', override = 0, content_override = 0)\n\t{\n\t\t// adjust box size\n\t\tthis.adjustActionBoxHeight(target_id, override, content_override);\n\t\t// center the alert box\n\t\tsetCenter(target_id, true, true);\n\t}\n\n\t/**\n\t * hide any open action boxes and hide overlay\n\t */\n\thideAllActionBoxes()\n\t{\n\t\t// hide all action boxes that might exist\n\t\t$('#actionBox, div[id^=\"actionBox-\"].actionBoxElement').hide();\n\t\t// hideOverlayBoxLayers();\n\t\t$('#overlayBox').hide();\n\t}\n\n\t/**\n\t * hide action box, but do not clear content\n\t * DEPRECATED\n\t * @param {string} [target_id='actionBox']\n\t */\n\thideActionBox(target_id = 'actionBox')\n\t{\n\t\tthis.closeActionBoxFloat(target_id, false);\n\t}\n\n\t/**\n\t * Just show and adjust the box\n\t * DEPRECAED\n\t * @param {string} [target_id='actionBox'] which actionBox to work on\n\t * @param {number} [override=0] override size adjust\n\t * @param {number} [content_override=0] override content size adjust\n\t * @param {Boolean} [hide_all=false] if set to true, hide all other action boxes\n\t */\n\tshowActionBox(target_id = 'actionBox', override = 0, content_override = 0, hide_all = true)\n\t{\n\t\tthis.showActionBoxFloat(target_id, override, content_override, hide_all);\n\t}\n\n\t/**\n\t * close an action box with default clear content\n\t * for just hide use hideActionBox\n\t * DEPRECATED\n\t * @param {String} [target_id='actionBox'] which action box to close, default is set\n\t * @param {Boolean} [clean=true] if set to false will not remove html content, just hide\n\t */\n\tcloseActionBox(target_id = 'actionBox', clean = true)\n\t{\n\t\t// set the target/content ids\n\t\tthis.closeActionBoxFloat(target_id, clean);\n\t}\n\n\t/**\n\t * TODO: better stacked action box: OPEN\n\t * @param {string} [target_id='actionBox'] which actionBox to work on\n\t * @param {number} [override=0] override size adjust\n\t * @param {number} [content_override=0] override content size adjust\n\t * @param {boolean} [hide_all=false] if set to true, hide all other action boxes\n\t */\n\tshowActionBoxFloat(target_id = 'actionBox', override = 0, content_override = 0, hide_all = false)\n\t{\n\t\tif (hide_all === true) {\n\t\t\t// hide all action boxes if they are currently open\n\t\t\tthis.hideAllActionBoxes();\n\t\t}\n\t\t// if no box, created if\n\t\tif (!exists('overlayBox')) {\n\t\t\t$('body').prepend(this.hec.phfo(this.hec.cel('div', 'overlayBox', '', ['overlayBoxElement'])));\n\t\t\t$('#overlayBox').css('zIndex', this.zIndex.base);\n\t\t}\n\t\t// adjust zIndex so its above all other and set action box zindex +1\n\t\t$('#overlayBox').show();\n\t\tif (!keyInObject(target_id, this.zIndex.boxes)) {\n\t\t\tthis.zIndex.boxes[target_id] = this.zIndex.max;\n\t\t\t// increase by ten\n\t\t\tthis.zIndex.max += 10;\n\t\t} else if (this.zIndex.boxes[target_id] + 10 < this.zIndex.max) {\n\t\t\t// see if this is the highest level, if not move up and write no max zIndex\n\t\t\t// move it up to be the new top and move the others down\n\t\t\t// [loop, order by value]\n\t\t\t// current hack, increase max\n\t\t\tthis.zIndex.boxes[target_id] = this.zIndex.max;\n\t\t\tthis.zIndex.max += 10;\n\t\t}\n\t\t// make sure the overlayBox is one level below this\n\t\t// unless there is an active \"indicator\" index\n\t\tif (!this.zIndex.indicator) {\n\t\t\t$('#overlayBox').css('zIndex', this.zIndex.boxes[target_id] - 1);\n\t\t}\n\t\t$('#' + target_id).css('zIndex', this.zIndex.boxes[target_id]).show();\n\t\t// set target to this new level\n\t\t// @ts-ignore\n\t\tif (this.zIndex.active.indexOf(target_id) == -1) {\n\t\t\t// @ts-ignore\n\t\t\tthis.zIndex.active.push(target_id);\n\t\t}\n\t\tthis.zIndex.top = target_id;\n\t\t// adjust size\n\t\tthis.adjustActionBox(target_id, override, content_override);\n\t}\n\n\t/**\n\t * TODO: better stacked action box: CLOSE\n\t * @param {String} [target_id='actionBox'] which action box to close, default is set\n\t * @param {Boolean} [clean=true] if set to false will not remove html content, just hide\n\t */\n\tcloseActionBoxFloat(target_id = 'actionBox', clean = true)\n\t{\n\t\t// do nothing if this does not exist\n\t\tif (!exists(target_id)) {\n\t\t\treturn;\n\t\t}\n\t\t// clear storage object\n\t\tif (\n\t\t\tkeyInObject(target_id, this.action_box_storage) && clean === true\n\t\t) {\n\t\t\tthis.action_box_storage[target_id] = {};\n\t\t}\n\t\tif (clean === true) {\n\t\t\t$('#' + target_id).html('');\n\t\t}\n\t\t$('#' + target_id).hide();\n\t\t// remove from active list\n\t\t// @ts-ignore\n\t\tlet idx = this.zIndex.active.indexOf(target_id);\n\t\tthis.zIndex.active.splice(idx, 1);\n\t\t// do we have any visible action boxes.\n\t\t// find the highest zIndex and set overlayBox to this -1\n\t\t// @ts-ignore\n\t\tlet visible_zIndexes = $('#actionBox:visible, div[id^=\"actionBox-\"].actionBoxElement:visible').map((i, el) => ({\n\t\t\tid: el.id,\n\t\t\tzIndex: $('#' + el.id).css('zIndex')\n\t\t})).get();\n\t\tif (visible_zIndexes.length > 0) {\n\t\t\tlet max_zIndex = 0;\n\t\t\tlet max_el_id = '';\n\t\t\tfor (let zIndex_el of visible_zIndexes) {\n\t\t\t\tif (parseInt(zIndex_el.zIndex) > max_zIndex) {\n\t\t\t\t\tmax_zIndex = parseInt(zIndex_el.zIndex);\n\t\t\t\t\tmax_el_id = zIndex_el.id;\n\t\t\t\t}\n\t\t\t}\n\t\t\t$('#overlayBox').css('zIndex', max_zIndex - 1);\n\t\t\tthis.zIndex.top = max_el_id;\n\t\t} else {\n\t\t\t$('#overlayBox').hide();\n\t\t}\n\t}\n\n\t/**\n\t * create a new action box and fill it with basic elements\n\t * @param {String} [target_id='actionBox']\n\t * @param {String} [title='']\n\t * @param {Object} [contents={}]\n\t * @param {Object} [headers={}]\n\t * @param {Boolean} [show_close=true]\n\t * @param {Object} [settings={}] Optional settings, eg style sheets\n\t */\n\tcreateActionBox(\n\t\ttarget_id = 'actionBox',\n\t\ttitle = '',\n\t\tcontents = {},\n\t\theaders = {},\n\t\tsettings = {},\n\t\tshow_close = true\n\t) {\n\t\tif (!keyInObject(target_id, this.action_box_storage)) {\n\t\t\tthis.action_box_storage[target_id] = {};\n\t\t}\n\t\t// settings can have the following\n\t\t// : header_css:[]\n\t\t// : action_box_css:[]\n\t\tlet header_css = [];\n\t\tif (keyInObject('header_css', settings)) {\n\t\t\theader_css = settings.header_css;\n\t\t}\n\t\tlet action_box_css = [];\n\t\tif (keyInObject('action_box_css', settings)) {\n\t\t\taction_box_css = settings.action_box_css;\n\t\t}\n\t\tlet elements = [];\n\t\t// add title + close button to actionBox\n\t\telements.push(this.hec.phfo(\n\t\t\tthis.hec.aelx(this.hec.cel('div', target_id + '_title', '', ['actionBoxTitle', 'flx-spbt'].concat(header_css)),\n\t\t\t\t...show_close === true ? [\n\t\t\t\t\t// title\n\t\t\t\t\tthis.hec.cel('div', '', title, ['fs-b', 'w-80']),\n\t\t\t\t\t// close button\n\t\t\t\t\tthis.hec.aelx(this.hec.cel('div', target_id + '_title_close_button', '', ['w-20', 'tar']),\n\t\t\t\t\t\tthis.hec.cel('input', target_id + '_title_close', '', ['button-close', 'fs-s'],\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 'button',\n\t\t\t\t\t\t\t\tvalue: this.l10n.__('Close'),\n\t\t\t\t\t\t\t\tOnClick: 'closeActionBox(\\'' + target_id + '\\', false);'\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t] : [\n\t\t\t\t\tthis.hec.cel('div', '', title, ['fs-b', 'w-100'])\n\t\t\t\t]\n\t\t\t)\n\t\t));\n\t\t// if we have header content, add that here\n\t\tif (getObjectCount(headers) > 0) {\n\t\t\t// if the element has an entry called \"raw_string\" then this does not need to be converted\n\t\t\tif (keyInObject('raw_string', headers)) {\n\t\t\t\telements.push(headers.raw_string);\n\t\t\t} else {\n\t\t\t\telements.push(this.hec.phfo(headers));\n\t\t\t}\n\t\t}\n\t\t// main content part (this should NOT be empty), if empty, add empty _content block\n\t\tif (getObjectCount(contents) > 0) {\n\t\t\t// if the element has an entry called \"raw_string\" then this does not need to be converted\n\t\t\tif (keyInObject('raw_string', contents)) {\n\t\t\t\telements.push(contents.raw_string);\n\t\t\t} else {\n\t\t\t\telements.push(this.hec.phfo(contents));\n\t\t\t}\n\t\t} else {\n\t\t\telements.push(this.hec.phfo(this.hec.cel('div', target_id + '_content', '', [])));\n\t\t}\n\t\t// footer clear call\n\t\telements.push(this.hec.phfo(\n\t\t\tthis.hec.aelx(this.hec.cel('div', target_id + '_footer', '', ['pd-5', 'flx-spbt']),\n\t\t\t\t...show_close === true ? [\n\t\t\t\t\t// dummy spacer\n\t\t\t\t\tthis.hec.cel('div', '', '', ['fs-b', 'w-80']),\n\t\t\t\t\t// close button\n\t\t\t\t\tthis.hec.aelx(this.hec.cel('div', target_id + '_footer_close_button', '', ['tar', 'w-20']),\n\t\t\t\t\t\tthis.hec.cel('input', target_id + '_footer_close', '', ['button-close', 'fs-s'],\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 'button',\n\t\t\t\t\t\t\t\tvalue: this.l10n.__('Close'),\n\t\t\t\t\t\t\t\tOnClick: 'closeActionBox(\\'' + target_id + '\\', false);'\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t] : [\n\t\t\t\t\tthis.hec.cel('div', '', '', ['fs-b', 'w-100'])\n\t\t\t\t]\n\t\t\t)\n\t\t));\n\t\telements.push(this.hec.phfo(this.hec.cel('input', target_id + '-cache_time', '', [], {\n\t\t\ttype: 'hidden',\n\t\t\tvalue: Date.now()\n\t\t})));\n\t\tthis.fillActionBox(target_id, elements.join(''), action_box_css);\n\t}\n\n\t/**\n\t * adjusts the action box height based on content and window height of browser\n\t * TODO: border on outside/and other margin things need to be added in overall adjustment\n\t * @param {String} [target_id='actionBox'] target id, if not set, fall back to default\n\t * @param {Number} [override=0] override value to add to the actionBox height\n\t * @param {Number} [content_override=0] override the value from _content block if it exists\n\t */\n\tadjustActionBoxHeight(target_id = 'actionBox', override = 0, content_override = 0)\n\t{\n\t\tvar new_height = 0;\n\t\tvar dim = {};\n\t\tvar abc_dim = {};\n\t\tvar content_id = '';\n\t\t// make sure it is a number\n\t\tif (isNaN(override)) {\n\t\t\toverride = 0;\n\t\t}\n\t\tif (isNaN(content_override)) {\n\t\t\tcontent_override = 0;\n\t\t}\n\t\t// set the target/content ids\n\t\tswitch (target_id) {\n\t\t\tcase 'actionBox':\n\t\t\t\tcontent_id = 'action_box';\n\t\t\t\tbreak;\n\t\t\tcase 'actionBoxSub':\n\t\t\t\tcontent_id ='action_box_sub';\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tcontent_id = target_id;\n\t\t\t\tbreak;\n\t\t}\n\t\t// first remove any height, left, top style entris from target and content blocks\n\t\t// @ts-ignore\n\t\t$.each([target_id, content_id + '_content'], function(i, v) {\n\t\t\t$('#' + v).css({\n\t\t\t\t'height': '',\n\t\t\t\t'width': ''\n\t\t\t});\n\t\t});\n\t\tif (exists(content_id + '_title')) {\n\t\t\tdim.height = $('#' + content_id + '_title').outerHeight();\n\t\t\tconsole.log('Target: %s, Action box Title: %s', target_id, dim.height);\n\t\t\tnew_height += dim.height ?? 0;\n\t\t}\n\t\tif (exists(content_id + '_header')) {\n\t\t\tdim.height = $('#' + content_id + '_header').outerHeight();\n\t\t\tconsole.log('Target: %s, Action box Header: %s', target_id, dim.height);\n\t\t\tnew_height += dim.height ?? 0;\n\t\t}\n\t\tif (exists(content_id + '_content')) {\n\t\t\tif (content_override > 0) {\n\t\t\t\tconsole.log('Target: %s, Action box Content Override: %s', target_id, content_override);\n\t\t\t\tnew_height += content_override;\n\t\t\t} else {\n\t\t\t\tabc_dim.height = $('#' + content_id + '_content').outerHeight();\n\t\t\t\tconsole.log('Target: %s, Action box Content: %s', target_id, abc_dim.height);\n\t\t\t\tnew_height += abc_dim.height ?? 0;\n\t\t\t}\n\t\t}\n\t\t// always there sets\n\t\tif (exists(content_id + '_footer')) {\n\t\t\tdim.height = $('#' + content_id + '_footer').outerHeight();\n\t\t\tconsole.log('Target: %s, Action box Footer: %s', target_id, dim.height);\n\t\t\tnew_height += dim.height ?? 0;\n\t\t}\n\t\t// get difference for the rest from outer box\n\t\t// console.log('Target: %s, Action box outer: %s, Content: %s, New: %s', target_id, $('#' + target_id).outerHeight(), $('#' + content_id + '_content').outerHeight(), new_height);\n\t\t// new_height += ($('#' + target_id).outerHeight() - new_height) + override;\n\t\tnew_height += override;\n\t\t// get border width top-bottom from action Box, we need to remove this from the final height\n\t\t// console.log('Target: %s, Border top: %s', target_id, $('#' + target_id).css('border-top-width'));\n\t\t// get window size and check if content is bigger\n\t\tvar viewport = getWindowSize();\n\t\tif (new_height >= viewport.height) {\n\t\t\t// resize the action box content and set overflow [of-s-y]\n\t\t\tif (exists(content_id + '_content')) {\n\t\t\t\tif (!$('#' + content_id + '_content').hasClass('of-s-y')) {\n\t\t\t\t\t$('#' + content_id + '_content').addClass('of-s-y');\n\t\t\t\t}\n\t\t\t}\n\t\t\tconsole.log('Target: %s, Viewport: %s, ActionBox (NH): %s, ABcontent: %s, ABouter: %s', target_id, viewport.height, new_height, abc_dim.height, $('#' + target_id).outerHeight());\n\t\t\t// the height off window - all - action box gives new action box height\n\t\t\tvar m_height = viewport.height - (new_height - (abc_dim.height ?? 0));\n\t\t\tconsole.log('Target: %s, New ABcontent: %s', target_id, m_height);\n\t\t\t$('#' + content_id + '_content').css('height', m_height + 'px');\n\t\t\tnew_height = new_height - (abc_dim.height ?? 0) + m_height;\n\t\t\tconsole.log('Target: %s, New Hight: %s', target_id, new_height);\n\t\t} else {\n\t\t\t// if size ok, check if overflow scoll is set, remove it\n\t\t\tif (exists(content_id + '_content')) {\n\t\t\t\tif ($('#' + content_id + '_content').hasClass('of-s-y')) {\n\t\t\t\t\t$('#' + content_id + '_content').removeClass('of-s-y');\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconsole.log('Target: %s, Action Box new height: %s px (override %s px, content override %s px), window height: %s px, Visible Height: %s px', target_id, new_height, override, content_override, viewport.height, $('#' + content_id).outerHeight());\n\t\t// adjust height\n\t\t$('#' + target_id).css('height', new_height + 'px');\n\t}\n}\n\n// __EMD__\n", "/*\nDescription: Login access and menu\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { LoginNavMenu };\nimport { isObject, getObjectCount } from './JavaScriptHelpers.mjs';\nimport { exists } from './DomHelpers.mjs';\n\nclass LoginNavMenu {\n\n\thec;\n\tl10n;\n\n\t/**\n\t * action box creator\n\t * @param {Object} hec HtmlElementCreator\n\t * @param {Object} l10n l10nTranslation\n\t */\n\tconstructor(hec, l10n)\n\t{\n\t\tthis.hec = hec;\n\t\tthis.l10n = l10n;\n\t}\n\n\t/**\n\t * create login string and logout button elements\n\t * @param {String} login_string the login string to show on the left\n\t * @param {String} [header_id='mainHeader'] the target for the main element block\n\t * if not set mainHeader is assumed\n\t * this is the target div for the \"loginRow\"\n\t */\n\tcreateLoginRow(login_string, header_id = 'mainHeader')\n\t{\n\t\t// if header does not exist, we do nothing\n\t\tif (exists(header_id)) {\n\t\t\t// that row must exist already, if not it must be the first in the \"mainHeader\"\n\t\t\tif (!exists('loginRow')) {\n\t\t\t\t$('#' + header_id).html(this.hec.phfo(this.hec.cel('div', 'loginRow', '', ['loginRow', 'flx-spbt'])));\n\t\t\t}\n\t\t\t// clear out just in case for first entry\n\t\t\t// fill with div name & login/logout button\n\t\t\t$('#loginRow').html(this.hec.phfo(this.hec.cel('div', 'loginRow-name', login_string)));\n\t\t\t$('#loginRow').append(this.hec.phfo(this.hec.cel('div', 'loginRow-info', '')));\n\t\t\t$('#loginRow').append(this.hec.phfo(\n\t\t\t\tthis.hec.aelx(\n\t\t\t\t\t// outer div\n\t\t\t\t\tthis.hec.cel('div', 'loginRow-logout'),\n\t\t\t\t\t// inner element\n\t\t\t\t\tthis.hec.cel('input', 'logout', '', [], {\n\t\t\t\t\t\tvalue: this.l10n.__('Logout'),\n\t\t\t\t\t\ttype: 'button',\n\t\t\t\t\t\tonClick: 'loginLogout()'\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t));\n\t\t}\n\t}\n\n\t/**\n\t * create the top nav menu that switches physical between pages\n\t * (edit access data based)\n\t * @param {Object} nav_menu the built nav menu with highlight info\n\t * @param {String} [header_id='mainHeader'] the target for the main element block\n\t * if not set mainHeader is assumed\n\t * this is the target div for the \"menuRow\"\n\t */\n\tcreateNavMenu(nav_menu, header_id = 'mainHeader')\n\t{\n\t\t// must be an object\n\t\tif (isObject(nav_menu) && getObjectCount(nav_menu) > 1) {\n\t\t\t// do we have more than one entry, if not, do not show (single page)\n\t\t\tif (!exists('menuRow')) {\n\t\t\t\t$('#' + header_id).html(this.hec.phfo(this.hec.cel('div', 'menuRow', '', ['menuRow', 'flx-s'])));\n\t\t\t}\n\t\t\tvar content = [];\n\t\t\t$.each(nav_menu, function(key, item) {\n\t\t\t\t// key is number\n\t\t\t\t// item is object with entries\n\t\t\t\tif (key != 0) {\n\t\t\t\t\tcontent.push(this.hec.phfo(this.hec.cel('div', '', '·', ['pd-2'])));\n\t\t\t\t}\n\t\t\t\t// ignore item.popup for now\n\t\t\t\tif (item.enabled) {\n\t\t\t\t\t// set selected based on window.location.href as the php set will not work\n\t\t\t\t\tif (window.location.href.indexOf(item.url) != -1) {\n\t\t\t\t\t\titem.selected = 1;\n\t\t\t\t\t}\n\t\t\t\t\t// create the entry\n\t\t\t\t\tcontent.push(this.hec.phfo(\n\t\t\t\t\t\tthis.hec.aelx(\n\t\t\t\t\t\t\tthis.hec.cel('div'),\n\t\t\t\t\t\t\tthis.hec.cel('a', '', item.name, ['pd-2'].concat(item.selected ? 'highlight': ''), {\n\t\t\t\t\t\t\t\thref: item.url\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t)\n\t\t\t\t\t));\n\t\t\t\t}\n\t\t\t});\n\t\t\t$('#menuRow').html(content.join(''));\n\t\t} else {\n\t\t\t$('#menuRow').hide();\n\t\t}\n\t}\n\n}\n\n// __END__\n", "/*\n * general edit javascript\n * former name: edit.jq.js\n * This is the jquery version\n * NOTE: jquey parts will be deprecated\n*/\n\nimport {\n\terrorCatch as _errorCatch,\n\tisFunction as _isFunction,\n\texecuteFunctionByName as _executeFunctionByName,\n\tisObject as _isObject,\n\tgetObjectCount as _getObjectCount,\n\tkeyInObject as _keyInObject,\n\tgetKeyByValue as _getKeyByValue,\n\tvalueInObject as _valueInObject,\n\tdeepCopyFunction as _deepCopyFunction\n} from './utils/JavaScriptHelpers.mjs';\nimport {\n\tescapeHtml as _escapeHtml,\n\tunescapeHtml as _unescapeHtml,\n\thtml_options as _html_options,\n\thtml_options_block as _html_options_block,\n\thtml_options_refill as _html_options_refill\n} from './utils/HtmlHelpers.mjs';\nimport {\n\tloadEl as _loadEl,\n\tpop as _pop,\n\texpandTA as _expandTA,\n\texists as _exists\n} from './utils/DomHelpers.mjs';\nimport {\n\tdec2hex as _dec2hex,\n\tgetRandomIntInclusive as _getRandomIntInclusive,\n\troundPrecision as _roundPrecision\n} from './utils/MathHelpers.mjs';\nimport {\n\tformatString as _formatString,\n\tnumberWithCommas as _numberWithCommas,\n\tconvertLBtoBR as _convertLBtoBR\n} from './utils/StringHelpers.mjs';\nimport {\n\tgetTimestamp as _getTimestamp\n} from './utils/DateTimeHelpers.mjs';\nimport {\n\tgenerateId as _generateId,\n\trandomIdF as _randomIdF,\n} from './utils/UniqIdGenerators.mjs';\nimport {\n\tgetWindowSize as _getWindowSize,\n\tgetScrollOffset as _getScrollOffset,\n\tgetScrollOffsetOpener as _getScrollOffsetOpener,\n\tsetCenter as _setCenter,\n\tgoToPos as _goToPos,\n\tgoTo as _goTo\n} from './utils/ResizingAndMove.mjs';\nimport {\n\tformatBytes as _formatBytes,\n\tformatBytesLong as _formatBytesLong,\n\tstringByteFormat as _stringByteFormat\n} from './utils/FormatBytes.mjs';\nimport {\n\tparseQueryString as _parseQueryString,\n\tgetQueryStringParam as _getQueryStringParam\n} from './utils/UrlParser.mjs';\nimport {\n\tloginLogout as _loginLogout,\n} from './utils/LoginLogout.mjs';\nimport {\n\tActionIndicatorOverlayBox,\n\tactionIndicator as _actionIndicator,\n\tactionIndicatorShow as _actionIndicatorShow,\n\tactionIndicatorHide as _actionIndicatorHide,\n\toverlayBoxShow as _overlayBoxShow,\n\toverlayBoxHide as _overlayBoxHide,\n\tsetOverlayBox as _setOverlayBox,\n\thideOverlayBox as _hideOverlayBox,\n\tClearCall as _ClearCall\n} from './utils/ActionIndicatorOverlayBox.mjs';\nimport { l10nTranslation } from './utils/l10nTranslation.mjs';\nimport { HtmlElementCreator } from './utils/HtmlElementCreator.mjs';\nimport { ActionBox } from './utils/ActionBox.mjs';\nimport { LoginNavMenu } from './utils/LoginNavMenu.mjs';\n\nlet aiob = new ActionIndicatorOverlayBox();\nlet hec = new HtmlElementCreator();\n// if ( undef === \"undefined\") {\n// @ts-ignore\n// eslint-disable-next-line no-undef\nlet l10n = new l10nTranslation(typeof i18n === \"undefined\" ? {} : i18n);\nlet ab = new ActionBox(hec, l10n);\nlet lnm = new LoginNavMenu(hec, l10n);\n\n// MARK: deprecated String/Number override\n\n/**\n * simple sprintf formater for replace\n * usage: \"{0} is cool, {1} is not\".format(\"Alpha\", \"Beta\");\n * First, checks if it isn't implemented yet.\n * @param {String} String.prototype.format string with elements to be replaced\n * @return {String} Formated string\n * @deprecated StringHelpers.formatString\n */\n// @ts-ignore\nif (!String.prototype.format) {\n\t// @ts-ignore\n\tString.prototype.format = function()\n\t{\n\t\tconsole.error('[DEPRECATED] use StringHelpers.formatString');\n\t\t// @ts-ignore\n\t\treturn _formatString(this, arguments);\n\t};\n}\n\n/**\n * round to digits (float)\n * @param {Number} Number.prototype.round Float type number to round\n * @param {Number} prec Precision to round to\n * @return {Float} Rounded number\n * @deprecated use MathHelpers.roundPrecision\n */\n// @ts-ignore\nif (Number.prototype.round) {\n\t// @ts-ignore\n\tNumber.prototype.round = function (prec) {\n\t\tconsole.error('[DEPRECATED] use MathHelpers.roundPrecision');\n\t\t// @ts-ignore\n\t\treturn _roundPrecision(this, prec);\n\t};\n}\n\n/**\n * escape HTML string\n * @param {String} String.prototype.escapeHTML HTML data string to be escaped\n * @return {String} escaped string\n * @deprecated use HtmlHelpers.escapeHtml\n */\n// @ts-ignore\nif (!String.prototype.escapeHTML) {\n\t// @ts-ignore\n\tString.prototype.escapeHTML = function() {\n\t\tconsole.error('[DEPRECATED] use HtmlHelpers.escapeHtml');\n\t\t// @ts-ignore\n\t\treturn _escapeHtml(this);\n\t};\n}\n\n/**\n * unescape a HTML encoded string\n * @param {String} String.prototype.unescapeHTML data with escaped entries\n * @return {String} HTML formated string\n * @deprecated use HtmlHelpers.unescapeHtml\n */\n// @ts-ignore\nif (!String.prototype.unescapeHTML) {\n\t// @ts-ignore\n\tString.prototype.unescapeHTML = function() {\n\t\tconsole.error('[DEPRECATED] use HtmlHelpers.unescapeHtml');\n\t\t// @ts-ignore\n\t\treturn _unescapeHtml(this);\n\t};\n}\n\n// MARK: general collection\n\n/**\n *\n * @param {String} string\n * @returns {String}\n */\n// @ts-ignore\nfunction escapeHtml(string) // eslint-disable-line no-unused-vars\n{\n\treturn _escapeHtml(string);\n}\n\n/**\n * round to digits (float)\n * @param {Number} number Float type number to round\n * @param {Number} prec Precision to round to\n * @return {Number} Rounded number\n */\n// @ts-ignore\nfunction roundPrecision(number, prec) // eslint-disable-line no-unused-vars\n{\n\treturn _roundPrecision(number, prec);\n}\n\n/**\n * simple sprintf formater for replace\n * usage: \"{0} is cool, {1} is not\".format(\"Alpha\", \"Beta\");\n * First, checks if it isn't implemented yet.\n * @param {String} string String with elements to be replaced\n * @return {String} Formated string\n * @deprecated StringHelpe\n */\n// @ts-ignore\nfunction formatString(string, ...args) // eslint-disable-line no-unused-vars\n{\n\treturn _formatString(string, ...args);\n}\n\n/**\n *\n * @param {String} string\n * @returns {String}\n */\n// @ts-ignore\nfunction unescapeHtml(string) // eslint-disable-line no-unused-vars\n{\n\treturn _unescapeHtml(string);\n}\n\n/**\n * Gets html element or throws an error\n * @param {string} el_id Element ID to get\n * @returns {HTMLElement}\n * @throws Error\n */\n// @ts-ignore\nfunction loadEl(el_id) // eslint-disable-line no-unused-vars\n{\n\treturn _loadEl(el_id);\n}\n\n/**\n * opens a pop_ window with winName and given features (string)\n * @param {String} theURL the url\n * @param {String} winName window name\n * @param {Object} features pop_ features\n */\n// @ts-ignore\nfunction pop(theURL, winName, features) // eslint-disable-line no-unused-vars\n{\n\t_pop(theURL, winName, features);\n}\n\n/**\n * automatically resize a text area based on the amount of lines in it\n * @param {string} ta_id element id\n */\n// @ts-ignore\nfunction expandTA(ta_id) // eslint-disable-line no-unused-vars\n{\n\t_expandTA(ta_id);\n}\n\n/**\n * wrapper to get the real window size for the current browser window\n * @return {Object} object with width/height\n */\n// @ts-ignore\nfunction getWindowSize() // eslint-disable-line no-unused-vars\n{\n\treturn _getWindowSize();\n}\n\n/**\n * wrapper to get the correct scroll offset\n * @return {Object} object with x/y px\n */\n// @ts-ignore\nfunction getScrollOffset() // eslint-disable-line no-unused-vars\n{\n\treturn _getScrollOffset();\n}\n\n/**\n * wrapper to get the correct scroll offset for opener page (from pop_)\n * @return {Object} object with x/y px\n */\n// @ts-ignore\nfunction getScrollOffsetOpener() // eslint-disable-line no-unused-vars\n{\n\treturn _getScrollOffsetOpener();\n}\n\n/**\n * centers div to current window size middle\n * @param {String} id element to center\n * @param {Boolean} left if true centers to the middle from the left\n * @param {Boolean} top if true centers to the middle from the top\n */\n// @ts-ignore\nfunction setCenter(id, left, top) // eslint-disable-line no-unused-vars\n{\n\t_setCenter(id, left, top);\n}\n\n/**\n * goes to an element id position\n * @param {String} element element id to move to\n * @param {Number} [offset=0] offset from top, default is 0 (px)\n * @param {Number} [duration=500] animation time, default 500ms\n * @param {String} [base='body,html'] base element for offset scroll\n */\n// @ts-ignore\nfunction goToPos(element, offset = 0, duration = 500, base = 'body,html') // eslint-disable-line no-unused-vars\n{\n\t_goToPos(element, offset, duration, base);\n}\n\n/**\n * go to element, scroll\n * non jquery\n * @param {string} target\n*/\n// @ts-ignore\nfunction goTo(target) // eslint-disable-line no-unused-vars\n{\n\t_goTo(target);\n}\n\n/**\n * uses the i18n object created in the translation template\n * that is filled from gettext in PHP\n * @param {String} string text to translate\n * @return {String} translated text (based on PHP selected language)\n */\n// @ts-ignore\nfunction __(string) // eslint-disable-line no-unused-vars\n{\n\treturn l10n.__(string);\n}\n\n/**\n * formats flat number 123456 to 123,456\n * @param {Number} x number to be formated\n * @return {String} formatted with , in thousands\n */\n// @ts-ignore\nfunction numberWithCommas(x) // eslint-disable-line no-unused-vars\n{\n\treturn _numberWithCommas(x);\n}\n\n/**\n * converts line breaks to br\n * @param {String} string any string\n * @return {String} string with
\n */\n// @ts-ignore\nfunction convertLBtoBR(string) // eslint-disable-line no-unused-vars\n{\n\treturn _convertLBtoBR(string);\n}\n\n/**\n * returns current timestamp (unix timestamp)\n * @return {Number} timestamp (in milliseconds)\n */\n// @ts-ignore\nfunction getTimestamp() // eslint-disable-line no-unused-vars\n{\n\treturn _getTimestamp();\n}\n\n/**\n * dec2hex :: Integer -> String\n * i.e. 0-255 -> '00'-'ff'\n * @param {Number} dec decimal string\n * @return {String} hex encdoded number\n */\n// @ts-ignore\nfunction dec2hex(dec) // eslint-disable-line no-unused-vars\n{\n\treturn _dec2hex(dec);\n}\n\n/**\n * generateId :: Integer -> String\n * only works on mondern browsers\n * @param {Number} len length of unique id string\n * @return {String} random string in length of len\n */\n// @ts-ignore\nfunction generateId(len) // eslint-disable-line no-unused-vars\n{\n\treturn _generateId(len);\n}\n\n/**\n * creates a pseudo random string of 10 characters\n * works on all browsers\n * after many runs it will create d_licates\n * @return {String} not true random string\n */\n// @ts-ignore\nfunction randomIdF() // eslint-disable-line no-unused-vars\n{\n\treturn _randomIdF();\n}\n\n/**\n * generate a number between min/max\n * with min/max inclusive.\n * eg: 1,5 will create a number ranging from 1 o 5\n * @param {Number} min minimum int number inclusive\n * @param {Number} max maximumg int number inclusive\n * @return {Number} Random number\n */\n// @ts-ignore\nfunction getRandomIntInclusive(min, max) // eslint-disable-line no-unused-vars\n{\n\treturn _getRandomIntInclusive(min, max);\n}\n\n/**\n * check if name is a function\n * @param {string} name Name of function to check if exists\n * @return {Boolean} true/false\n */\n// @ts-ignore\nfunction isFunction(name) // eslint-disable-line no-unused-vars\n{\n\treturn _isFunction(name);\n}\n\n/**\n * call a function by its string name\n * https://stackoverflow.com/a/359910\n * example: executeFunctionByName(\"My.Namespace.functionName\", window, arguments);\n * @param {string} functionName The function name or namespace + function\n * @param {any} context context (window or first namespace)\n * hidden next are all the arguments\n * @return {any} Return values from functon\n */\n// @ts-ignore\nfunction executeFunctionByName(functionName, context) // eslint-disable-line no-unused-vars\n{\n\treturn _executeFunctionByName(functionName, context);\n}\n\n/**\n * checks if a variable is an object\n * @param {any} val possible object\n * @return {Boolean} true/false if it is an object or not\n */\n// @ts-ignore\nfunction isObject(val) // eslint-disable-line no-unused-vars\n{\n\treturn _isObject(val);\n}\n\n/**\n * get the length of an object (entries)\n * @param {Object} object object to check\n * @return {Number} number of entry\n */\n// @ts-ignore\nfunction getObjectCount(object) // eslint-disable-line no-unused-vars\n{\n\treturn _getObjectCount(object);\n}\n\n/**\n * checks if a key exists in a given object\n * @param {String} key key name\n * @param {Object} object object to search key in\n * @return {Boolean} true/false if key exists in object\n */\n// @ts-ignore\nfunction keyInObject(key, object) // eslint-disable-line no-unused-vars\n{\n\treturn _keyInObject(key, object);\n}\n\n/**\n * returns matching key of value\n * @param {Object} object object to search value in\n * @param {any} value any value (String, Number, etc)\n * @return {String} the key found for the first matching value\n */\n// @ts-ignore\nfunction getKeyByValue(object, value) // eslint-disable-line no-unused-vars\n{\n\treturn _getKeyByValue(object, value);\n}\n\n/**\n * returns true if value is found in object with a key\n * @param {Object} object object to search value in\n * @param {any} value any value (String, Number, etc)\n * @return {Boolean} true on value found, false on not found\n */\n// @ts-ignore\nfunction valueInObject(object, value) // eslint-disable-line no-unused-vars\n{\n\treturn _valueInObject(object, value);\n}\n\n/**\n * true deep copy for Javascript objects\n * if Object.assign({}, obj) is not working (shallow)\n * or if JSON.parse(JSON.stringify(obj)) is failing\n * @param {Object} inObject Object to copy\n * @return {Object} Copied Object\n */\n// @ts-ignore\nfunction deepCopyFunction(inObject) // eslint-disable-line no-unused-vars\n{\n\treturn _deepCopyFunction(inObject);\n}\n\n/**\n * checks if a DOM element actually exists\n * @param {String} id Element id to check for\n * @return {Boolean} true if element exists, false on failure\n */\n// @ts-ignore\nfunction exists(id) // eslint-disable-line no-unused-vars\n{\n\treturn _exists(id);\n}\n\n/**\n * converts a int number into bytes with prefix in two decimals precision\n * currently precision is fixed, if dynamic needs check for max/min precision\n * @param {Number} bytes bytes in int\n * @return {String} string in GB/MB/KB\n */\n// @ts-ignore\nfunction formatBytes(bytes) // eslint-disable-line no-unused-vars\n{\n\treturn _formatBytes(bytes);\n}\n\n/**\n * like formatBytes, but returns bytes for <1KB and not 0.n KB\n * @param {Number} bytes bytes in int\n * @return {String} string in GB/MB/KB\n */\n// @ts-ignore\nfunction formatBytesLong(bytes) // eslint-disable-line no-unused-vars\n{\n\treturn _formatBytesLong(bytes);\n}\n\n/**\n * Convert a string with B/K/M/etc into a byte number\n * @param {String|Number} bytes Any string with B/K/M/etc\n * @return {String|Number} A byte number, or original string as is\n */\n// @ts-ignore\nfunction stringByteFormat(bytes) // eslint-disable-line no-unused-vars\n{\n\treturn _stringByteFormat(bytes);\n}\n\n/**\n * prints out error messages based on data available from the browser\n * @param {Object} err error from try/catch block\n */\n// @ts-ignore\nfunction errorCatch(err) // eslint-disable-line no-unused-vars\n{\n\t_errorCatch(err);\n}\n\n// MARK: ActionIndicatorOverlayBoxLegacy\n\n/*************************************************************\n * OLD action indicator and overlay boxes calls\n * DO NOT USE\n * actionIndicator -> showActionIndicator\n * actionIndicator -> hideActionIndicator\n * actionIndicatorShow -> showActionIndicator\n * actionIndicatorHide -> hideActionIndicator\n * overlayBoxShow -> showOverlayBoxLayers\n * overlayBoxHide -> hideOverlayBoxLayers\n * setOverlayBox -> showOverlayBoxLayers\n * hideOverlayBox -> hideOverlayBoxLayers\n * ClearCall -> ClearCallActionBox\n * ***********************************************************/\n\n/**\n * show or hide the \"do\" overlay\n * @param {String} loc location name for action indicator\n * default empty. for console.log\n * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block\n */\n// @ts-ignore\nfunction actionIndicator(loc, overlay = true) // eslint-disable-line no-unused-vars\n{\n\t_actionIndicator(loc, overlay);\n}\n\n/**\n * explicit show for action Indicator\n * instead of automatically show or hide, do on command show\n * @param {String} loc location name for action indicator\n * default empty. for console.log\n * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block\n */\n// @ts-ignore\nfunction actionIndicatorShow(loc, overlay = true) // eslint-disable-line no-unused-vars\n{\n\t_actionIndicatorShow(loc, overlay);\n}\n\n/**\n * explicit hide for action Indicator\n * instead of automatically show or hide, do on command hide\n * @param {String} loc location name for action indicator\n * default empty. for console.log\n * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block\n */\n// @ts-ignore\nfunction actionIndicatorHide(loc, overlay = true) // eslint-disable-line no-unused-vars\n{\n\t_actionIndicatorHide(loc, overlay);\n}\n\n/**\n * shows the overlay box or if already visible, bumps the zIndex to 100\n */\n// @ts-ignore\nfunction overlayBoxShow() // eslint-disable-line no-unused-vars\n{\n\t_overlayBoxShow();\n}\n\n/**\n * hides the overlay box or if zIndex is 100 bumps it down to previous level\n */\n// @ts-ignore\nfunction overlayBoxHide() // eslint-disable-line no-unused-vars\n{\n\t_overlayBoxHide();\n}\n\n/**\n * position the overlay block box and shows it\n */\n// @ts-ignore\nfunction setOverlayBox() // eslint-disable-line no-unused-vars\n{\n\t_setOverlayBox();\n}\n\n/**\n * opposite of set, always hides overlay box\n */\n// @ts-ignore\nfunction hideOverlayBox() // eslint-disable-line no-unused-vars\n{\n\t_hideOverlayBox();\n}\n\n/**\n * the abort call, clears the action box and hides it and the overlay box\n */\n// @ts-ignore\nfunction ClearCall() // eslint-disable-line no-unused-vars\n{\n\t_ClearCall();\n}\n\n// MARK: ActionIndicatorOverlayBox\n\n/*************************************************************\n * NEW action indicator and overlay box calls\n * USE THIS\n * ***********************************************************/\n\n/**\n * show action indicator\n * - checks if not existing and add\n * - only shows if not visible (else ignore)\n * - overlaybox check is called and shown on a fixzed\n * zIndex of 1000\n * - indicator is page centered\n * @param {String} loc ID string, only used for console log\n */\n// @ts-ignore\nfunction showActionIndicator(loc) // eslint-disable-line no-unused-vars\n{\n\taiob.showActionIndicator(loc);\n}\n\n/**\n * hide action indicator, if it is visiable\n * If the global variable GL_OB_S is > GL_OB_BASE then\n * the overlayBox is not hidden but the zIndex\n * is set to this value\n * @param {String} loc ID string, only used for console log\n */\n// @ts-ignore\nfunction hideActionIndicator(loc) // eslint-disable-line no-unused-vars\n{\n\taiob.hideActionIndicator(loc);\n}\n\n/**\n * checks if overlayBox exists, if not it is\n * added as hidden item at the body end\n */\n// @ts-ignore\nfunction checkOverlayExists() // eslint-disable-line no-unused-vars\n{\n\taiob.checkOverlayExists();\n}\n\n/**\n * show overlay box\n * if not visible show and set zIndex to 10 (GL_OB_BASE)\n * if visible, add +1 to the GL_OB_S variable and\n * _ zIndex by this value\n */\n// @ts-ignore\nfunction showOverlayBoxLayers(el_id) // eslint-disable-line no-unused-vars\n{\n\taiob.showOverlayBoxLayers(el_id);\n}\n\n/**\n * hide overlay box\n * lower GL_OB_S value by -1\n * if we are 10 (GL_OB_BASE) or below hide the overlayIndex\n * and set zIndex and GL_OB_S to 0\n * else just set zIndex to the new GL_OB_S value\n * @param {String} el_id Target to hide layer\n */\n// @ts-ignore\nfunction hideOverlayBoxLayers(el_id='') // eslint-disable-line no-unused-vars\n{\n\taiob.hideOverlayBoxLayers(el_id);\n}\n\n/**\n * only for single action box\n */\n// @ts-ignore\nfunction clearCallActionBox() // eslint-disable-line no-unused-vars\n{\n\taiob.clearCallActionBox();\n}\n\n// MARK: DOM MANAGEMENT FUNCTIONS\n/**\n * reates object for DOM element creation flow\n * @param {String} tag must set tag (div, span, etc)\n * @param {String} [id=''] optional set for id, if input, select will be used for name\n * @param {String} [content=''] text content inside, is skipped if sub elements exist\n * @param {Array} [css=[]] array for css tags\n * @param {Object} [options={}] anything else (value, placeholder, OnClick, style)\n * @return {Object} created element as an object\n */\n// @ts-ignore\nfunction cel(tag, id = '', content = '', css = [], options = {}) // eslint-disable-line no-unused-vars\n{\n\treturn hec.cel(tag, id, content, css, options);\n}\n\n/**\n * attach a cel created object to another to create a basic DOM tree\n * @param {Object} base object where to attach/search\n * @param {Object} attach the object to be attached\n * @param {String} [id=''] optional id, if given search in base for this id and attach there\n * @return {Object} \"none\", technically there is no return needed as it is global attach\n */\n// @ts-ignore\nfunction ael(base, attach, id = '') // eslint-disable-line no-unused-vars\n{\n\treturn hec.ael(base, attach, id);\n}\n\n/**\n * directly attach n elements to one master base element\n * this type does not s_port attach with optional id\n * @param {Object} base object to where we attach the elements\n * @param {...Object} attach attach 1..n: attach directly to the base element those attachments\n * @return {Object} \"none\", technically there is no return needed, global attach\n */\n// @ts-ignore\nfunction aelx(base, ...attach) // eslint-disable-line no-unused-vars\n{\n\treturn hec.aelx(base, ...attach);\n}\n\n/**\n * same as aelx, but instead of using objects as parameters\n * get an array of objects to attach\n * @param {Object} base object to where we attach the elements\n * @param {Array} attach array of objects to attach\n * @return {Object} \"none\", technically there is no return needed, global attach\n */\n// @ts-ignore\nfunction aelxar(base, attach) // eslint-disable-line no-unused-vars\n{\n\treturn hec.aelxar(base, attach);\n}\n\n/**\n * resets the sub elements of the base element given\n * @param {Object} base cel created element\n * @return {Object} returns reset base element\n */\n// @ts-ignore\nfunction rel(base) // eslint-disable-line no-unused-vars\n{\n\treturn hec.rel(base);\n}\n\n/**\n * searches and removes style from css array\n * @param {Object} _element element to work one\n * @param {String} css style sheet to remove (name)\n * @return {Object} returns full element\n */\n// @ts-ignore\nfunction rcssel(_element, css) // eslint-disable-line no-unused-vars\n{\n\treturn hec.rcssel(_element, css);\n}\n\n/**\n * adds a new style sheet to the element given\n * @param {Object} _element element to work on\n * @param {String} css style sheet to add (name)\n * @return {Object} returns full element\n */\n// @ts-ignore\nfunction acssel(_element, css) // eslint-disable-line no-unused-vars\n{\n\treturn hec.acssel(_element, css);\n}\n\n/**\n * removes one css and adds another\n * is a wrapper around rcssel/acssel\n * @param {Object} _element element to work on\n * @param {String} rcss style to remove (name)\n * @param {String} acss style to add (name)\n * @return {Object} returns full element\n */\n// @ts-ignore\nfunction scssel(_element, rcss, acss) // eslint-disable-line no-unused-vars\n{\n\thec.scssel(_element, rcss, acss);\n}\n\n/**\n * parses the object tree created with cel/ael and converts it into an HTML string\n * that can be inserted into the page\n * @param {Object} tree object tree with dom element declarations\n * @return {String} HTML string that can be used as innerHTML\n */\n// @ts-ignore\nfunction phfo(tree) // eslint-disable-line no-unused-vars\n{\n\treturn hec.phfo(tree);\n}\n\n/**\n * Create HTML elements from array list\n * as a flat element without master object file\n * Is like tree.sub call\n * @param {Array} list Array of cel created objects\n * @return {String} HTML String\n */\n// @ts-ignore\nfunction phfa(list) // eslint-disable-line no-unused-vars\n{\n\treturn hec.phfa(list);\n}\n// *** DOM MANAGEMENT FUNCTIONS\n\n// MARK: HTML Helpers\n// BLOCK: html wrappers for quickly creating html data blocks\n\n/**\n * NOTE: OLD FORMAT which misses multiple block set\n * creates an select/options drop down block.\n * the array needs to be key -> value format.\n * key is for the option id and value is for the data output\n * @param {String} name name/id\n * @param {Object} data array for the options\n * @param {String} [selected=''] selected item uid\n * @param {Boolean} [options_only=false] if this is true, it will not print the select part\n * @param {Boolean} [return_string=false] return as string and not as element\n * @param {String} [sort=''] if empty as is, else allowed 'keys',\n * 'values' all others are ignored\n * @return {String} html with build options block\n */\n// @ts-ignore\nfunction html_options(name, data, selected = '', options_only = false, return_string = false, sort = '') // eslint-disable-line no-unused-vars\n{\n\treturn _html_options(name, data, selected, options_only, return_string, sort);\n}\n\n/**\n * NOTE: USE THIS CALL, the above one is deprecated\n * creates an select/options drop down block.\n * the array needs to be key -> value format.\n * key is for the option id and value is for the data output\n * @param {String} name name/id\n * @param {Object} data array for the options\n * @param {String} [selected=''] selected item uid\n * @param {Number} [multiple=0] if this is 1 or larger, the drop down\n * will be turned into multiple select\n * the number sets the size value unless it is 1,\n * then it is default\n * @param {Boolean} [options_only=false] if this is true, it will not print the select part\n * @param {Boolean} [return_string=false] return as string and not as element\n * @param {String} [sort=''] if empty as is, else allowed 'keys',\n * 'values' all others are ignored\n * @param {String} [onchange=''] onchange trigger call, default unset\n * @return {String} html with build options block\n */\n// @ts-ignore\nfunction html_options_block( // eslint-disable-line no-unused-vars\n\tname, data, selected = '', multiple = 0, options_only = false, return_string = false, sort = '', onchange = ''\n) {\n\treturn _html_options_block(\n\t\tname, data, selected, multiple, options_only, return_string, sort, onchange\n\t);\n}\n\n/**\n * refills a select box with options and keeps the selected\n * @param {String} name name/id\n * @param {Object} data array of options\n * @param {String} [sort=''] if empty as is, else allowed 'keys', 'values'\n * all others are ignored\n */\n// @ts-ignore\nfunction html_options_refill(name, data, sort = '') // eslint-disable-line no-unused-vars\n{\n\t_html_options_refill(name, data, sort);\n}\n\n// MARK: URL\n\n/**\n * parses a query string from window.location.search.substring(1)\n * ALTERNATIVE CODE\n * var url = new URL(window.location.href);\n * param_uid = url.searchParams.get('uid');\n * @param {String} [query=''] the query string to parse\n * if not set will auto fill\n * @param {String} [return_key=''] if set only returns this key entry\n * or empty for none\n * @return {Object|String} parameter entry list\n */\n// @ts-ignore\nfunction parseQueryString(query = '', return_key = '') // eslint-disable-line no-unused-vars\n{\n\treturn _parseQueryString(query, return_key);\n}\n\n/**\n * searches query parameters for entry and returns data either as string or array\n * if no search is given the whole parameters are returned as an object\n * if a parameter is set several times it will be returned as an array\n * if search parameter set and nothing found and empty string is returned\n * if no parametes exist and no serach is set and empty object is returned\n * @param {String} [search=''] if set searches for this entry, if empty\n * all parameters are returned\n * @param {String} [query=''] different query string to parse, if not\n * set (default) the current window href is used\n * @param {Boolean} [single=false] if set to true then only the first found\n * will be returned\n * @return {Object|Array|String} if search is empty, object, if search is set\n * and only one entry, then string, else array\n * unless single is true\n */\n// @ts-ignore\nfunction getQueryStringParam(search = '', query = '', single = false) // eslint-disable-line no-unused-vars\n{\n\treturn _getQueryStringParam(search, query, single);\n}\n\n// MARK: ACL LOGIN\n// *** MASTER logout call\n/**\n * submits basic data for form logout\n */\n// @ts-ignore\nfunction loginLogout() // eslint-disable-line no-unused-vars\n{\n\t_loginLogout();\n}\n\n/**\n * create login string and logout button elements\n * @param {String} login_string the login string to show on the left\n * @param {String} [header_id='mainHeader'] the target for the main element block\n * if not set mainHeader is assumed\n * this is the target div for the \"loginRow\"\n */\n// @ts-ignore\nfunction createLoginRow(login_string, header_id = 'mainHeader') // eslint-disable-line no-unused-vars\n{\n\tlnm.createLoginRow(login_string, header_id);\n}\n\n/**\n * create the top nav menu that switches physical between pages\n * (edit access data based)\n * @param {Object} nav_menu the built nav menu with highlight info\n * @param {String} [header_id='mainHeader'] the target for the main element block\n * if not set mainHeader is assumed\n * this is the target div for the \"menuRow\"\n */\n// @ts-ignore\nfunction createNavMenu(nav_menu, header_id = 'mainHeader') // eslint-disable-line no-unused-vars\n{\n\tlnm.createNavMenu(nav_menu, header_id);\n}\n\n// MARK: ACTION BOX\n\n/**\n * Show an action box\n * @param {string} [target_id='actionBox'] where to attach content to, if not exists, create new\n * @param {string} [content=''] content to add to the box\n * @param {array} [action_box_css=[]] additional css elements for the action box\n * @param {number} [override=0] override size adjust\n * @param {number} [content_override=0] override content size adjust\n */\n// @ts-ignore\nfunction showFillActionBox(target_id = 'actionBox', content = '', action_box_css = [], override = 0, content_override = 0) // eslint-disable-line no-unused-vars\n{\n\tab.showFillActionBox(target_id, content, action_box_css, override, content_override);\n}\n\n/**\n * Fill action box with content, create it if it does not existgs\n * @param {string} [target_id='actionBox'] where to attach content to, if not exists, create new\n * @param {string} [content=''] content to add to the box\n * @param {array} [action_box_css=[]] additional css elements for the action box\n */\n// @ts-ignore\nfunction fillActionBox(target_id = 'actionBox', content = '', action_box_css = []) // eslint-disable-line no-unused-vars\n{\n\t// show action box, calc height + center\n\tab.fillActionBox(target_id, content, action_box_css);\n}\n\n/**\n * Adjust the size of the action box\n * @param {string} [target_id='actionBox'] which actionBox to work on\n * @param {number} [override=0] override size adjust\n * @param {number} [content_override=0] override content size adjust\n */\n// @ts-ignore\nfunction adjustActionBox(target_id = 'actionBox', override = 0, content_override = 0) // eslint-disable-line no-unused-vars\n{\n\tab.adjustActionBox(target_id, override, content_override);\n}\n\n/**\n * hide any open action boxes and hide overlay\n */\n// @ts-ignore\nfunction hideAllActionBoxes() // eslint-disable-line no-unused-vars\n{\n\tab.hideAllActionBoxes();\n}\n\n/**\n * hide action box, but do not clear content\n * DEPRECATED\n * @param {string} [target_id='actionBox']\n */\n// @ts-ignore\nfunction hideActionBox(target_id = 'actionBox') // eslint-disable-line no-unused-vars\n{\n\tab.hideActionBox(target_id);\n}\n\n/**\n * Just show and adjust the box\n * DEPRECAED\n * @param {string} [target_id='actionBox'] which actionBox to work on\n * @param {number} [override=0] override size adjust\n * @param {number} [content_override=0] override content size adjust\n * @param {Boolean} [hide_all=false] if set to true, hide all other action boxes\n */\n// @ts-ignore\nfunction showActionBox(target_id = 'actionBox', override = 0, content_override = 0, hide_all = true) // eslint-disable-line no-unused-vars\n{\n\tab.showActionBox(target_id, override, content_override, hide_all);\n}\n\n/**\n * close an action box with default clear content\n * for just hide use hideActionBox\n * DEPRECATED\n * @param {String} [target_id='actionBox'] which action box to close, default is set\n * @param {Boolean} [clean=true] if set to false will not remove html content, just hide\n */\n// @ts-ignore\nfunction closeActionBox(target_id = 'actionBox', clean = true) // eslint-disable-line no-unused-vars\n{\n\t// set the target/content ids\n\tab.closeActionBox(target_id, clean);\n}\n\n/**\n * TODO: better stacked action box: OPEN\n * @param {string} [target_id='actionBox'] which actionBox to work on\n * @param {number} [override=0] override size adjust\n * @param {number} [content_override=0] override content size adjust\n * @param {Boolean} [hide_all=false] if set to true, hide all other action boxes\n */\n// @ts-ignore\nfunction showActionBoxFloat(target_id = 'actionBox', override = 0, content_override = 0, hide_all = false) // eslint-disable-line no-unused-vars\n{\n\tab.showActionBoxFloat(target_id, override, content_override, hide_all);\n}\n\n/**\n * TODO: better stacked action box: CLOSE\n * @param {String} [target_id='actionBox'] which action box to close, default is set\n * @param {Boolean} [clean=true] if set to false will not remove html content, just hide\n */\n// @ts-ignore\nfunction closeActionBoxFloat(target_id = 'actionBox', clean = true) // eslint-disable-line no-unused-vars\n{\n\tab.closeActionBoxFloat(target_id, clean);\n}\n\n/**\n * create a new action box and fill it with basic elements\n * @param {String} [target_id='actionBox']\n * @param {String} [title='']\n * @param {Object} [contents={}]\n * @param {Object} [headers={}]\n * @param {Boolean} [show_close=true]\n * @param {Object} [settings={}] Optional settings, eg style sheets\n */\n// @ts-ignore\nfunction createActionBox( // eslint-disable-line no-unused-vars\n\ttarget_id = 'actionBox',\n\ttitle = '',\n\tcontents = {},\n\theaders = {},\n\tsettings = {},\n\tshow_close = true\n) {\n\tab.createActionBox(target_id, title, contents, headers, settings, show_close);\n}\n\n/**\n * adjusts the action box height based on content and window height of browser\n * TODO: border on outside/and other margin things need to be added in overall adjustment\n * @param {String} [target_id='actionBox'] target id, if not set, fall back to default\n * @param {Number} [override=0] override value to add to the actionBox height\n * @param {Number} [content_override=0] override the value from _content block if it exists\n */\n// @ts-ignore\nfunction adjustActionBoxHeight(target_id = 'actionBox', override = 0, content_override = 0) // eslint-disable-line no-unused-vars\n{\n\tab.adjustActionBoxHeight(target_id, override, content_override);\n}\n\n/* END */\n"], - "mappings": "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", - "names": ["i18n", "hec", "l10n", "hec", "l10n", "escapeHtml", "roundPrecision", "formatString", "unescapeHtml", "loadEl", "pop", "expandTA", "getWindowSize", "getScrollOffset", "getScrollOffsetOpener", "setCenter", "goToPos", "goTo", "numberWithCommas", "convertLBtoBR", "getTimestamp", "dec2hex", "generateId", "randomIdF", "getRandomIntInclusive", "isFunction", "executeFunctionByName", "isObject", "getObjectCount", "keyInObject", "getKeyByValue", "valueInObject", "deepCopyFunction", "exists", "formatBytes", "formatBytesLong", "stringByteFormat", "errorCatch", "actionIndicator", "actionIndicatorShow", "actionIndicatorHide", "overlayBoxShow", "overlayBoxHide", "setOverlayBox", "hideOverlayBox", "ClearCall", "html_options", "html_options_block", "html_options_refill", "parseQueryString", "getQueryStringParam", "loginLogout"] + "sourcesContent": ["/*\nDescription: JavaScript Helpers\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport {\n\terrorCatch, isFunction,\n\texecuteFunctionByName, runFunction, runFunctionArgsArray,\n\tisObject, getObjectCount,\n\tkeyInObject, objectKeyExists,\n\tgetKeyByValue, valueInObject, objectValueExists,\n\tdeepCopyFunction\n};\n\n/**\n * prints out error messages based on data available from the browser\n * @param {Object} err error from try/catch block\n */\nfunction errorCatch(err)\n{\n\t// for FF & Chrome\n\tif (err.stack) {\n\t\t// only FF\n\t\tif (err.lineNumber) {\n\t\t\tconsole.error('ERROR[%s:%s] ', err.name, err.lineNumber, err);\n\t\t} else if (err.line) {\n\t\t\t// only Safari\n\t\t\tconsole.error('ERROR[%s:%s] ', err.name, err.line, err);\n\t\t} else {\n\t\t\tconsole.error('ERROR[%s] ', err.name, err);\n\t\t}\n\t} else if (err.number) {\n\t\t// IE\n\t\tconsole.error('ERROR[%s:%s] %s', err.name, err.number, err.message);\n\t\tconsole.error('ERROR[description] %s', err.description);\n\t} else {\n\t\t// the rest\n\t\tconsole.error('ERROR[%s] %s', err.name, err.message);\n\t}\n}\n\n/**\n * check if name is a function\n * @param {string} name Name of function to check if exists\n * @return {Boolean} true/false\n */\nfunction isFunction(name)\n{\n\tif (typeof window[name] !== 'undefined' &&\n\t\ttypeof window[name] === 'function') {\n\t\treturn true;\n\t} else {\n\t\treturn false;\n\t}\n}\n\n/**\n * call a function by its string name\n * https://stackoverflow.com/a/359910\n * example: executeFunctionByName(\"My.Namespace.functionName\", window, arguments);\n * @param {string} functionName The function name or namespace + function\n * @param {any} context context (window or first namespace)\n * hidden next are all the arguments\n * @return {any} Return values from functon\n */\nfunction executeFunctionByName(functionName, context /*, args */)\n{\n\tvar args = Array.prototype.slice.call(arguments, 2);\n\tvar namespaces = functionName.split('.');\n\tvar func = namespaces.pop();\n\tif (func == undefined) {\n\t\tthrow new Error(\"Cannot get function from namespaces: \" + functionName);\n\t}\n\tfor (var i = 0; i < namespaces.length; i++) {\n\t\tcontext = context[namespaces[i]];\n\t}\n\treturn context[func].apply(context, args);\n}\n\n/**\n * call a function by string\n * call runFunctionArgArray\n * @param {string} name Function name to call\n * @param {Array} arguments all next function arguments are passed on as argument to the function\n * @returns void\n */\nfunction runFunction(name)\n{\n\tvar args = Array.prototype.slice.call(arguments, 1);\n\trunFunctionArgsArray(name, args);\n}\n\n/**\n * call a function with a string, argumens as array\n * @param {string} name Function name to call\n * @param {array} args function arguments as arry\n * @returns void\n */\nfunction runFunctionArgsArray(name, args)\n{\n\tvar fn = window[name];\n\tif(typeof fn !== 'function') {\n\t\treturn;\n\t}\n\n\tfn.apply(window, args);\n}\n\n/**\n * checks if a variable is an object\n * @param {any} val possible object\n * @return {Boolean} true/false if it is an object or not\n */\nfunction isObject(val)\n{\n\tif (val === null) {\n\t\treturn false;\n\t}\n\treturn ((typeof val === 'function') || (typeof val === 'object'));\n}\n\n/**\n * get the length of an object (entries)\n * @param {Object} object object to check\n * @return {Number} number of entry, or -1 if not object\n */\nfunction getObjectCount(object)\n{\n\tif (!isObject(object)) {\n\t\treturn -1;\n\t}\n\treturn Object.keys(object).length;\n}\n\n/**\n * checks if a key exists in a given object\n * @param {String} key key name\n * @param {Object} object object to search key in\n * @return {Boolean} true/false if key exists in object\n * @deprecated Use objectKeyExists\n */\nfunction keyInObject(key, object)\n{\n\treturn objectKeyExists(object, key);\n}\n\n/**\n * This is the correct order and will superseed keyInObject\n * @param {Object} object object to search key in\n * @param {String} key key name\n * @returns {Boolean} true/false if key exists in object\n */\nfunction objectKeyExists(object, key)\n{\n\treturn Object.prototype.hasOwnProperty.call(object, key) ? true : false;\n}\n\n/**\n * returns matching key of value\n * @param {Object} object object to search value in\n * @param {any} value any value (String, Number, etc)\n * @return {String} the key found for the first matching value\n */\nfunction getKeyByValue(object, value)\n{\n\treturn Object.keys(object).find(key => object[key] === value) ?? '';\n}\n\n/**\n * returns true if value is found in object with a key\n * @param {Object} object object to search value in\n * @param {any} value any value (String, Number, etc)\n * @return {Boolean} true on value found, false on not found\n * @deprecated use objectValueExists\n */\nfunction valueInObject(object, value)\n{\n\treturn objectValueExists(object, value);\n}\n\n/**\n * returns true if value is found in object with a key\n * @param {Object} object object to search value in\n * @param {any} value any value (String, Number, etc)\n * @return {Boolean} true on value found, false on not found\n */\nfunction objectValueExists(object, value)\n{\n\treturn Object.keys(object).find(key => object[key] === value) ? true : false;\n}\n\n/**\n * true deep copy for Javascript objects\n * if Object.assign({}, obj) is not working (shallow)\n * or if JSON.parse(JSON.stringify(obj)) is failing\n * @param {Object} inObject Object to copy\n * @return {Object} Copied Object\n */\nfunction deepCopyFunction(inObject)\n{\n\tvar outObject, value, key;\n\tif (typeof inObject !== 'object' || inObject === null) {\n\t\t// Return the value if inObject is not an object\n\t\treturn inObject;\n\t}\n\t// Create an array or object to hold the values\n\toutObject = Array.isArray(inObject) ? [] : {};\n\t// loop over ech entry in object\n\tfor (key in inObject) {\n\t\tvalue = inObject[key];\n\t\t// Recursively (deep) copy for nested objects, including arrays\n\t\toutObject[key] = deepCopyFunction(value);\n\t}\n\n\treturn outObject;\n}\n\n// __END__\n", "/*\nDescription: DOM Helpers\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { loadEl, pop, expandTA, exists };\n\n/**\n * Gets html element or throws an error\n * @param {string} el_id Element ID to get\n * @returns {HTMLElement}\n * @throws Error\n */\nfunction loadEl(el_id)\n{\n\tlet el = document.getElementById(el_id);\n\tif (el === null) {\n\t\tthrow new Error('Cannot find: ' + el_id);\n\t}\n\treturn el;\n}\n\n/**\n * opens a popup window with winName and given features (string)\n * @param {String} theURL the url\n * @param {String} winName window name\n * @param {Object} features popup features\n */\nfunction pop(theURL, winName, features)\n{\n\tlet __winName = window.open(theURL, winName, features);\n\tif (__winName == null) {\n\t\treturn;\n\t}\n\t__winName.focus();\n}\n\n/**\n * automatically resize a text area based on the amount of lines in it\n * @param {string} ta_id element id\n */\nfunction expandTA(ta_id)\n{\n\tlet ta = this.loadEl(ta_id);\n\tif (ta instanceof HTMLElement && ta.getAttribute('type') !== \"textarea\") {\n\t\tthrow new Error(\"Element is not a textarea: \" + ta_id);\n\t}\n\tlet maxChars = parseInt(ta.getAttribute('cols') ?? \"0\");\n\tlet ta_value = ta.getAttribute('value');\n\tlet theRows = [];\n\tif (ta_value != null) {\n\t\ttheRows = ta_value.split('\\n');\n\t}\n\tvar numNewRows = 0;\n\n\tfor ( var i = 0; i < theRows.length; i++ ) {\n\t\tif ((theRows[i].length+2) > maxChars) {\n\t\t\tnumNewRows += Math.ceil( (theRows[i].length+2) / maxChars ) ;\n\t\t}\n\t}\n\tta.setAttribute('row', (numNewRows + theRows.length).toString());\n}\n\n/**\n * checks if a DOM element actually exists\n * @param {String} id Element id to check for\n * @return {Boolean} true if element exists, false on failure\n */\nfunction exists(id)\n{\n\treturn $('#' + id).length > 0 ? true : false;\n}\n\n// __END__\n", "/*\nDescription: DOM Management and HTML builder\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport {\n\tHtmlElementCreator,\n\t// deprecated name\n\tHtmlElementCreator as DomManagement\n};\nimport { deepCopyFunction, isObject } from './JavaScriptHelpers.mjs';\n\nclass HtmlElementCreator {\n\t/**\n\t * reates object for DOM element creation flow\n\t * @param {String} tag must set tag (div, span, etc)\n\t * @param {String} [id=''] optional set for id, if input, select will be used for name\n\t * @param {String} [content=''] text content inside, is skipped if sub elements exist\n\t * @param {Array} [css=[]] array for css tags\n\t * @param {Object} [options={}] anything else (value, placeholder, OnClick, style)\n\t * @return {Object} created element as an object\n\t */\n\tcel(tag, id = '', content = '', css = [], options = {})\n\t{\n\t\treturn {\n\t\t\ttag: tag,\n\t\t\tid: id,\n\t\t\t// override name if set, else id is used. Only for input/button\n\t\t\tname: options.name,\n\t\t\tcontent: content,\n\t\t\tcss: css,\n\t\t\toptions: options,\n\t\t\tsub: []\n\t\t};\n\t}\n\n\t/**\n\t * attach a cel created object to another to create a basic DOM tree\n\t * @param {Object} base object where to attach/search\n\t * @param {Object} attach the object to be attached\n\t * @param {String} [id=''] optional id, if given search in base for this id and attach there\n\t * @return {Object} \"none\", technically there is no return needed as it is global attach\n\t */\n\tael(base, attach, id = '')\n\t{\n\t\tif (id) {\n\t\t\t// base id match already\n\t\t\tif (base.id == id) {\n\t\t\t\tbase.sub.push(deepCopyFunction(attach));\n\t\t\t} else {\n\t\t\t\t// sub check\n\t\t\t\tif (isObject(base.sub) && base.sub.length > 0) {\n\t\t\t\t\tfor (var i = 0; i < base.sub.length; i ++) {\n\t\t\t\t\t\t// recursive call to sub element\n\t\t\t\t\t\tthis.ael(base.sub[i], attach, id);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tbase.sub.push(deepCopyFunction(attach));\n\t\t}\n\t\treturn base;\n\t}\n\n\t/**\n\t * directly attach n elements to one master base element\n\t * this type does not support attach with optional id\n\t * @param {Object} base object to where we attach the elements\n\t * @param {...Object} attach attach 1..n: attach directly to the base element those attachments\n\t * @return {Object} \"none\", technically there is no return needed, global attach\n\t */\n\taelx(base, ...attach)\n\t{\n\t\tfor (var i = 0; i < attach.length; i ++) {\n\t\t\tbase.sub.push(deepCopyFunction(attach[i]));\n\t\t}\n\t\treturn base;\n\t}\n\n\t/**\n\t * same as aelx, but instead of using objects as parameters\n\t * get an array of objects to attach\n\t * @param {Object} base object to where we attach the elements\n\t * @param {Array} attach array of objects to attach\n\t * @return {Object} \"none\", technically there is no return needed, global attach\n\t */\n\taelxar(base, attach)\n\t{\n\t\tfor (var i = 0; i < attach.length; i ++) {\n\t\t\tbase.sub.push(deepCopyFunction(attach[i]));\n\t\t}\n\t\treturn base;\n\t}\n\n\t/**\n\t * resets the sub elements of the base element given\n\t * @param {Object} base cel created element\n\t * @return {Object} returns reset base element\n\t */\n\trel(base)\n\t{\n\t\tbase.sub = [];\n\t\treturn base;\n\t}\n\n\t/**\n\t * searches and removes style from css array\n\t * @param {Object} _element element to work one\n\t * @param {String} css style sheet to remove (name)\n\t * @return {Object} returns full element\n\t */\n\trcssel(_element, css)\n\t{\n\t\tvar css_index = _element.css.indexOf(css);\n\t\tif (css_index > -1) {\n\t\t\t_element.css.splice(css_index, 1);\n\t\t}\n\t\treturn _element;\n\t}\n\n\t/**\n\t * adds a new style sheet to the element given\n\t * @param {Object} _element element to work on\n\t * @param {String} css style sheet to add (name)\n\t * @return {Object} returns full element\n\t */\n\tacssel(_element, css)\n\t{\n\t\tvar css_index = _element.css.indexOf(css);\n\t\tif (css_index == -1) {\n\t\t\t_element.css.push(css);\n\t\t}\n\t\treturn _element;\n\t}\n\n\t/**\n\t * removes one css and adds another\n\t * is a wrapper around rcssel/acssel\n\t * @param {Object} _element element to work on\n\t * @param {String} rcss style to remove (name)\n\t * @param {String} acss style to add (name)\n\t * @return {Object} returns full element\n\t */\n\tscssel(_element, rcss, acss)\n\t{\n\t\tthis.rcssel(_element, rcss);\n\t\tthis.acssel(_element, acss);\n\t\treturn _element;\n\t}\n\n\t/**\n\t * parses the object tree created with cel/ael and converts it into an HTML string\n\t * that can be inserted into the page\n\t * @param {Object} tree object tree with dom element declarations\n\t * @return {String} HTML string that can be used as innerHTML\n\t */\n\tphfo(tree)\n\t{\n\t\tlet name_elements = [\n\t\t\t'button',\n\t\t\t'fieldset',\n\t\t\t'form',\n\t\t\t'iframe',\n\t\t\t'input',\n\t\t\t'map',\n\t\t\t'meta',\n\t\t\t'object',\n\t\t\t'output',\n\t\t\t'param',\n\t\t\t'select',\n\t\t\t'textarea',\n\t\t];\n\t\tlet skip_options = [\n\t\t\t'id',\n\t\t\t'name',\n\t\t\t'class',\n\t\t];\n\t\tlet no_close = [\n\t\t\t'input',\n\t\t\t'br',\n\t\t\t'img',\n\t\t\t'hr',\n\t\t\t'area',\n\t\t\t'col',\n\t\t\t'keygen',\n\t\t\t'wbr',\n\t\t\t'track',\n\t\t\t'source',\n\t\t\t'param',\n\t\t\t'command',\n\t\t\t// only in header\n\t\t\t'base',\n\t\t\t'meta',\n\t\t\t'link',\n\t\t\t'embed',\n\t\t];\n\t\t// holds the elements\n\t\tvar content = [];\n\t\t// main part line\n\t\tvar line = '<' + tree.tag;\n\t\tvar i;\n\t\t// first id, if set\n\t\tif (tree.id) {\n\t\t\tline += ' id=\"' + tree.id + '\"';\n\t\t\t// if anything input (input, textarea, select then add name too)\n\t\t\tif (name_elements.includes(tree.tag)) {\n\t\t\t\tline += ' name=\"' + (tree.name ? tree.name : tree.id) + '\"';\n\t\t\t}\n\t\t}\n\t\t// second CSS\n\t\tif (isObject(tree.css) && tree.css.length > 0) {\n\t\t\tline += ' class=\"';\n\t\t\tfor (i = 0; i < tree.css.length; i ++) {\n\t\t\t\tline += tree.css[i] + ' ';\n\t\t\t}\n\t\t\t// strip last space\n\t\t\tline = line.slice(0, -1);\n\t\t\tline += '\"';\n\t\t}\n\t\t// options is anything key = \"data\"\n\t\tif (isObject(tree.options)) {\n\t\t\t// ignores id, name, class as key\n\t\t\tfor (const [key, item] of Object.entries(tree.options)) {\n\t\t\t\tif (!skip_options.includes(key)) {\n\t\t\t\t\tline += ' ' + key + '=\"' + item + '\"';\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// finish open tag\n\t\tline += '>';\n\t\t// push finished line\n\t\tcontent.push(line);\n\t\t// dive into sub tree to attach sub nodes\n\t\t// NOTES: we can have content (text) AND sub nodes at the same level\n\t\t// CONTENT (TEXT) takes preference over SUB NODE in order\n\t\tif (isObject(tree.sub) && tree.sub.length > 0) {\n\t\t\tif (tree.content) {\n\t\t\t\tcontent.push(tree.content);\n\t\t\t}\n\t\t\tfor (i = 0; i < tree.sub.length; i ++) {\n\t\t\t\tcontent.push(this.phfo(tree.sub[i]));\n\t\t\t}\n\t\t} else if (tree.content) {\n\t\t\tcontent.push(tree.content);\n\t\t}\n\t\t// if not input, image or br, then close\n\t\tif (\n\t\t\t!no_close.includes(tree.tag)\n\t\t) {\n\t\t\tcontent.push('');\n\t\t}\n\t\t// combine to string\n\t\treturn content.join('');\n\t}\n\n\t/**\n\t * Create HTML elements from array list\n\t * as a flat element without master object file\n\t * Is like tree.sub call\n\t * @param {Array} list Array of cel created objects\n\t * @return {String} HTML String\n\t */\n\tphfa(list)\n\t{\n\t\tvar content = [];\n\t\tfor (var i = 0; i < list.length; i ++) {\n\t\t\tcontent.push(this.phfo(list[i]));\n\t\t}\n\t\treturn content.join('');\n\t}\n}\n\n// __EMD__\n", "/*\nDescription: HTML Helpers\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { escapeHtml, unescapeHtml, html_options, html_options_block, html_options_refill };\nimport { loadEl} from './DomHelpers.mjs';\nimport { DomManagement } from './HtmlElementCreator.mjs';\nlet dom = new DomManagement();\n\n/**\n * Escapes HTML in string\n * @param {String} string Text to escape HTML in\n * @returns {String}\n */\nfunction escapeHtml(string)\n{\n\treturn string.replace(/[&<>\"'/]/g, function (s) {\n\t\tvar entityMap = {\n\t\t\t'&': '&',\n\t\t\t'<': '<',\n\t\t\t'>': '>',\n\t\t\t'\"': '"',\n\t\t\t'\\'': ''',\n\t\t\t'/': '/'\n\t\t};\n\n\t\treturn entityMap[s];\n\t});\n}\n\n/**\n * Unescape a HTML encoded string\n * @param {String} string Text to unescape HTML in\n * @returns {String}\n */\nfunction unescapeHtml(string)\n{\n\treturn string.replace(/&[#\\w]+;/g, function (s) {\n\t\tvar entityMap = {\n\t\t\t'&': '&',\n\t\t\t'<': '<',\n\t\t\t'>': '>',\n\t\t\t'"': '\"',\n\t\t\t''': '\\'',\n\t\t\t'/': '/'\n\t\t};\n\n\t\treturn entityMap[s];\n\t});\n}\n\n// BLOCK: html wrappers for quickly creating html data blocks\n\n/**\n * NOTE: OLD FORMAT which misses multiple block set\n * creates an select/options drop down block.\n * the array needs to be key -> value format.\n * key is for the option id and value is for the data output\n * @param {String} name name/id\n * @param {Object} data array for the options\n * @param {String} [selected=''] selected item uid\n * @param {Boolean} [options_only=false] if this is true, it will not print the select part\n * @param {Boolean} [return_string=false] return as string and not as element\n * @param {String} [sort=''] if empty as is, else allowed 'keys',\n * 'values' all others are ignored\n * @return {String} html with build options block\n * @deprecated html_options_block\n */\nfunction html_options(name, data, selected = '', options_only = false, return_string = false, sort = '')\n{\n\t// wrapper to new call\n\treturn this.html_options_block(\n\t\tname, data, selected, 0, options_only, return_string, sort\n\t);\n}\n\n/**\n * NOTE: USE THIS CALL, the above one is deprecated\n * creates an select/options drop down block.\n * the array needs to be key -> value format.\n * key is for the option id and value is for the data output\n * @param {String} name name/id\n * @param {Object} data array for the options\n * @param {String} [selected=''] selected item uid\n * @param {Number} [multiple=0] if this is 1 or larger, the drop down\n * will be turned into multiple select\n * the number sets the size value unless it is 1,\n * then it is default\n * @param {Boolean} [options_only=false] if this is true, it will not print the select part\n * @param {Boolean} [return_string=false] return as string and not as element\n * @param {String} [sort=''] if empty as is, else allowed 'keys',\n * 'values' all others are ignored\n * @param {String} [onchange=''] onchange trigger call, default unset\n * @return {String} html with build options block\n */\nfunction html_options_block(\n\tname, data, selected = '', multiple = 0, options_only = false, return_string = false, sort = '', onchange = ''\n) {\n\tvar content = [];\n\tvar element_select;\n\tvar select_options = {};\n\tvar element_option;\n\tvar data_list = []; // for sorted output\n\tvar value;\n\tvar options = {};\n\t// var option;\n\tif (multiple > 0) {\n\t\tselect_options.multiple = '';\n\t\tif (multiple > 1) {\n\t\t\tselect_options.size = multiple;\n\t\t}\n\t}\n\tif (onchange) {\n\t\tselect_options.OnChange = onchange;\n\t}\n\t// set outside select, gets stripped on return if options only is true\n\telement_select = dom.cel('select', name, '', [], select_options);\n\t// console.log('Call for %s, options: %s', name, options_only);\n\tif (sort == 'keys') {\n\t\tdata_list = Object.keys(data).sort();\n\t} else if (sort == 'values') {\n\t\tdata_list = Object.keys(data).sort((a, b) => ('' + data[a]).localeCompare(data[b]));\n\t} else {\n\t\tdata_list = Object.keys(data);\n\t}\n\t// console.log('ORDER: %s', data_list);\n\t// use the previously sorted list\n\t// for (const [key, value] of Object.entries(data)) {\n\tfor (const key of data_list) {\n\t\tvalue = data[key];\n\t\t// console.log('create [%s] options: key: %s, value: %s', name, key, value);\n\t\t// basic options init\n\t\toptions = {\n\t\t\t'label': value,\n\t\t\t'value': key,\n\t\t\t'selected': ''\n\t\t};\n\t\t// add selected if matching\n\t\tif (multiple == 0 && !Array.isArray(selected) && selected == key) {\n\t\t\toptions.selected = '';\n\t\t}\n\t\t// for multiple, we match selected as array\n\t\tif (multiple == 1 && Array.isArray(selected) && selected.indexOf(key) != -1) {\n\t\t\toptions.selected = '';\n\t\t}\n\t\t// create the element option\n\t\telement_option = dom.cel('option', '', value, [], options);\n\t\t// attach it to the select element\n\t\tdom.ael(element_select, element_option);\n\t}\n\t// if with select part, convert to text\n\tif (!options_only) {\n\t\tif (return_string) {\n\t\t\tcontent.push(dom.phfo(element_select));\n\t\t\treturn content.join('');\n\t\t} else {\n\t\t\treturn element_select;\n\t\t}\n\t} else {\n\t\t// strip select part\n\t\tif (return_string) {\n\t\t\tfor (var i = 0; i < element_select.sub.length; i ++) {\n\t\t\t\tcontent.push(dom.phfo(element_select.sub[i]));\n\t\t\t}\n\t\t\treturn content.join('');\n\t\t} else {\n\t\t\treturn element_select.sub;\n\t\t}\n\t}\n}\n\n/**\n * refills a select box with options and keeps the selected\n * @param {String} name name/id\n * @param {Object} data array of options\n * @param {String} [sort=''] if empty as is, else allowed 'keys', 'values'\n * all others are ignored\n */\nfunction html_options_refill(name, data, sort = '')\n{\n\tvar element_option;\n\tvar option_selected;\n\tvar data_list = []; // for sorted output\n\tvar value;\n\t// skip if not exists\n\tif (document.getElementById(name)) {\n\t\t// console.log('Call for %s, options: %s', name, options_only);\n\t\tif (sort == 'keys') {\n\t\t\tdata_list = Object.keys(data).sort();\n\t\t} else if (sort == 'values') {\n\t\t\tdata_list = Object.keys(data).sort((a, b) => ('' + data[a]).localeCompare(data[b]));\n\t\t} else {\n\t\t\tdata_list = Object.keys(data);\n\t\t}\n\t\t// first read in existing ones from the options and get the selected one\n\t\t[].forEach.call(document.querySelectorAll('#' + name + ' :checked'), function(elm) {\n\t\t\toption_selected = elm.value;\n\t\t});\n\t\tloadEl(name).innerHTML = '';\n\t\tfor (const key of data_list) {\n\t\t\tvalue = data[key];\n\t\t\t// console.log('add [%s] options: key: %s, value: %s', name, key, value);\n\t\t\telement_option = document.createElement('option');\n\t\t\telement_option.label = value;\n\t\t\telement_option.value = key;\n\t\t\telement_option.innerHTML = value;\n\t\t\tif (key == option_selected) {\n\t\t\t\telement_option.selected = true;\n\t\t\t}\n\t\t\tloadEl(name).appendChild(element_option);\n\t\t}\n\t}\n}\n\n// __EMD__\n", "/*\nDescription: Math Helpers\nDate: 2025/3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { dec2hex, getRandomIntInclusive, roundPrecision };\n\n/**\n * dec2hex :: Integer -> String\n * i.e. 0-255 -> '00'-'ff'\n * @param {Number} dec decimal string\n * @return {String} hex encdoded number, prefix with 0x\n */\nfunction dec2hex(dec)\n{\n\treturn ('0x' + dec.toString(16)).substring(-2);\n}\n\n/**\n * generate a number between min/max\n * with min/max inclusive.\n * eg: 1,5 will create a number ranging from 1 o 5\n * @param {Number} min minimum int number inclusive\n * @param {Number} max maximum int number inclusive\n * @return {Number} Random number\n */\nfunction getRandomIntInclusive(min, max)\n{\n\tmin = Math.ceil(min);\n\tmax = Math.floor(max);\n\t// The maximum is inclusive and the minimum is inclusive\n\treturn Math.floor(Math.random() * (max - min + 1) + min);\n}\n\n/**\n * Round a number to precision\n * @param {Number} number Number to round\n * @param {Number} precision Precision value\n * @returns {Number}\n */\nfunction roundPrecision(number, precision)\n{\n\tif (isNaN(number) || isNaN(precision)) {\n\t\treturn number;\n\t}\n\treturn Math.round(number * Math.pow(10, precision)) / Math.pow(10, precision);\n}\n\n// __END__\n", "/*\nDescription: String Helpers\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { formatString, numberWithCommas, convertLBtoBR };\n\n/**\n * simple sprintf formater for replace\n * usage: formatString(\"{0} is cool, {1} is not\", \"Alpha\", \"Beta\");\n * First, checks if it isn't implemented yet.\n * @param {String} string String with {..} entries\n * @param {...any} args List of replacement\n * @returns {String} Escaped string\n */\nfunction formatString(string, ...args)\n{\n\treturn string.replace(/{(\\d+)}/g, function(match, number)\n\t{\n\t\treturn typeof args[number] != 'undefined' ?\n\t\t\targs[number] :\n\t\t\tmatch\n\t\t;\n\t});\n}\n/**\n * formats flat number 123456 to 123,456\n * @param {Number} number number to be formated\n * @return {String} formatted with , in thousands\n */\nfunction numberWithCommas(number)\n{\n\tvar parts = number.toString().split('.');\n\tparts[0] = parts[0].replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');\n\treturn parts.join('.');\n}\n\n/**\n * converts line breaks to br\n * @param {String} string any string\n * @return {String} string with
\n */\nfunction convertLBtoBR(string)\n{\n\treturn string.replace(/(?:\\r\\n|\\r|\\n)/g, '
');\n}\n\n// __END__\n", "/*\nDescription: Date Time functions\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { getTimestamp };\n\n/**\n * returns current timestamp (unix timestamp)\n * @return {Number} timestamp (in milliseconds)\n */\nfunction getTimestamp()\n{\n\tvar date = new Date();\n\treturn date.getTime();\n}\n\n// __END__\n", "/*\nDescription: Generate unique ids\nDate: 2025/3/7\nCreator: Clemens Schwaighofer\n*/\n\nexport { generateId, randomIdF };\n\n\n/**\n * generateId :: Integer -> String\n * only works on mondern browsers\n * @param {Number} len length of unique id string\n * @return {String} random string in length of len\n */\nfunction generateId(len)\n{\n\tvar arr = new Uint8Array((len || 40) / 2);\n\t(\n\t\twindow.crypto ||\n\t\t// @ts-ignore\n\t\twindow.msCrypto\n\t).getRandomValues(arr);\n\treturn Array.from(arr, self.dec2hex).join('');\n}\n\n/**\n * creates a pseudo random string of 10 or 11 characters\n * works on all browsers\n * after many runs it will create duplicates\n * NOTE: no idea why this sometimes returns 10 or 11\n * @return {String} not true random string\n */\nfunction randomIdF()\n{\n\treturn Math.random().toString(36).substring(2);\n}\n", "/*\nDescription: Resize and Move Javascript\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nimport { errorCatch} from './JavaScriptHelpers.mjs';\nimport { loadEl } from './DomHelpers.mjs';\nexport { getWindowSize, getScrollOffset, getScrollOffsetOpener, setCenter, goToPos, goTo };\n\n/**\n * wrapper to get the real window size for the current browser window\n * @return {Object} object with width/height\n */\nfunction getWindowSize()\n{\n\tvar width, height;\n\twidth = window.innerWidth || (window.document.documentElement.clientWidth || window.document.body.clientWidth);\n\theight = window.innerHeight || (window.document.documentElement.clientHeight || window.document.body.clientHeight);\n\treturn {\n\t\twidth: width,\n\t\theight: height\n\t};\n}\n\n/**\n * wrapper to get the correct scroll offset\n * @return {Object} object with x/y px\n */\nfunction getScrollOffset()\n{\n\tvar left, top;\n\tleft = window.pageXOffset || (window.document.documentElement.scrollLeft || window.document.body.scrollLeft);\n\ttop = window.pageYOffset || (window.document.documentElement.scrollTop || window.document.body.scrollTop);\n\treturn {\n\t\tleft: left,\n\t\ttop: top\n\t};\n}\n\n/**\n * wrapper to get the correct scroll offset for opener page (from popup)\n * @return {Object} object with x/y px\n */\nfunction getScrollOffsetOpener()\n{\n\tvar left, top;\n\tleft = opener.window.pageXOffset || (opener.document.documentElement.scrollLeft || opener.document.body.scrollLeft);\n\ttop = opener.window.pageYOffset || (opener.document.documentElement.scrollTop || opener.document.body.scrollTop);\n\treturn {\n\t\tleft: left,\n\t\ttop: top\n\t};\n}\n\n/**\n * centers div to current window size middle\n * @param {String} id element to center\n * @param {Boolean} left if true centers to the middle from the left\n * @param {Boolean} top if true centers to the middle from the top\n */\nfunction setCenter(id, left, top)\n{\n\t// get size of id\n\tvar dimensions = {\n\t\theight: $('#' + id).height() ?? 0,\n\t\twidth: $('#' + id).width() ?? 0\n\t};\n\tvar type = $('#' + id).css('position');\n\tvar viewport = this.getWindowSize();\n\tvar offset = this.getScrollOffset();\n\n\t// console.log('Id %s, type: %s, dimensions %s x %s, viewport %s x %s', id, type, dimensions.width, dimensions.height, viewport.width, viewport.height);\n\t// console.log('Scrolloffset left: %s, top: %s', offset.left, offset.top);\n\t// 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)));\n\tif (left) {\n\t\t$('#' + id).css({\n\t\t\tleft: (viewport.width / 2) - (dimensions.width / 2) + offset.left + 'px'\n\t\t});\n\t}\n\tif (top) {\n\t\t// if we have fixed, we do not add the offset, else it moves out of the screen\n\t\tvar top_pos = type == 'fixed' ?\n\t\t\t(viewport.height / 2) - (dimensions.height / 2) :\n\t\t\t(viewport.height / 2) - (dimensions.height / 2) + offset.top;\n\t\t$('#' + id).css({\n\t\t\ttop: top_pos + 'px'\n\t\t});\n\t}\n}\n\n/**\n * goes to an element id position\n * @param {String} element element id to move to\n * @param {Number} [offset=0] offset from top, default is 0 (px)\n * @param {Number} [duration=500] animation time, default 500ms\n * @param {String} [base='body,html'] base element for offset scroll\n */\nfunction goToPos(element, offset = 0, duration = 500, base = 'body,html')\n{\n\ttry {\n\t\tlet element_offset = $('#' + element).offset();\n\t\tif (element_offset == undefined) {\n\t\t\treturn;\n\t\t}\n\t\tif ($('#' + element).length) {\n\t\t\t$(base).animate({\n\t\t\t\tscrollTop: element_offset.top - offset\n\t\t\t}, duration);\n\t\t}\n\t} catch (err) {\n\t\terrorCatch(err);\n\t}\n}\n\n/**\n * go to element, scroll\n * non jquery\n * @param {string} target\n*/\nfunction goTo(target)\n{\n\tloadEl(target).scrollIntoView({\n\t\tbehavior: 'smooth'\n\t});\n}\n\n// __END__\n", "/*\nDescription: Byte string formatting\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { formatBytes, formatBytesLong, stringByteFormat };\n\n/**\n * converts a int number into bytes with prefix in two decimals precision\n * currently precision is fixed, if dynamic needs check for max/min precision\n * @param {Number|BigInt} bytes bytes in int\n * @return {String} string in GB/MB/KB\n */\nfunction formatBytes(bytes)\n{\n\tvar i = -1;\n\t// If this ia bigint -> convert to number, we need the decimals\n\tif (typeof bytes === \"bigint\") {\n\t\tbytes = Number(bytes);\n\t}\n\tif (isNaN(bytes)) {\n\t\treturn bytes.toString();\n\t}\n\tdo {\n\t\tbytes = bytes / 1024;\n\t\ti++;\n\t} while (bytes > 99);\n\treturn (\n\t\tMath.round(bytes * Math.pow(10, 2)) / Math.pow(10, 2)\n\t) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];\n}\n\n/**\n * like formatBytes, but returns bytes for <1KB and not 0.n KB\n * @param {Number|BigInt} bytes bytes in int\n * @return {String} string in GB/MB/KB\n */\nfunction formatBytesLong(bytes)\n{\n\t// If this ia bigint -> convert to number, we need the decimals\n\tif (typeof bytes === \"bigint\") {\n\t\tbytes = Number(bytes);\n\t}\n\tif (isNaN(bytes)) {\n\t\treturn bytes.toString();\n\t}\n\tlet negative = false;\n\tif (bytes < 0) {\n\t\tnegative = true;\n\t\tbytes *= -1;\n\t}\n\tvar i = Math.floor(Math.log(bytes) / Math.log(1024));\n\tvar sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];\n\treturn (negative ? '-' : '') + (\n\t\t(\n\t\t\tbytes /\n\t\t\tMath.pow(1024, i)\n\t\t).toFixed(2)\n\t\t// * 1 + ' ' + sizes[i]\n\t\t+ ' ' + sizes[i]\n\t).toString();\n}\n\n/**\n * Convert a string with B/K/M/etc into a byte number\n * @param {String|Number|Object} bytes Any string with B/K/M/etc\n * @param {Boolean} raw [default=false] Return not rounded values\n * @return {String|Number} A byte number, or original string as is\n */\nfunction stringByteFormat(bytes, raw=false)\n{\n\t// if anything not string return\n\tif (!(typeof bytes === 'string' || bytes instanceof String)) {\n\t\treturn bytes.toString();\n\t}\n\t// for pow exponent list\n\tlet valid_units = 'bkmgtpezy';\n\t// valid string that can be converted\n\tlet regex = /([\\d.,]*)\\s?(eb|pb|tb|gb|mb|kb|e|p|t|g|m|k|b)$/i;\n\tlet matches = bytes.match(regex);\n\t// if nothing found, return original input\n\tif (matches !== null) {\n\t\t// remove all non valid entries outside numbers and .\n\t\t// convert to float number\n\t\tlet m1 = parseFloat(matches[1].replace(/[^0-9.]/,''));\n\t\t// only get the FIRST letter from the size, convert it to lower case\n\t\tlet m2 = matches[2].replace(/[^bkmgtpezy]/i, '').charAt(0).toLowerCase();\n\t\tif (m2) {\n\t\t\t// use the position in the valid unit list to do the math conversion\n\t\t\tbytes = m1 * Math.pow(1024, valid_units.indexOf(m2));\n\t\t}\n\t}\n\t// if we want to have the raw data returned\n\tif (raw) {\n\t\treturn bytes;\n\t}\n\treturn Math.round(bytes);\n}\n\n// __END__\n", "/*\nDescription: HTML Helpers\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { parseQueryString, getQueryStringParam, hasUrlParameter, getUrlParameter, updateUrlParameter, removeUrlParameter };\n\n/**\n * NOTE: this original code was wrong, now using URL and parsing through\n * getQueryStringParam\n * parses a query string from window.location.search.substring(1)\n * ALTERNATIVE CODE\n * var url = new URL(window.location.href);\n * param_uid = url.searchParams.get('uid');\n * @param {String} [query=''] the query string to parse, if not set will auto fill\n * @param {String} [return_key=''] if set only returns this key entry or empty for none\n * @param {Boolean} [single=false] if set to true then only the first found will be returned\n * @return {Object|String} parameter entry list\n */\nfunction parseQueryString(query = '', return_key = '', single = false)\n{\n\treturn getQueryStringParam(return_key, query, single);\n}\n\n/**\n * searches query parameters for entry and returns data either as string or array\n * if no search is given the whole parameters are returned as an object\n * if a parameter is set several times it will be returned as an array\n * if search parameter set and nothing found and empty string is returned\n * if no parametes exist and no serach is set and empty object is returned\n * @param {String} [search=''] if set searches for this entry, if empty all parameters are returned\n * @param {String} [query=''] different query string to parse, if not set (default) the current window href is used\n * @param {Boolean} [single=false] if set to true then only the first found will be returned\n * @return {Object|Array|String} if search is empty, object, if search is set\n * and only one entry, then string, else array\n * unless single is true\n */\nfunction getQueryStringParam(search = '', query = '', single = false)\n{\n\tif (!query) {\n\t\tquery = window.location.href;\n\t}\n\tconst url = new URL(query);\n\tlet param = null;\n\tif (search) {\n\t\tlet _params = url.searchParams.getAll(search);\n\t\tif (_params.length == 1 || single === true) {\n\t\t\tparam = _params[0];\n\t\t} else if (_params.length > 1) {\n\t\t\tparam = _params;\n\t\t}\n\t} else {\n\t\t// will be object, so declare it one\n\t\tparam = {};\n\t\t// loop over paramenters\n\t\tfor (const [key] of url.searchParams.entries()) {\n\t\t\t// check if not yet set\n\t\t\tif (typeof param[key] === 'undefined') {\n\t\t\t\t// get the parameters multiple\n\t\t\t\tlet _params = url.searchParams.getAll(key);\n\t\t\t\t// if 1 set as string, else attach array as is\n\t\t\t\tparam[key] = _params.length < 2 || single === true ?\n\t\t\t\t\t_params[0] :\n\t\t\t\t\t_params;\n\t\t\t}\n\t\t}\n\t}\n\treturn param;\n}\n\n/**\n * Check if key exists as URL parameter\n * @param {String} key URL parameter to search\n * @returns {Boolean} True if key exists\n */\nfunction hasUrlParameter(key)\n{\n\tvar urlParams = new URLSearchParams(window.location.search);\n\treturn urlParams.has(key);\n}\n\n/**\n * Return one value for a URL paramter or null if not found\n * @param {String} key Which URL parameter to get\n * @returns {String|Null} URL parameter content\n */\nfunction getUrlParameter(key)\n{\n\tvar urlParams = new URLSearchParams(window.location.search);\n\treturn urlParams.get(key);\n}\n\n/**\n * Add or update a query parameter in the current URL and update the browser's address bar\n * @param {string} key - The parameter name to add or update\n * @param {string} value - The value to set for the parameter\n * @param {boolean} [reload=false] - Whether to reload the page after updating the URL\n */\nfunction updateUrlParameter(key, value, reload = false)\n{\n\t// Create a URL object from the current URL\n\tconst url = new URL(window.location.href);\n\n\t// Set or update the parameter\n\turl.searchParams.set(key, value);\n\n\tconst newUrl = url.toString();\n\n\t// Update the browser's address bar without reloading the page\n\twindow.history.pushState({ path: newUrl }, '', newUrl);\n\n\t// Optionally reload the page\n\tif (reload) {\n\t\t// window.location.href = newUrl;\n\t\twindow.location.reload();\n\t}\n}\n\n/**\n * Remove a parameter from the current URL and update the browser's address bar\n * @param {string} key - The parameter name to remove\n * @param {boolean} [reload=false] - Whether to reload the page after updating the URL\n */\nfunction removeUrlParameter(key, reload = false)\n{\n\t// Create a URL object from the current URL\n\tconst url = new URL(window.location.href);\n\n\t// Remove the parameter if it exists\n\turl.searchParams.delete(key);\n\n\t// Update the browser's address bar without reloading the page\n\twindow.history.pushState({}, '', url.toString());\n\n\t// Optionally reload the page\n\tif (reload) {\n\t\twindow.location.reload();\n\t}\n}\n\n// __EMD__\n", "/*\nDescription: Login access and menu\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { loginLogout };\n\n/**\n * submits basic data for form logout\n */\nfunction loginLogout()\n{\n\tconst form = document.createElement('form');\n\tform.method = 'post';\n\tconst hiddenField = document.createElement('input');\n\thiddenField.type = 'hidden';\n\thiddenField.name = 'login_logout';\n\thiddenField.value = 'Logout';\n\tform.appendChild(hiddenField);\n\tdocument.body.appendChild(form);\n\tform.submit();\n}\n\n// __END__\n", "/*\nDescription: Action Indicator and Overlay\nDate: 2025/2/7\nCreator: Clemens Schwaighofer\n*/\n\nimport { setCenter } from './ResizingAndMove.mjs';\nexport {\n\tActionIndicatorOverlayBox,\n\tactionIndicator, actionIndicatorShow, actionIndicatorHide, overlayBoxShow,\n\toverlayBoxHide, setOverlayBox, hideOverlayBox, ClearCall\n};\n\n/*************************************************************\n * OLD action indicator and overlay boxes calls\n * DO NOT USE\n * actionIndicator -> showActionIndicator\n * actionIndicator -> hideActionIndicator\n * actionIndicatorShow -> showActionIndicator\n * actionIndicatorHide -> hideActionIndicator\n * overlayBoxShow -> showOverlayBoxLayers\n * overlayBoxHide -> hideOverlayBoxLayers\n * setOverlayBox -> showOverlayBoxLayers\n * hideOverlayBox -> hideOverlayBoxLayers\n * ClearCall -> clearCallActionBox\n * ***********************************************************/\n\n/**\n * show or hide the \"do\" overlay\n * @param {String} loc location name for action indicator\n * default empty. for console.log\n * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block\n * @deprecated showActionIndicator, hideActionIndicator\n */\nfunction actionIndicator(loc, overlay = true)\n{\n\tif ($('#indicator').is(':visible')) {\n\t\tthis.actionIndicatorHide(loc, overlay);\n\t} else {\n\t\tthis.actionIndicatorShow(loc, overlay);\n\t}\n}\n\n/**\n * explicit show for action Indicator\n * instead of automatically show or hide, do on command show\n * @param {String} loc location name for action indicator\n * default empty. for console.log\n * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block\n * @deprecated showActionIndicator, hideActionIndicator\n */\nfunction actionIndicatorShow(loc, overlay = true)\n{\n\t// console.log('{Indicator}: SHOW [%s]', loc);\n\tif (!$('#indicator').is(':visible')) {\n\t\tif (!$('#indicator').hasClass('progress')) {\n\t\t\t$('#indicator').addClass('progress');\n\t\t}\n\t\tsetCenter('indicator', true, true);\n\t\t$('#indicator').show();\n\t}\n\tif (overlay === true) {\n\t\tthis.overlayBoxShow();\n\t}\n}\n\n/**\n * explicit hide for action Indicator\n * instead of automatically show or hide, do on command hide\n * @param {String} loc location name for action indicator\n * default empty. for console.log\n * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block\n * @deprecated hideActionIndicator\n */\nfunction actionIndicatorHide(loc, overlay = true)\n{\n\t// console.log('{Indicator}: HIDE [%s]', loc);\n\t$('#indicator').hide();\n\tif (overlay === true) {\n\t\toverlayBoxHide();\n\t}\n}\n\n/**\n * shows the overlay box or if already visible, bumps the zIndex to 100\n * @deprecated showOverlayBoxLayers\n */\nfunction overlayBoxShow()\n{\n\t// check if overlay box exists and if yes set the z-index to 100\n\tif ($('#overlayBox').is(':visible')) {\n\t\t$('#overlayBox').css('zIndex', '100');\n\t} else {\n\t\t$('#overlayBox').show();\n\t\t$('#overlayBox').css('zIndex', '98');\n\t}\n}\n\n/**\n * hides the overlay box or if zIndex is 100 bumps it down to previous level\n * @deprecated hideOverlayBoxLayers\n */\nfunction overlayBoxHide()\n{\n\t// if the overlay box z-index is 100, do no hide, but set to 98\n\tif (parseInt($('#overlayBox').css('zIndex')) >= 100) {\n\t\t$('#overlayBox').css('zIndex', '98');\n\t} else {\n\t\t$('#overlayBox').hide();\n\t}\n}\n\n/**\n * position the overlay block box and shows it\n * @deprecated showOverlayBoxLayers\n */\nfunction setOverlayBox()\n{\n\tif (!$('#overlayBox').is(':visible')) {\n\t\t$('#overlayBox').show();\n\t}\n}\n\n/**\n * opposite of set, always hides overlay box\n * @deprecated hideOverlayBoxLayers\n */\nfunction hideOverlayBox()\n{\n\tif ($('#overlayBox').is(':visible')) {\n\t\t$('#overlayBox').hide();\n\t}\n}\n\n/**\n * the abort call, clears the action box and hides it and the overlay box\n * @deprecated clearCallActionBox\n */\nfunction ClearCall()\n{\n\t$('#actionBox').html('');\n\t$('#actionBox').hide();\n\t$('#overlayBox').hide();\n}\n\n/*\nThe below class will need the following CSS set\n\nProgress indicator (#indicator):\n.progress {\n\twidth: 100px;\n\theight: 100px;\n\tbackground: rgba(255, 255, 255, 0.6);\n\tborder: 20px solid rgba(255, 255, 255 ,0.25);\n\tborder-left-color: rgba(3, 155, 229 ,1);\n\tborder-top-color: rgba(3, 155, 229 ,1);\n\tborder-radius: 50%;\n\tdisplay: inline-block;\n\tanimation: progress-move 600ms infinite linear;\n\tleft: 0;\n\ttop: 0;\n\tposition: absolute;\n\tz-index: 1000;\n}\n@keyframes progress-move {\n\tto {\n\t\ttransform: rotate(1turn)\n\t}\n}\n\nOverlay box darken background (#overlayBox):\n.overlayBoxElement {\n\tbackground-color: rgba(0, 0, 0, 0.3);\n\theight: 100%;\n\tleft: 0;\n\tposition: fixed;\n\ttop: 0;\n\twidth: 100%;\n\tz-index: 98;\n}\n*/\n\nclass ActionIndicatorOverlayBox {\n\n\t// open overlay boxes counter for z-index\n\t#GL_OB_S = 100;\n\t#GL_OB_BASE = 100;\n\n\t/**\n\t * show action indicator\n\t * - checks if not existing and add\n\t * - only shows if not visible (else ignore)\n\t * - overlaybox check is called and shown on a fixzed\n\t * zIndex of 1000\n\t * - indicator is page centered\n\t * @param {String} loc ID string, only used for console log\n\t */\n\tshowActionIndicator(loc) // eslint-disable-line no-unused-vars\n\t{\n\t\t// console.log('{Indicator}: SHOW [%s]', loc);\n\t\t// check if indicator element exists\n\t\tif ($('#indicator').length == 0) {\n\t\t\tvar el = document.createElement('div');\n\t\t\tel.className = 'progress hide';\n\t\t\tel.id = 'indicator';\n\t\t\t$('body').append(el);\n\t\t} else if (!$('#indicator').hasClass('progress')) {\n\t\t\t// if I add a class it will not be hidden anymore\n\t\t\t// hide it\n\t\t\t$('#indicator').addClass('progress').hide();\n\t\t}\n\t\t// indicator not visible\n\t\tif (!$('#indicator').is(':visible')) {\n\t\t\t// check if overlay box element exits\n\t\t\tthis.checkOverlayExists();\n\t\t\t// if not visible show\n\t\t\tif (!$('#overlayBox').is(':visible')) {\n\t\t\t\t$('#overlayBox').show();\n\t\t\t}\n\t\t\t// always set to 1000 zIndex to be top\n\t\t\t$('#overlayBox').css('zIndex', 1000);\n\t\t\t// show indicator\n\t\t\t$('#indicator').show();\n\t\t\t// center it\n\t\t\tsetCenter('indicator', true, true);\n\t\t}\n\t}\n\n\t/**\n\t * hide action indicator, if it is visiable\n\t * If the global variable GL_OB_S is > GL_OB_BASE then\n\t * the overlayBox is not hidden but the zIndex\n\t * is set to this value\n\t * @param {String} loc ID string, only used for console log\n\t */\n\thideActionIndicator(loc) // eslint-disable-line no-unused-vars\n\t{\n\t\t// console.log('{Indicator}: HIDE [%s]', loc);\n\t\t// check if indicator is visible\n\t\tif ($('#indicator').is(':visible')) {\n\t\t\t// hide indicator\n\t\t\t$('#indicator').hide();\n\t\t\t// if global overlay box count is > 0\n\t\t\t// then set it to this level and keep\n\t\t\tif (this.#GL_OB_S > this.#GL_OB_BASE) {\n\t\t\t\t$('#overlayBox').css('zIndex', this.#GL_OB_S);\n\t\t\t} else {\n\t\t\t\t// else hide overlay box and set zIndex to 0\n\t\t\t\t$('#overlayBox').hide();\n\t\t\t\t$('#overlayBox').css('zIndex', this.#GL_OB_BASE);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * checks if overlayBox exists, if not it is\n\t * added as hidden item at the body end\n\t */\n\tcheckOverlayExists()\n\t{\n\t\t// check if overlay box exists, if not create it\n\t\tif ($('#overlayBox').length == 0) {\n\t\t\tvar el = document.createElement('div');\n\t\t\tel.className = 'overlayBoxElement hide';\n\t\t\tel.id = 'overlayBox';\n\t\t\t$('body').append(el);\n\t\t}\n\t}\n\n\t/**\n\t * show overlay box\n\t * if not visible show and set zIndex to 10 (GL_OB_BASE)\n\t * if visible, add +1 to the GL_OB_S variable and\n\t * up zIndex by this value\n\t */\n\tshowOverlayBoxLayers(el_id)\n\t{\n\t\t// console.log('SHOW overlaybox: %s', GL_OB_S);\n\t\t// if overlay box is not visible show and set zIndex to 0\n\t\tif (!$('#overlayBox').is(':visible')) {\n\t\t\t$('#overlayBox').show();\n\t\t\t$('#overlayBox').css('zIndex', this.#GL_OB_BASE);\n\t\t\t// also set start variable to 0\n\t\t\tthis.#GL_OB_S = this.#GL_OB_BASE;\n\t\t}\n\t\t// up the overlay box counter by 1\n\t\tthis.#GL_OB_S ++;\n\t\t// set zIndex\n\t\t$('#overlayBox').css('zIndex', this.#GL_OB_S);\n\t\t// if element given raise zIndex and show\n\t\tif (el_id) {\n\t\t\tif ($('#' + el_id).length > 0) {\n\t\t\t\t$('#' + el_id).css('zIndex', this.#GL_OB_S + 1);\n\t\t\t\t$('#' + el_id).show();\n\t\t\t}\n\t\t}\n\t\t// console.log('SHOW overlaybox NEW zIndex: %s', $('#overlayBox').css('zIndex'));\n\t}\n\n\t/**\n\t * hide overlay box\n\t * lower GL_OB_S value by -1\n\t * if we are 10 (GL_OB_BASE) or below hide the overlayIndex\n\t * and set zIndex and GL_OB_S to 0\n\t * else just set zIndex to the new GL_OB_S value\n\t * @param {String} el_id Target to hide layer\n\t */\n\thideOverlayBoxLayers(el_id='')\n\t{\n\t\t// console.log('HIDE overlaybox: %s', GL_OB_S);\n\t\t// remove on layer\n\t\tthis.#GL_OB_S --;\n\t\t// if 0 or lower (overflow) hide it and\n\t\t// set zIndex to 0\n\t\tif (this.#GL_OB_S <= this.#GL_OB_BASE) {\n\t\t\tthis.#GL_OB_S = this.#GL_OB_BASE;\n\t\t\t$('#overlayBox').hide();\n\t\t\t$('#overlayBox').css('zIndex', this.#GL_OB_BASE);\n\t\t} else {\n\t\t\t// if OB_S > 0 then set new zIndex\n\t\t\t$('#overlayBox').css('zIndex', this.#GL_OB_S);\n\t\t}\n\t\tif (el_id) {\n\t\t\t$('#' + el_id).hide();\n\t\t\t$('#' + el_id).css('zIndex', 0);\n\t\t}\n\t\t// console.log('HIDE overlaybox NEW zIndex: %s', $('#overlayBox').css('zIndex'));\n\t}\n\n\t/**\n\t * only for single action box\n\t */\n\tclearCallActionBox()\n\t{\n\t\t$('#actionBox').html('');\n\t\t$('#actionBox').hide();\n\t\tthis.hideOverlayBoxLayers();\n\t}\n}\n\n\n// __END__\n", "/*\nDescription: Translation call\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { l10nTranslation };\nimport { isObject } from './JavaScriptHelpers.mjs';\n\nclass l10nTranslation {\n\n\t#i18n = {};\n\n\tconstructor(i18n) {\n\t\tthis.#i18n = i18n;\n\n\t}\n\t/**\n\t * uses the i18n object created in the translation template\n\t * that is filled from gettext in PHP\n\t * @param {String} string text to translate\n\t * @return {String} translated text (based on PHP selected language)\n\t */\n\t__(string)\n\t{\n\t\tif (typeof this.#i18n !== 'undefined' && isObject(this.#i18n) && this.#i18n[string]) {\n\t\t\treturn this.#i18n[string];\n\t\t} else {\n\t\t\treturn string;\n\t\t}\n\t}\n}\n\n// __END__\n", "/*\nDescription: Action Box handling\nDate: 2025/3/7\nCreator: Clemens Schwaighofer\n*/\n\nexport { ActionBox };\nimport { objectKeyExists, getObjectCount } from './JavaScriptHelpers.mjs';\nimport { exists } from './DomHelpers.mjs';\nimport { setCenter, getWindowSize } from './ResizingAndMove.mjs';\n\nclass ActionBox {\n\n\t// open overlay boxes counter for z-index\n\tzIndex = {\n\t\tbase: 100,\n\t\tmax: 110,\n\t\tindicator: 0,\n\t\tboxes: {},\n\t\tactive: [],\n\t\ttop: ''\n\t};\n\t// general action box storage\n\taction_box_storage = {};\n\t// set to 10 min (*60 for seconds, *1000 for microseconds)\n\taction_box_cache_timeout = 10 * 60 * 1000;\n\n\thec;\n\tl10n;\n\n\t/**\n\t * action box creator\n\t * @param {Object} hec HtmlElementCreator\n\t * @param {Object} l10n l10nTranslation\n\t */\n\tconstructor(hec, l10n)\n\t{\n\t\tthis.hec = hec;\n\t\tthis.l10n = l10n;\n\t}\n\n\t/**\n\t * Show an action box\n\t * @param {string} [target_id='actionBox'] where to attach content to, if not exists, create new\n\t * @param {string} [content=''] content to add to the box\n\t * @param {array} [action_box_css=[]] additional css elements for the action box\n\t * @param {number} [override=0] override size adjust\n\t * @param {number} [content_override=0] override content size adjust\n\t */\n\tshowFillActionBox(target_id = 'actionBox', content = '', action_box_css = [], override = 0, content_override = 0)\n\t{\n\t\t// fill content\n\t\tthis.fillActionBox(target_id, content, action_box_css);\n\t\t// show the box\n\t\tthis.showActionBox(target_id, override, content_override);\n\t}\n\n\t/**\n\t * Fill action box with content, create it if it does not existgs\n\t * @param {string} [target_id='actionBox'] where to attach content to, if not exists, create new\n\t * @param {string} [content=''] content to add to the box\n\t * @param {array} [action_box_css=[]] additional css elements for the action box\n\t */\n\tfillActionBox(target_id = 'actionBox', content = '', action_box_css = [])\n\t{\n\t\t// show action box, calc height + center\n\t\tif (!exists(target_id)) {\n\t\t\t// add at the bottom\n\t\t\t$('#mainContainer').after(\n\t\t\t\tthis.hec.phfo(this.hec.cel('div', target_id, '', ['actionBoxElement', 'hide'].concat(action_box_css)))\n\t\t\t);\n\t\t}\n\t\t// add the info box\n\t\t$('#' + target_id).html(content);\n\t}\n\n\t/**\n\t * Adjust the size of the action box\n\t * @param {string} [target_id='actionBox'] which actionBox to work on\n\t * @param {number} [override=0] override size adjust\n\t * @param {number} [content_override=0] override content size adjust\n\t */\n\tadjustActionBox(target_id = 'actionBox', override = 0, content_override = 0)\n\t{\n\t\t// adjust box size\n\t\tthis.adjustActionBoxHeight(target_id, override, content_override);\n\t\t// center the alert box\n\t\tsetCenter(target_id, true, true);\n\t}\n\n\t/**\n\t * hide any open action boxes and hide overlay\n\t */\n\thideAllActionBoxes()\n\t{\n\t\t// hide all action boxes that might exist\n\t\t$('#actionBox, div[id^=\"actionBox-\"].actionBoxElement').hide();\n\t\t// hideOverlayBoxLayers();\n\t\t$('#overlayBox').hide();\n\t}\n\n\t/**\n\t * hide action box, but do not clear content\n\t * DEPRECATED\n\t * @param {string} [target_id='actionBox']\n\t */\n\thideActionBox(target_id = 'actionBox')\n\t{\n\t\tthis.closeActionBoxFloat(target_id, false);\n\t}\n\n\t/**\n\t * Just show and adjust the box\n\t * DEPRECAED\n\t * @param {string} [target_id='actionBox'] which actionBox to work on\n\t * @param {number} [override=0] override size adjust\n\t * @param {number} [content_override=0] override content size adjust\n\t * @param {Boolean} [hide_all=false] if set to true, hide all other action boxes\n\t */\n\tshowActionBox(target_id = 'actionBox', override = 0, content_override = 0, hide_all = true)\n\t{\n\t\tthis.showActionBoxFloat(target_id, override, content_override, hide_all);\n\t}\n\n\t/**\n\t * close an action box with default clear content\n\t * for just hide use hideActionBox\n\t * DEPRECATED\n\t * @param {String} [target_id='actionBox'] which action box to close, default is set\n\t * @param {Boolean} [clean=true] if set to false will not remove html content, just hide\n\t */\n\tcloseActionBox(target_id = 'actionBox', clean = true)\n\t{\n\t\t// set the target/content ids\n\t\tthis.closeActionBoxFloat(target_id, clean);\n\t}\n\n\t/**\n\t * TODO: better stacked action box: OPEN\n\t * @param {string} [target_id='actionBox'] which actionBox to work on\n\t * @param {number} [override=0] override size adjust\n\t * @param {number} [content_override=0] override content size adjust\n\t * @param {boolean} [hide_all=false] if set to true, hide all other action boxes\n\t */\n\tshowActionBoxFloat(target_id = 'actionBox', override = 0, content_override = 0, hide_all = false)\n\t{\n\t\tif (hide_all === true) {\n\t\t\t// hide all action boxes if they are currently open\n\t\t\tthis.hideAllActionBoxes();\n\t\t}\n\t\t// if no box, created if\n\t\tif (!exists('overlayBox')) {\n\t\t\t$('body').prepend(this.hec.phfo(this.hec.cel('div', 'overlayBox', '', ['overlayBoxElement'])));\n\t\t\t$('#overlayBox').css('zIndex', this.zIndex.base);\n\t\t}\n\t\t// adjust zIndex so its above all other and set action box zindex +1\n\t\t$('#overlayBox').show();\n\t\tif (!objectKeyExists(this.zIndex.boxes, target_id)) {\n\t\t\tthis.zIndex.boxes[target_id] = this.zIndex.max;\n\t\t\t// increase by ten\n\t\t\tthis.zIndex.max += 10;\n\t\t} else if (this.zIndex.boxes[target_id] + 10 < this.zIndex.max) {\n\t\t\t// see if this is the highest level, if not move up and write no max zIndex\n\t\t\t// move it up to be the new top and move the others down\n\t\t\t// [loop, order by value]\n\t\t\t// current hack, increase max\n\t\t\tthis.zIndex.boxes[target_id] = this.zIndex.max;\n\t\t\tthis.zIndex.max += 10;\n\t\t}\n\t\t// make sure the overlayBox is one level below this\n\t\t// unless there is an active \"indicator\" index\n\t\tif (!this.zIndex.indicator) {\n\t\t\t$('#overlayBox').css('zIndex', this.zIndex.boxes[target_id] - 1);\n\t\t}\n\t\t$('#' + target_id).css('zIndex', this.zIndex.boxes[target_id]).show();\n\t\t// set target to this new level\n\t\t// @ts-ignore\n\t\tif (this.zIndex.active.indexOf(target_id) == -1) {\n\t\t\t// @ts-ignore\n\t\t\tthis.zIndex.active.push(target_id);\n\t\t}\n\t\tthis.zIndex.top = target_id;\n\t\t// adjust size\n\t\tthis.adjustActionBox(target_id, override, content_override);\n\t}\n\n\t/**\n\t * TODO: better stacked action box: CLOSE\n\t * @param {String} [target_id='actionBox'] which action box to close, default is set\n\t * @param {Boolean} [clean=true] if set to false will not remove html content, just hide\n\t */\n\tcloseActionBoxFloat(target_id = 'actionBox', clean = true)\n\t{\n\t\t// do nothing if this does not exist\n\t\tif (!exists(target_id)) {\n\t\t\treturn;\n\t\t}\n\t\t// clear storage object\n\t\tif (\n\t\t\tobjectKeyExists(this.action_box_storage, target_id) && clean === true\n\t\t) {\n\t\t\tthis.action_box_storage[target_id] = {};\n\t\t}\n\t\tif (clean === true) {\n\t\t\t$('#' + target_id).html('');\n\t\t}\n\t\t$('#' + target_id).hide();\n\t\t// remove from active list\n\t\t// @ts-ignore\n\t\tlet idx = this.zIndex.active.indexOf(target_id);\n\t\tthis.zIndex.active.splice(idx, 1);\n\t\t// do we have any visible action boxes.\n\t\t// find the highest zIndex and set overlayBox to this -1\n\t\t// @ts-ignore\n\t\tlet visible_zIndexes = $('#actionBox:visible, div[id^=\"actionBox-\"].actionBoxElement:visible').map((i, el) => ({\n\t\t\tid: el.id,\n\t\t\tzIndex: $('#' + el.id).css('zIndex')\n\t\t})).get();\n\t\tif (visible_zIndexes.length > 0) {\n\t\t\tlet max_zIndex = 0;\n\t\t\tlet max_el_id = '';\n\t\t\tfor (let zIndex_el of visible_zIndexes) {\n\t\t\t\tif (parseInt(zIndex_el.zIndex) > max_zIndex) {\n\t\t\t\t\tmax_zIndex = parseInt(zIndex_el.zIndex);\n\t\t\t\t\tmax_el_id = zIndex_el.id;\n\t\t\t\t}\n\t\t\t}\n\t\t\t$('#overlayBox').css('zIndex', max_zIndex - 1);\n\t\t\tthis.zIndex.top = max_el_id;\n\t\t} else {\n\t\t\t$('#overlayBox').hide();\n\t\t}\n\t}\n\n\t/**\n\t * create a new action box and fill it with basic elements\n\t * @param {String} [target_id='actionBox']\n\t * @param {String} [title='']\n\t * @param {Object} [contents={}]\n\t * @param {Object} [headers={}]\n\t * @param {Boolean} [show_close=true]\n\t * @param {Object} [settings={}] Optional settings, eg style sheets\n\t */\n\tcreateActionBox(\n\t\ttarget_id = 'actionBox',\n\t\ttitle = '',\n\t\tcontents = {},\n\t\theaders = {},\n\t\tsettings = {},\n\t\tshow_close = true\n\t) {\n\t\tif (!objectKeyExists(this.action_box_storage, target_id)) {\n\t\t\tthis.action_box_storage[target_id] = {};\n\t\t}\n\t\t// settings can have the following\n\t\t// : header_css:[]\n\t\t// : action_box_css:[]\n\t\tlet header_css = [];\n\t\tif (objectKeyExists(settings, 'header_css')) {\n\t\t\theader_css = settings.header_css;\n\t\t}\n\t\tlet action_box_css = [];\n\t\tif (objectKeyExists(settings, 'action_box_css')) {\n\t\t\taction_box_css = settings.action_box_css;\n\t\t}\n\t\tlet elements = [];\n\t\t// add title + close button to actionBox\n\t\telements.push(this.hec.phfo(\n\t\t\tthis.hec.aelx(this.hec.cel('div', target_id + '_title', '', ['actionBoxTitle', 'flx-spbt'].concat(header_css)),\n\t\t\t\t...show_close === true ? [\n\t\t\t\t\t// title\n\t\t\t\t\tthis.hec.cel('div', '', title, ['fs-b', 'w-80']),\n\t\t\t\t\t// close button\n\t\t\t\t\tthis.hec.aelx(this.hec.cel('div', target_id + '_title_close_button', '', ['w-20', 'tar']),\n\t\t\t\t\t\tthis.hec.cel('input', target_id + '_title_close', '', ['button-close', 'fs-s'],\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 'button',\n\t\t\t\t\t\t\t\tvalue: this.l10n.__('Close'),\n\t\t\t\t\t\t\t\tOnClick: 'closeActionBox(\\'' + target_id + '\\', false);'\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t] : [\n\t\t\t\t\tthis.hec.cel('div', '', title, ['fs-b', 'w-100'])\n\t\t\t\t]\n\t\t\t)\n\t\t));\n\t\t// if we have header content, add that here\n\t\tif (getObjectCount(headers) > 0) {\n\t\t\t// if the element has an entry called \"raw_string\" then this does not need to be converted\n\t\t\tif (objectKeyExists(headers, 'raw_string')) {\n\t\t\t\telements.push(headers.raw_string);\n\t\t\t} else {\n\t\t\t\telements.push(this.hec.phfo(headers));\n\t\t\t}\n\t\t}\n\t\t// main content part (this should NOT be empty), if empty, add empty _content block\n\t\tif (getObjectCount(contents) > 0) {\n\t\t\t// if the element has an entry called \"raw_string\" then this does not need to be converted\n\t\t\tif (objectKeyExists(contents, 'raw_string')) {\n\t\t\t\telements.push(contents.raw_string);\n\t\t\t} else {\n\t\t\t\telements.push(this.hec.phfo(contents));\n\t\t\t}\n\t\t} else {\n\t\t\telements.push(this.hec.phfo(this.hec.cel('div', target_id + '_content', '', [])));\n\t\t}\n\t\t// footer clear call\n\t\telements.push(this.hec.phfo(\n\t\t\tthis.hec.aelx(this.hec.cel('div', target_id + '_footer', '', ['pd-5', 'flx-spbt']),\n\t\t\t\t...show_close === true ? [\n\t\t\t\t\t// dummy spacer\n\t\t\t\t\tthis.hec.cel('div', '', '', ['fs-b', 'w-80']),\n\t\t\t\t\t// close button\n\t\t\t\t\tthis.hec.aelx(this.hec.cel('div', target_id + '_footer_close_button', '', ['tar', 'w-20']),\n\t\t\t\t\t\tthis.hec.cel('input', target_id + '_footer_close', '', ['button-close', 'fs-s'],\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: 'button',\n\t\t\t\t\t\t\t\tvalue: this.l10n.__('Close'),\n\t\t\t\t\t\t\t\tOnClick: 'closeActionBox(\\'' + target_id + '\\', false);'\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t] : [\n\t\t\t\t\tthis.hec.cel('div', '', '', ['fs-b', 'w-100'])\n\t\t\t\t]\n\t\t\t)\n\t\t));\n\t\telements.push(this.hec.phfo(this.hec.cel('input', target_id + '-cache_time', '', [], {\n\t\t\ttype: 'hidden',\n\t\t\tvalue: Date.now()\n\t\t})));\n\t\tthis.fillActionBox(target_id, elements.join(''), action_box_css);\n\t}\n\n\t/**\n\t * adjusts the action box height based on content and window height of browser\n\t * TODO: border on outside/and other margin things need to be added in overall adjustment\n\t * @param {String} [target_id='actionBox'] target id, if not set, fall back to default\n\t * @param {Number} [override=0] override value to add to the actionBox height\n\t * @param {Number} [content_override=0] override the value from _content block if it exists\n\t */\n\tadjustActionBoxHeight(target_id = 'actionBox', override = 0, content_override = 0)\n\t{\n\t\tvar new_height = 0;\n\t\tvar dim = {};\n\t\tvar abc_dim = {};\n\t\tvar content_id = '';\n\t\t// make sure it is a number\n\t\tif (isNaN(override)) {\n\t\t\toverride = 0;\n\t\t}\n\t\tif (isNaN(content_override)) {\n\t\t\tcontent_override = 0;\n\t\t}\n\t\t// set the target/content ids\n\t\tswitch (target_id) {\n\t\t\tcase 'actionBox':\n\t\t\t\tcontent_id = 'action_box';\n\t\t\t\tbreak;\n\t\t\tcase 'actionBoxSub':\n\t\t\t\tcontent_id ='action_box_sub';\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tcontent_id = target_id;\n\t\t\t\tbreak;\n\t\t}\n\t\t// first remove any height, left, top style entris from target and content blocks\n\t\t// @ts-ignore\n\t\t$.each([target_id, content_id + '_content'], function(i, v) {\n\t\t\t$('#' + v).css({\n\t\t\t\t'height': '',\n\t\t\t\t'width': ''\n\t\t\t});\n\t\t});\n\t\tif (exists(content_id + '_title')) {\n\t\t\tdim.height = $('#' + content_id + '_title').outerHeight();\n\t\t\tconsole.log('Target: %s, Action box Title: %s', target_id, dim.height);\n\t\t\tnew_height += dim.height ?? 0;\n\t\t}\n\t\tif (exists(content_id + '_header')) {\n\t\t\tdim.height = $('#' + content_id + '_header').outerHeight();\n\t\t\tconsole.log('Target: %s, Action box Header: %s', target_id, dim.height);\n\t\t\tnew_height += dim.height ?? 0;\n\t\t}\n\t\tif (exists(content_id + '_content')) {\n\t\t\tif (content_override > 0) {\n\t\t\t\tconsole.log('Target: %s, Action box Content Override: %s', target_id, content_override);\n\t\t\t\tnew_height += content_override;\n\t\t\t} else {\n\t\t\t\tabc_dim.height = $('#' + content_id + '_content').outerHeight();\n\t\t\t\tconsole.log('Target: %s, Action box Content: %s', target_id, abc_dim.height);\n\t\t\t\tnew_height += abc_dim.height ?? 0;\n\t\t\t}\n\t\t}\n\t\t// always there sets\n\t\tif (exists(content_id + '_footer')) {\n\t\t\tdim.height = $('#' + content_id + '_footer').outerHeight();\n\t\t\tconsole.log('Target: %s, Action box Footer: %s', target_id, dim.height);\n\t\t\tnew_height += dim.height ?? 0;\n\t\t}\n\t\t// get difference for the rest from outer box\n\t\t// console.log('Target: %s, Action box outer: %s, Content: %s, New: %s', target_id, $('#' + target_id).outerHeight(), $('#' + content_id + '_content').outerHeight(), new_height);\n\t\t// new_height += ($('#' + target_id).outerHeight() - new_height) + override;\n\t\tnew_height += override;\n\t\t// get border width top-bottom from action Box, we need to remove this from the final height\n\t\t// console.log('Target: %s, Border top: %s', target_id, $('#' + target_id).css('border-top-width'));\n\t\t// get window size and check if content is bigger\n\t\tvar viewport = getWindowSize();\n\t\tif (new_height >= viewport.height) {\n\t\t\t// resize the action box content and set overflow [of-s-y]\n\t\t\tif (exists(content_id + '_content')) {\n\t\t\t\tif (!$('#' + content_id + '_content').hasClass('of-s-y')) {\n\t\t\t\t\t$('#' + content_id + '_content').addClass('of-s-y');\n\t\t\t\t}\n\t\t\t}\n\t\t\tconsole.log('Target: %s, Viewport: %s, ActionBox (NH): %s, ABcontent: %s, ABouter: %s', target_id, viewport.height, new_height, abc_dim.height, $('#' + target_id).outerHeight());\n\t\t\t// the height off window - all - action box gives new action box height\n\t\t\tvar m_height = viewport.height - (new_height - (abc_dim.height ?? 0));\n\t\t\tconsole.log('Target: %s, New ABcontent: %s', target_id, m_height);\n\t\t\t$('#' + content_id + '_content').css('height', m_height + 'px');\n\t\t\tnew_height = new_height - (abc_dim.height ?? 0) + m_height;\n\t\t\tconsole.log('Target: %s, New Hight: %s', target_id, new_height);\n\t\t} else {\n\t\t\t// if size ok, check if overflow scoll is set, remove it\n\t\t\tif (exists(content_id + '_content')) {\n\t\t\t\tif ($('#' + content_id + '_content').hasClass('of-s-y')) {\n\t\t\t\t\t$('#' + content_id + '_content').removeClass('of-s-y');\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconsole.log('Target: %s, Action Box new height: %s px (override %s px, content override %s px), window height: %s px, Visible Height: %s px', target_id, new_height, override, content_override, viewport.height, $('#' + content_id).outerHeight());\n\t\t// adjust height\n\t\t$('#' + target_id).css('height', new_height + 'px');\n\t}\n}\n\n// __EMD__\n", "/*\nDescription: Login access and menu\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { LoginNavMenu };\nimport { isObject, getObjectCount } from './JavaScriptHelpers.mjs';\nimport { exists } from './DomHelpers.mjs';\n\nclass LoginNavMenu {\n\n\thec;\n\tl10n;\n\n\t/**\n\t * action box creator\n\t * @param {Object} hec HtmlElementCreator\n\t * @param {Object} l10n l10nTranslation\n\t */\n\tconstructor(hec, l10n)\n\t{\n\t\tthis.hec = hec;\n\t\tthis.l10n = l10n;\n\t}\n\n\t/**\n\t * create login string and logout button elements\n\t * @param {String} login_string the login string to show on the left\n\t * @param {String} [header_id='mainHeader'] the target for the main element block\n\t * if not set mainHeader is assumed\n\t * this is the target div for the \"loginRow\"\n\t */\n\tcreateLoginRow(login_string, header_id = 'mainHeader')\n\t{\n\t\t// if header does not exist, we do nothing\n\t\tif (exists(header_id)) {\n\t\t\t// that row must exist already, if not it must be the first in the \"mainHeader\"\n\t\t\tif (!exists('loginRow')) {\n\t\t\t\t$('#' + header_id).html(this.hec.phfo(this.hec.cel('div', 'loginRow', '', ['loginRow', 'flx-spbt'])));\n\t\t\t}\n\t\t\t// clear out just in case for first entry\n\t\t\t// fill with div name & login/logout button\n\t\t\t$('#loginRow').html(this.hec.phfo(this.hec.cel('div', 'loginRow-name', login_string)));\n\t\t\t$('#loginRow').append(this.hec.phfo(this.hec.cel('div', 'loginRow-info', '')));\n\t\t\t$('#loginRow').append(this.hec.phfo(\n\t\t\t\tthis.hec.aelx(\n\t\t\t\t\t// outer div\n\t\t\t\t\tthis.hec.cel('div', 'loginRow-logout'),\n\t\t\t\t\t// inner element\n\t\t\t\t\tthis.hec.cel('input', 'logout', '', [], {\n\t\t\t\t\t\tvalue: this.l10n.__('Logout'),\n\t\t\t\t\t\ttype: 'button',\n\t\t\t\t\t\tonClick: 'loginLogout()'\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t));\n\t\t}\n\t}\n\n\t/**\n\t * create the top nav menu that switches physical between pages\n\t * (edit access data based)\n\t * @param {Object} nav_menu the built nav menu with highlight info\n\t * @param {String} [header_id='mainHeader'] the target for the main element block\n\t * if not set mainHeader is assumed\n\t * this is the target div for the \"menuRow\"\n\t */\n\tcreateNavMenu(nav_menu, header_id = 'mainHeader')\n\t{\n\t\t// must be an object\n\t\tif (isObject(nav_menu) && getObjectCount(nav_menu) > 1) {\n\t\t\t// do we have more than one entry, if not, do not show (single page)\n\t\t\tif (!exists('menuRow')) {\n\t\t\t\t$('#' + header_id).html(this.hec.phfo(this.hec.cel('div', 'menuRow', '', ['menuRow', 'flx-s'])));\n\t\t\t}\n\t\t\tvar content = [];\n\t\t\t$.each(nav_menu, function(key, item) {\n\t\t\t\t// key is number\n\t\t\t\t// item is object with entries\n\t\t\t\tif (key != 0) {\n\t\t\t\t\tcontent.push(this.hec.phfo(this.hec.cel('div', '', '·', ['pd-2'])));\n\t\t\t\t}\n\t\t\t\t// ignore item.popup for now\n\t\t\t\tif (item.enabled) {\n\t\t\t\t\t// set selected based on window.location.href as the php set will not work\n\t\t\t\t\tif (window.location.href.indexOf(item.url) != -1) {\n\t\t\t\t\t\titem.selected = 1;\n\t\t\t\t\t}\n\t\t\t\t\t// create the entry\n\t\t\t\t\tcontent.push(this.hec.phfo(\n\t\t\t\t\t\tthis.hec.aelx(\n\t\t\t\t\t\t\tthis.hec.cel('div'),\n\t\t\t\t\t\t\tthis.hec.cel('a', '', item.name, ['pd-2'].concat(item.selected ? 'highlight': ''), {\n\t\t\t\t\t\t\t\thref: item.url\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t)\n\t\t\t\t\t));\n\t\t\t\t}\n\t\t\t});\n\t\t\t$('#menuRow').html(content.join(''));\n\t\t} else {\n\t\t\t$('#menuRow').hide();\n\t\t}\n\t}\n\n}\n\n// __END__\n", "/*\n * general edit javascript\n * former name: edit.jq.js\n * This is the jquery version\n * NOTE: jquey parts will be deprecated\n*/\n\nimport {\n\terrorCatch as _errorCatch,\n\tisFunction as _isFunction,\n\texecuteFunctionByName as _executeFunctionByName,\n\tisObject as _isObject,\n\tgetObjectCount as _getObjectCount,\n\tkeyInObject as _keyInObject,\n\tgetKeyByValue as _getKeyByValue,\n\tvalueInObject as _valueInObject,\n\tdeepCopyFunction as _deepCopyFunction\n} from './utils/JavaScriptHelpers.mjs';\nimport {\n\tescapeHtml as _escapeHtml,\n\tunescapeHtml as _unescapeHtml,\n\thtml_options as _html_options,\n\thtml_options_block as _html_options_block,\n\thtml_options_refill as _html_options_refill\n} from './utils/HtmlHelpers.mjs';\nimport {\n\tloadEl as _loadEl,\n\tpop as _pop,\n\texpandTA as _expandTA,\n\texists as _exists\n} from './utils/DomHelpers.mjs';\nimport {\n\tdec2hex as _dec2hex,\n\tgetRandomIntInclusive as _getRandomIntInclusive,\n\troundPrecision as _roundPrecision\n} from './utils/MathHelpers.mjs';\nimport {\n\tformatString as _formatString,\n\tnumberWithCommas as _numberWithCommas,\n\tconvertLBtoBR as _convertLBtoBR\n} from './utils/StringHelpers.mjs';\nimport {\n\tgetTimestamp as _getTimestamp\n} from './utils/DateTimeHelpers.mjs';\nimport {\n\tgenerateId as _generateId,\n\trandomIdF as _randomIdF,\n} from './utils/UniqIdGenerators.mjs';\nimport {\n\tgetWindowSize as _getWindowSize,\n\tgetScrollOffset as _getScrollOffset,\n\tgetScrollOffsetOpener as _getScrollOffsetOpener,\n\tsetCenter as _setCenter,\n\tgoToPos as _goToPos,\n\tgoTo as _goTo\n} from './utils/ResizingAndMove.mjs';\nimport {\n\tformatBytes as _formatBytes,\n\tformatBytesLong as _formatBytesLong,\n\tstringByteFormat as _stringByteFormat\n} from './utils/FormatBytes.mjs';\nimport {\n\tparseQueryString as _parseQueryString,\n\tgetQueryStringParam as _getQueryStringParam,\n\thasUrlParameter as _hasUrlParameter,\n\tgetUrlParameter as _getUrlParameter,\n\tupdateUrlParameter as _updateUrlParameter,\n\tremoveUrlParameter as _removeUrlParameter,\n} from './utils/UrlParser.mjs';\nimport {\n\tloginLogout as _loginLogout,\n} from './utils/LoginLogout.mjs';\nimport {\n\tActionIndicatorOverlayBox,\n\tactionIndicator as _actionIndicator,\n\tactionIndicatorShow as _actionIndicatorShow,\n\tactionIndicatorHide as _actionIndicatorHide,\n\toverlayBoxShow as _overlayBoxShow,\n\toverlayBoxHide as _overlayBoxHide,\n\tsetOverlayBox as _setOverlayBox,\n\thideOverlayBox as _hideOverlayBox,\n\tClearCall as _ClearCall\n} from './utils/ActionIndicatorOverlayBox.mjs';\nimport { l10nTranslation } from './utils/l10nTranslation.mjs';\nimport { HtmlElementCreator } from './utils/HtmlElementCreator.mjs';\nimport { ActionBox } from './utils/ActionBox.mjs';\nimport { LoginNavMenu } from './utils/LoginNavMenu.mjs';\n\nlet aiob = new ActionIndicatorOverlayBox();\nlet hec = new HtmlElementCreator();\n// if ( undef === \"undefined\") {\n// @ts-ignore\n// eslint-disable-next-line no-undef\nlet l10n = new l10nTranslation(typeof i18n === \"undefined\" ? {} : i18n);\nlet ab = new ActionBox(hec, l10n);\nlet lnm = new LoginNavMenu(hec, l10n);\n\n// MARK: deprecated String/Number override\n\n/**\n * simple sprintf formater for replace\n * usage: \"{0} is cool, {1} is not\".format(\"Alpha\", \"Beta\");\n * First, checks if it isn't implemented yet.\n * @param {String} String.prototype.format string with elements to be replaced\n * @return {String} Formated string\n * @deprecated StringHelpers.formatString\n */\n// @ts-ignore\nif (!String.prototype.format) {\n\t// @ts-ignore\n\tString.prototype.format = function()\n\t{\n\t\tconsole.error('[DEPRECATED] use StringHelpers.formatString');\n\t\t// @ts-ignore\n\t\treturn _formatString(this, arguments);\n\t};\n}\n\n/**\n * round to digits (float)\n * @param {Number} Number.prototype.round Float type number to round\n * @param {Number} prec Precision to round to\n * @return {Float} Rounded number\n * @deprecated use MathHelpers.roundPrecision\n */\n// @ts-ignore\nif (Number.prototype.round) {\n\t// @ts-ignore\n\tNumber.prototype.round = function (prec) {\n\t\tconsole.error('[DEPRECATED] use MathHelpers.roundPrecision');\n\t\t// @ts-ignore\n\t\treturn _roundPrecision(this, prec);\n\t};\n}\n\n/**\n * escape HTML string\n * @param {String} String.prototype.escapeHTML HTML data string to be escaped\n * @return {String} escaped string\n * @deprecated use HtmlHelpers.escapeHtml\n */\n// @ts-ignore\nif (!String.prototype.escapeHTML) {\n\t// @ts-ignore\n\tString.prototype.escapeHTML = function() {\n\t\tconsole.error('[DEPRECATED] use HtmlHelpers.escapeHtml');\n\t\t// @ts-ignore\n\t\treturn _escapeHtml(this);\n\t};\n}\n\n/**\n * unescape a HTML encoded string\n * @param {String} String.prototype.unescapeHTML data with escaped entries\n * @return {String} HTML formated string\n * @deprecated use HtmlHelpers.unescapeHtml\n */\n// @ts-ignore\nif (!String.prototype.unescapeHTML) {\n\t// @ts-ignore\n\tString.prototype.unescapeHTML = function() {\n\t\tconsole.error('[DEPRECATED] use HtmlHelpers.unescapeHtml');\n\t\t// @ts-ignore\n\t\treturn _unescapeHtml(this);\n\t};\n}\n\n// MARK: general collection\n\n/**\n *\n * @param {String} string\n * @returns {String}\n */\n// @ts-ignore\nfunction escapeHtml(string) // eslint-disable-line no-unused-vars\n{\n\treturn _escapeHtml(string);\n}\n\n/**\n * round to digits (float)\n * @param {Number} number Float type number to round\n * @param {Number} prec Precision to round to\n * @return {Number} Rounded number\n */\n// @ts-ignore\nfunction roundPrecision(number, prec) // eslint-disable-line no-unused-vars\n{\n\treturn _roundPrecision(number, prec);\n}\n\n/**\n * simple sprintf formater for replace\n * usage: \"{0} is cool, {1} is not\".format(\"Alpha\", \"Beta\");\n * First, checks if it isn't implemented yet.\n * @param {String} string String with elements to be replaced\n * @return {String} Formated string\n * @deprecated StringHelpe\n */\n// @ts-ignore\nfunction formatString(string, ...args) // eslint-disable-line no-unused-vars\n{\n\treturn _formatString(string, ...args);\n}\n\n/**\n *\n * @param {String} string\n * @returns {String}\n */\n// @ts-ignore\nfunction unescapeHtml(string) // eslint-disable-line no-unused-vars\n{\n\treturn _unescapeHtml(string);\n}\n\n/**\n * Gets html element or throws an error\n * @param {string} el_id Element ID to get\n * @returns {HTMLElement}\n * @throws Error\n */\n// @ts-ignore\nfunction loadEl(el_id) // eslint-disable-line no-unused-vars\n{\n\treturn _loadEl(el_id);\n}\n\n/**\n * opens a pop_ window with winName and given features (string)\n * @param {String} theURL the url\n * @param {String} winName window name\n * @param {Object} features pop_ features\n */\n// @ts-ignore\nfunction pop(theURL, winName, features) // eslint-disable-line no-unused-vars\n{\n\t_pop(theURL, winName, features);\n}\n\n/**\n * automatically resize a text area based on the amount of lines in it\n * @param {string} ta_id element id\n */\n// @ts-ignore\nfunction expandTA(ta_id) // eslint-disable-line no-unused-vars\n{\n\t_expandTA(ta_id);\n}\n\n/**\n * wrapper to get the real window size for the current browser window\n * @return {Object} object with width/height\n */\n// @ts-ignore\nfunction getWindowSize() // eslint-disable-line no-unused-vars\n{\n\treturn _getWindowSize();\n}\n\n/**\n * wrapper to get the correct scroll offset\n * @return {Object} object with x/y px\n */\n// @ts-ignore\nfunction getScrollOffset() // eslint-disable-line no-unused-vars\n{\n\treturn _getScrollOffset();\n}\n\n/**\n * wrapper to get the correct scroll offset for opener page (from pop_)\n * @return {Object} object with x/y px\n */\n// @ts-ignore\nfunction getScrollOffsetOpener() // eslint-disable-line no-unused-vars\n{\n\treturn _getScrollOffsetOpener();\n}\n\n/**\n * centers div to current window size middle\n * @param {String} id element to center\n * @param {Boolean} left if true centers to the middle from the left\n * @param {Boolean} top if true centers to the middle from the top\n */\n// @ts-ignore\nfunction setCenter(id, left, top) // eslint-disable-line no-unused-vars\n{\n\t_setCenter(id, left, top);\n}\n\n/**\n * goes to an element id position\n * @param {String} element element id to move to\n * @param {Number} [offset=0] offset from top, default is 0 (px)\n * @param {Number} [duration=500] animation time, default 500ms\n * @param {String} [base='body,html'] base element for offset scroll\n */\n// @ts-ignore\nfunction goToPos(element, offset = 0, duration = 500, base = 'body,html') // eslint-disable-line no-unused-vars\n{\n\t_goToPos(element, offset, duration, base);\n}\n\n/**\n * go to element, scroll\n * non jquery\n * @param {string} target\n*/\n// @ts-ignore\nfunction goTo(target) // eslint-disable-line no-unused-vars\n{\n\t_goTo(target);\n}\n\n/**\n * uses the i18n object created in the translation template\n * that is filled from gettext in PHP\n * @param {String} string text to translate\n * @return {String} translated text (based on PHP selected language)\n */\n// @ts-ignore\nfunction __(string) // eslint-disable-line no-unused-vars\n{\n\treturn l10n.__(string);\n}\n\n/**\n * formats flat number 123456 to 123,456\n * @param {Number} x number to be formated\n * @return {String} formatted with , in thousands\n */\n// @ts-ignore\nfunction numberWithCommas(x) // eslint-disable-line no-unused-vars\n{\n\treturn _numberWithCommas(x);\n}\n\n/**\n * converts line breaks to br\n * @param {String} string any string\n * @return {String} string with
\n */\n// @ts-ignore\nfunction convertLBtoBR(string) // eslint-disable-line no-unused-vars\n{\n\treturn _convertLBtoBR(string);\n}\n\n/**\n * returns current timestamp (unix timestamp)\n * @return {Number} timestamp (in milliseconds)\n */\n// @ts-ignore\nfunction getTimestamp() // eslint-disable-line no-unused-vars\n{\n\treturn _getTimestamp();\n}\n\n/**\n * dec2hex :: Integer -> String\n * i.e. 0-255 -> '00'-'ff'\n * @param {Number} dec decimal string\n * @return {String} hex encdoded number\n */\n// @ts-ignore\nfunction dec2hex(dec) // eslint-disable-line no-unused-vars\n{\n\treturn _dec2hex(dec);\n}\n\n/**\n * generateId :: Integer -> String\n * only works on mondern browsers\n * @param {Number} len length of unique id string\n * @return {String} random string in length of len\n */\n// @ts-ignore\nfunction generateId(len) // eslint-disable-line no-unused-vars\n{\n\treturn _generateId(len);\n}\n\n/**\n * creates a pseudo random string of 10 characters\n * works on all browsers\n * after many runs it will create d_licates\n * @return {String} not true random string\n */\n// @ts-ignore\nfunction randomIdF() // eslint-disable-line no-unused-vars\n{\n\treturn _randomIdF();\n}\n\n/**\n * generate a number between min/max\n * with min/max inclusive.\n * eg: 1,5 will create a number ranging from 1 o 5\n * @param {Number} min minimum int number inclusive\n * @param {Number} max maximumg int number inclusive\n * @return {Number} Random number\n */\n// @ts-ignore\nfunction getRandomIntInclusive(min, max) // eslint-disable-line no-unused-vars\n{\n\treturn _getRandomIntInclusive(min, max);\n}\n\n/**\n * check if name is a function\n * @param {string} name Name of function to check if exists\n * @return {Boolean} true/false\n */\n// @ts-ignore\nfunction isFunction(name) // eslint-disable-line no-unused-vars\n{\n\treturn _isFunction(name);\n}\n\n/**\n * call a function by its string name\n * https://stackoverflow.com/a/359910\n * example: executeFunctionByName(\"My.Namespace.functionName\", window, arguments);\n * @param {string} functionName The function name or namespace + function\n * @param {any} context context (window or first namespace)\n * hidden next are all the arguments\n * @return {any} Return values from functon\n */\n// @ts-ignore\nfunction executeFunctionByName(functionName, context) // eslint-disable-line no-unused-vars\n{\n\treturn _executeFunctionByName(functionName, context);\n}\n\n/**\n * checks if a variable is an object\n * @param {any} val possible object\n * @return {Boolean} true/false if it is an object or not\n */\n// @ts-ignore\nfunction isObject(val) // eslint-disable-line no-unused-vars\n{\n\treturn _isObject(val);\n}\n\n/**\n * get the length of an object (entries)\n * @param {Object} object object to check\n * @return {Number} number of entry\n */\n// @ts-ignore\nfunction getObjectCount(object) // eslint-disable-line no-unused-vars\n{\n\treturn _getObjectCount(object);\n}\n\n/**\n * checks if a key exists in a given object\n * @param {String} key key name\n * @param {Object} object object to search key in\n * @return {Boolean} true/false if key exists in object\n */\n// @ts-ignore\nfunction keyInObject(key, object) // eslint-disable-line no-unused-vars\n{\n\treturn _keyInObject(key, object);\n}\n\n/**\n * returns matching key of value\n * @param {Object} object object to search value in\n * @param {any} value any value (String, Number, etc)\n * @return {String} the key found for the first matching value\n */\n// @ts-ignore\nfunction getKeyByValue(object, value) // eslint-disable-line no-unused-vars\n{\n\treturn _getKeyByValue(object, value);\n}\n\n/**\n * returns true if value is found in object with a key\n * @param {Object} object object to search value in\n * @param {any} value any value (String, Number, etc)\n * @return {Boolean} true on value found, false on not found\n */\n// @ts-ignore\nfunction valueInObject(object, value) // eslint-disable-line no-unused-vars\n{\n\treturn _valueInObject(object, value);\n}\n\n/**\n * true deep copy for Javascript objects\n * if Object.assign({}, obj) is not working (shallow)\n * or if JSON.parse(JSON.stringify(obj)) is failing\n * @param {Object} inObject Object to copy\n * @return {Object} Copied Object\n */\n// @ts-ignore\nfunction deepCopyFunction(inObject) // eslint-disable-line no-unused-vars\n{\n\treturn _deepCopyFunction(inObject);\n}\n\n/**\n * checks if a DOM element actually exists\n * @param {String} id Element id to check for\n * @return {Boolean} true if element exists, false on failure\n */\n// @ts-ignore\nfunction exists(id) // eslint-disable-line no-unused-vars\n{\n\treturn _exists(id);\n}\n\n/**\n * converts a int number into bytes with prefix in two decimals precision\n * currently precision is fixed, if dynamic needs check for max/min precision\n * @param {Number} bytes bytes in int\n * @return {String} string in GB/MB/KB\n */\n// @ts-ignore\nfunction formatBytes(bytes) // eslint-disable-line no-unused-vars\n{\n\treturn _formatBytes(bytes);\n}\n\n/**\n * like formatBytes, but returns bytes for <1KB and not 0.n KB\n * @param {Number} bytes bytes in int\n * @return {String} string in GB/MB/KB\n */\n// @ts-ignore\nfunction formatBytesLong(bytes) // eslint-disable-line no-unused-vars\n{\n\treturn _formatBytesLong(bytes);\n}\n\n/**\n * Convert a string with B/K/M/etc into a byte number\n * @param {String|Number} bytes Any string with B/K/M/etc\n * @return {String|Number} A byte number, or original string as is\n */\n// @ts-ignore\nfunction stringByteFormat(bytes) // eslint-disable-line no-unused-vars\n{\n\treturn _stringByteFormat(bytes);\n}\n\n/**\n * prints out error messages based on data available from the browser\n * @param {Object} err error from try/catch block\n */\n// @ts-ignore\nfunction errorCatch(err) // eslint-disable-line no-unused-vars\n{\n\t_errorCatch(err);\n}\n\n// MARK: ActionIndicatorOverlayBoxLegacy\n\n/*************************************************************\n * OLD action indicator and overlay boxes calls\n * DO NOT USE\n * actionIndicator -> showActionIndicator\n * actionIndicator -> hideActionIndicator\n * actionIndicatorShow -> showActionIndicator\n * actionIndicatorHide -> hideActionIndicator\n * overlayBoxShow -> showOverlayBoxLayers\n * overlayBoxHide -> hideOverlayBoxLayers\n * setOverlayBox -> showOverlayBoxLayers\n * hideOverlayBox -> hideOverlayBoxLayers\n * ClearCall -> ClearCallActionBox\n * ***********************************************************/\n\n/**\n * show or hide the \"do\" overlay\n * @param {String} loc location name for action indicator\n * default empty. for console.log\n * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block\n */\n// @ts-ignore\nfunction actionIndicator(loc, overlay = true) // eslint-disable-line no-unused-vars\n{\n\t_actionIndicator(loc, overlay);\n}\n\n/**\n * explicit show for action Indicator\n * instead of automatically show or hide, do on command show\n * @param {String} loc location name for action indicator\n * default empty. for console.log\n * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block\n */\n// @ts-ignore\nfunction actionIndicatorShow(loc, overlay = true) // eslint-disable-line no-unused-vars\n{\n\t_actionIndicatorShow(loc, overlay);\n}\n\n/**\n * explicit hide for action Indicator\n * instead of automatically show or hide, do on command hide\n * @param {String} loc location name for action indicator\n * default empty. for console.log\n * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block\n */\n// @ts-ignore\nfunction actionIndicatorHide(loc, overlay = true) // eslint-disable-line no-unused-vars\n{\n\t_actionIndicatorHide(loc, overlay);\n}\n\n/**\n * shows the overlay box or if already visible, bumps the zIndex to 100\n */\n// @ts-ignore\nfunction overlayBoxShow() // eslint-disable-line no-unused-vars\n{\n\t_overlayBoxShow();\n}\n\n/**\n * hides the overlay box or if zIndex is 100 bumps it down to previous level\n */\n// @ts-ignore\nfunction overlayBoxHide() // eslint-disable-line no-unused-vars\n{\n\t_overlayBoxHide();\n}\n\n/**\n * position the overlay block box and shows it\n */\n// @ts-ignore\nfunction setOverlayBox() // eslint-disable-line no-unused-vars\n{\n\t_setOverlayBox();\n}\n\n/**\n * opposite of set, always hides overlay box\n */\n// @ts-ignore\nfunction hideOverlayBox() // eslint-disable-line no-unused-vars\n{\n\t_hideOverlayBox();\n}\n\n/**\n * the abort call, clears the action box and hides it and the overlay box\n */\n// @ts-ignore\nfunction ClearCall() // eslint-disable-line no-unused-vars\n{\n\t_ClearCall();\n}\n\n// MARK: ActionIndicatorOverlayBox\n\n/*************************************************************\n * NEW action indicator and overlay box calls\n * USE THIS\n * ***********************************************************/\n\n/**\n * show action indicator\n * - checks if not existing and add\n * - only shows if not visible (else ignore)\n * - overlaybox check is called and shown on a fixzed\n * zIndex of 1000\n * - indicator is page centered\n * @param {String} loc ID string, only used for console log\n */\n// @ts-ignore\nfunction showActionIndicator(loc) // eslint-disable-line no-unused-vars\n{\n\taiob.showActionIndicator(loc);\n}\n\n/**\n * hide action indicator, if it is visiable\n * If the global variable GL_OB_S is > GL_OB_BASE then\n * the overlayBox is not hidden but the zIndex\n * is set to this value\n * @param {String} loc ID string, only used for console log\n */\n// @ts-ignore\nfunction hideActionIndicator(loc) // eslint-disable-line no-unused-vars\n{\n\taiob.hideActionIndicator(loc);\n}\n\n/**\n * checks if overlayBox exists, if not it is\n * added as hidden item at the body end\n */\n// @ts-ignore\nfunction checkOverlayExists() // eslint-disable-line no-unused-vars\n{\n\taiob.checkOverlayExists();\n}\n\n/**\n * show overlay box\n * if not visible show and set zIndex to 10 (GL_OB_BASE)\n * if visible, add +1 to the GL_OB_S variable and\n * _ zIndex by this value\n */\n// @ts-ignore\nfunction showOverlayBoxLayers(el_id) // eslint-disable-line no-unused-vars\n{\n\taiob.showOverlayBoxLayers(el_id);\n}\n\n/**\n * hide overlay box\n * lower GL_OB_S value by -1\n * if we are 10 (GL_OB_BASE) or below hide the overlayIndex\n * and set zIndex and GL_OB_S to 0\n * else just set zIndex to the new GL_OB_S value\n * @param {String} el_id Target to hide layer\n */\n// @ts-ignore\nfunction hideOverlayBoxLayers(el_id='') // eslint-disable-line no-unused-vars\n{\n\taiob.hideOverlayBoxLayers(el_id);\n}\n\n/**\n * only for single action box\n */\n// @ts-ignore\nfunction clearCallActionBox() // eslint-disable-line no-unused-vars\n{\n\taiob.clearCallActionBox();\n}\n\n// MARK: DOM MANAGEMENT FUNCTIONS\n/**\n * reates object for DOM element creation flow\n * @param {String} tag must set tag (div, span, etc)\n * @param {String} [id=''] optional set for id, if input, select will be used for name\n * @param {String} [content=''] text content inside, is skipped if sub elements exist\n * @param {Array} [css=[]] array for css tags\n * @param {Object} [options={}] anything else (value, placeholder, OnClick, style)\n * @return {Object} created element as an object\n */\n// @ts-ignore\nfunction cel(tag, id = '', content = '', css = [], options = {}) // eslint-disable-line no-unused-vars\n{\n\treturn hec.cel(tag, id, content, css, options);\n}\n\n/**\n * attach a cel created object to another to create a basic DOM tree\n * @param {Object} base object where to attach/search\n * @param {Object} attach the object to be attached\n * @param {String} [id=''] optional id, if given search in base for this id and attach there\n * @return {Object} \"none\", technically there is no return needed as it is global attach\n */\n// @ts-ignore\nfunction ael(base, attach, id = '') // eslint-disable-line no-unused-vars\n{\n\treturn hec.ael(base, attach, id);\n}\n\n/**\n * directly attach n elements to one master base element\n * this type does not s_port attach with optional id\n * @param {Object} base object to where we attach the elements\n * @param {...Object} attach attach 1..n: attach directly to the base element those attachments\n * @return {Object} \"none\", technically there is no return needed, global attach\n */\n// @ts-ignore\nfunction aelx(base, ...attach) // eslint-disable-line no-unused-vars\n{\n\treturn hec.aelx(base, ...attach);\n}\n\n/**\n * same as aelx, but instead of using objects as parameters\n * get an array of objects to attach\n * @param {Object} base object to where we attach the elements\n * @param {Array} attach array of objects to attach\n * @return {Object} \"none\", technically there is no return needed, global attach\n */\n// @ts-ignore\nfunction aelxar(base, attach) // eslint-disable-line no-unused-vars\n{\n\treturn hec.aelxar(base, attach);\n}\n\n/**\n * resets the sub elements of the base element given\n * @param {Object} base cel created element\n * @return {Object} returns reset base element\n */\n// @ts-ignore\nfunction rel(base) // eslint-disable-line no-unused-vars\n{\n\treturn hec.rel(base);\n}\n\n/**\n * searches and removes style from css array\n * @param {Object} _element element to work one\n * @param {String} css style sheet to remove (name)\n * @return {Object} returns full element\n */\n// @ts-ignore\nfunction rcssel(_element, css) // eslint-disable-line no-unused-vars\n{\n\treturn hec.rcssel(_element, css);\n}\n\n/**\n * adds a new style sheet to the element given\n * @param {Object} _element element to work on\n * @param {String} css style sheet to add (name)\n * @return {Object} returns full element\n */\n// @ts-ignore\nfunction acssel(_element, css) // eslint-disable-line no-unused-vars\n{\n\treturn hec.acssel(_element, css);\n}\n\n/**\n * removes one css and adds another\n * is a wrapper around rcssel/acssel\n * @param {Object} _element element to work on\n * @param {String} rcss style to remove (name)\n * @param {String} acss style to add (name)\n * @return {Object} returns full element\n */\n// @ts-ignore\nfunction scssel(_element, rcss, acss) // eslint-disable-line no-unused-vars\n{\n\thec.scssel(_element, rcss, acss);\n}\n\n/**\n * parses the object tree created with cel/ael and converts it into an HTML string\n * that can be inserted into the page\n * @param {Object} tree object tree with dom element declarations\n * @return {String} HTML string that can be used as innerHTML\n */\n// @ts-ignore\nfunction phfo(tree) // eslint-disable-line no-unused-vars\n{\n\treturn hec.phfo(tree);\n}\n\n/**\n * Create HTML elements from array list\n * as a flat element without master object file\n * Is like tree.sub call\n * @param {Array} list Array of cel created objects\n * @return {String} HTML String\n */\n// @ts-ignore\nfunction phfa(list) // eslint-disable-line no-unused-vars\n{\n\treturn hec.phfa(list);\n}\n// *** DOM MANAGEMENT FUNCTIONS\n\n// MARK: HTML Helpers\n// BLOCK: html wrappers for quickly creating html data blocks\n\n/**\n * NOTE: OLD FORMAT which misses multiple block set\n * creates an select/options drop down block.\n * the array needs to be key -> value format.\n * key is for the option id and value is for the data output\n * @param {String} name name/id\n * @param {Object} data array for the options\n * @param {String} [selected=''] selected item uid\n * @param {Boolean} [options_only=false] if this is true, it will not print the select part\n * @param {Boolean} [return_string=false] return as string and not as element\n * @param {String} [sort=''] if empty as is, else allowed 'keys',\n * 'values' all others are ignored\n * @return {String} html with build options block\n */\n// @ts-ignore\nfunction html_options(name, data, selected = '', options_only = false, return_string = false, sort = '') // eslint-disable-line no-unused-vars\n{\n\treturn _html_options(name, data, selected, options_only, return_string, sort);\n}\n\n/**\n * NOTE: USE THIS CALL, the above one is deprecated\n * creates an select/options drop down block.\n * the array needs to be key -> value format.\n * key is for the option id and value is for the data output\n * @param {String} name name/id\n * @param {Object} data array for the options\n * @param {String} [selected=''] selected item uid\n * @param {Number} [multiple=0] if this is 1 or larger, the drop down\n * will be turned into multiple select\n * the number sets the size value unless it is 1,\n * then it is default\n * @param {Boolean} [options_only=false] if this is true, it will not print the select part\n * @param {Boolean} [return_string=false] return as string and not as element\n * @param {String} [sort=''] if empty as is, else allowed 'keys',\n * 'values' all others are ignored\n * @param {String} [onchange=''] onchange trigger call, default unset\n * @return {String} html with build options block\n */\n// @ts-ignore\nfunction html_options_block( // eslint-disable-line no-unused-vars\n\tname, data, selected = '', multiple = 0, options_only = false, return_string = false, sort = '', onchange = ''\n) {\n\treturn _html_options_block(\n\t\tname, data, selected, multiple, options_only, return_string, sort, onchange\n\t);\n}\n\n/**\n * refills a select box with options and keeps the selected\n * @param {String} name name/id\n * @param {Object} data array of options\n * @param {String} [sort=''] if empty as is, else allowed 'keys', 'values'\n * all others are ignored\n */\n// @ts-ignore\nfunction html_options_refill(name, data, sort = '') // eslint-disable-line no-unused-vars\n{\n\t_html_options_refill(name, data, sort);\n}\n\n// MARK: URL\n\n/**\n * parses a query string from window.location.search.substring(1)\n * ALTERNATIVE CODE\n * var url = new URL(window.location.href);\n * param_uid = url.searchParams.get('uid');\n * @param {String} [query=''] the query string to parse, if not set will auto fill\n * @param {String} [return_key=''] if set only returns this key entry or empty for none\n * @param {Boolean} [single=false] if set to true then only the first found will be returned\n */\n// @ts-ignore\nfunction parseQueryString(query = '', return_key = '', single = false) // eslint-disable-line no-unused-vars\n{\n\treturn _parseQueryString(query, return_key, single);\n}\n\n/**\n * searches query parameters for entry and returns data either as string or array\n * if no search is given the whole parameters are returned as an object\n * if a parameter is set several times it will be returned as an array\n * if search parameter set and nothing found and empty string is returned\n * if no parametes exist and no serach is set and empty object is returned\n * @param {String} [search=''] if set searches for this entry, if empty all parameters are returned\n * @param {String} [query=''] different query string to parse, if not set (default) the current window href is used\n * @param {Boolean} [single=false] if set to true then only the first found will be returned\n * @return {Object|Array|String} if search is empty, object, if search is set\n * and only one entry, then string, else array\n * unless single is true\n */\n// @ts-ignore\nfunction getQueryStringParam(search = '', query = '', single = false) // eslint-disable-line no-unused-vars\n{\n\treturn _getQueryStringParam(search, query, single);\n}\n\n/**\n * Add or update a query parameter in the current URL and update the browser's address bar\n * @param {string} key - The parameter name to add or update\n * @param {string} value - The value to set for the parameter\n * @param {boolean} [reload=false] - Whether to reload the page after updating the URL\n */\n// @ts-ignore\nfunction updateUrlParameter(key, value, reload = false) // eslint-disable-line no-unused-vars\n{\n\treturn _updateUrlParameter(key, value, reload);\n}\n\n/**\n * Remove a parameter from the current URL and update the browser's address bar\n * @param {string} key - The parameter name to remove\n * @param {boolean} [reload=false] - Whether to reload the page after updating the URL\n */\n// @ts-ignore\nfunction removeUrlParameter(key, reload = false) // eslint-disable-line no-unused-vars\n{\n\treturn _removeUrlParameter(key, reload);\n}\n\n/**\n * Check if key exists as URL parameter\n * @param {String} key URL parameter to search\n * @returns {Boolean} True if key exists\n */\n// @ts-ignore\nfunction hasUrlParameter(key) // eslint-disable-line no-unused-vars\n{\n\treturn _hasUrlParameter(key);\n}\n\n/**\n * Return one value for a URL paramter or null if not found\n * @param {String} key Which URL parameter to get\n * @returns {String|Null} URL parameter content\n */\n// @ts-ignore\nfunction getUrlParameter(key) // eslint-disable-line no-unused-vars\n{\n\treturn _getUrlParameter(key);\n}\n\n// MARK: ACL LOGIN\n// *** MASTER logout call\n/**\n * submits basic data for form logout\n */\n// @ts-ignore\nfunction loginLogout() // eslint-disable-line no-unused-vars\n{\n\t_loginLogout();\n}\n\n/**\n * create login string and logout button elements\n * @param {String} login_string the login string to show on the left\n * @param {String} [header_id='mainHeader'] the target for the main element block\n * if not set mainHeader is assumed\n * this is the target div for the \"loginRow\"\n */\n// @ts-ignore\nfunction createLoginRow(login_string, header_id = 'mainHeader') // eslint-disable-line no-unused-vars\n{\n\tlnm.createLoginRow(login_string, header_id);\n}\n\n/**\n * create the top nav menu that switches physical between pages\n * (edit access data based)\n * @param {Object} nav_menu the built nav menu with highlight info\n * @param {String} [header_id='mainHeader'] the target for the main element block\n * if not set mainHeader is assumed\n * this is the target div for the \"menuRow\"\n */\n// @ts-ignore\nfunction createNavMenu(nav_menu, header_id = 'mainHeader') // eslint-disable-line no-unused-vars\n{\n\tlnm.createNavMenu(nav_menu, header_id);\n}\n\n// MARK: ACTION BOX\n\n/**\n * Show an action box\n * @param {string} [target_id='actionBox'] where to attach content to, if not exists, create new\n * @param {string} [content=''] content to add to the box\n * @param {array} [action_box_css=[]] additional css elements for the action box\n * @param {number} [override=0] override size adjust\n * @param {number} [content_override=0] override content size adjust\n */\n// @ts-ignore\nfunction showFillActionBox(target_id = 'actionBox', content = '', action_box_css = [], override = 0, content_override = 0) // eslint-disable-line no-unused-vars\n{\n\tab.showFillActionBox(target_id, content, action_box_css, override, content_override);\n}\n\n/**\n * Fill action box with content, create it if it does not existgs\n * @param {string} [target_id='actionBox'] where to attach content to, if not exists, create new\n * @param {string} [content=''] content to add to the box\n * @param {array} [action_box_css=[]] additional css elements for the action box\n */\n// @ts-ignore\nfunction fillActionBox(target_id = 'actionBox', content = '', action_box_css = []) // eslint-disable-line no-unused-vars\n{\n\t// show action box, calc height + center\n\tab.fillActionBox(target_id, content, action_box_css);\n}\n\n/**\n * Adjust the size of the action box\n * @param {string} [target_id='actionBox'] which actionBox to work on\n * @param {number} [override=0] override size adjust\n * @param {number} [content_override=0] override content size adjust\n */\n// @ts-ignore\nfunction adjustActionBox(target_id = 'actionBox', override = 0, content_override = 0) // eslint-disable-line no-unused-vars\n{\n\tab.adjustActionBox(target_id, override, content_override);\n}\n\n/**\n * hide any open action boxes and hide overlay\n */\n// @ts-ignore\nfunction hideAllActionBoxes() // eslint-disable-line no-unused-vars\n{\n\tab.hideAllActionBoxes();\n}\n\n/**\n * hide action box, but do not clear content\n * DEPRECATED\n * @param {string} [target_id='actionBox']\n */\n// @ts-ignore\nfunction hideActionBox(target_id = 'actionBox') // eslint-disable-line no-unused-vars\n{\n\tab.hideActionBox(target_id);\n}\n\n/**\n * Just show and adjust the box\n * DEPRECAED\n * @param {string} [target_id='actionBox'] which actionBox to work on\n * @param {number} [override=0] override size adjust\n * @param {number} [content_override=0] override content size adjust\n * @param {Boolean} [hide_all=false] if set to true, hide all other action boxes\n */\n// @ts-ignore\nfunction showActionBox(target_id = 'actionBox', override = 0, content_override = 0, hide_all = true) // eslint-disable-line no-unused-vars\n{\n\tab.showActionBox(target_id, override, content_override, hide_all);\n}\n\n/**\n * close an action box with default clear content\n * for just hide use hideActionBox\n * DEPRECATED\n * @param {String} [target_id='actionBox'] which action box to close, default is set\n * @param {Boolean} [clean=true] if set to false will not remove html content, just hide\n */\n// @ts-ignore\nfunction closeActionBox(target_id = 'actionBox', clean = true) // eslint-disable-line no-unused-vars\n{\n\t// set the target/content ids\n\tab.closeActionBox(target_id, clean);\n}\n\n/**\n * TODO: better stacked action box: OPEN\n * @param {string} [target_id='actionBox'] which actionBox to work on\n * @param {number} [override=0] override size adjust\n * @param {number} [content_override=0] override content size adjust\n * @param {Boolean} [hide_all=false] if set to true, hide all other action boxes\n */\n// @ts-ignore\nfunction showActionBoxFloat(target_id = 'actionBox', override = 0, content_override = 0, hide_all = false) // eslint-disable-line no-unused-vars\n{\n\tab.showActionBoxFloat(target_id, override, content_override, hide_all);\n}\n\n/**\n * TODO: better stacked action box: CLOSE\n * @param {String} [target_id='actionBox'] which action box to close, default is set\n * @param {Boolean} [clean=true] if set to false will not remove html content, just hide\n */\n// @ts-ignore\nfunction closeActionBoxFloat(target_id = 'actionBox', clean = true) // eslint-disable-line no-unused-vars\n{\n\tab.closeActionBoxFloat(target_id, clean);\n}\n\n/**\n * create a new action box and fill it with basic elements\n * @param {String} [target_id='actionBox']\n * @param {String} [title='']\n * @param {Object} [contents={}]\n * @param {Object} [headers={}]\n * @param {Boolean} [show_close=true]\n * @param {Object} [settings={}] Optional settings, eg style sheets\n */\n// @ts-ignore\nfunction createActionBox( // eslint-disable-line no-unused-vars\n\ttarget_id = 'actionBox',\n\ttitle = '',\n\tcontents = {},\n\theaders = {},\n\tsettings = {},\n\tshow_close = true\n) {\n\tab.createActionBox(target_id, title, contents, headers, settings, show_close);\n}\n\n/**\n * adjusts the action box height based on content and window height of browser\n * TODO: border on outside/and other margin things need to be added in overall adjustment\n * @param {String} [target_id='actionBox'] target id, if not set, fall back to default\n * @param {Number} [override=0] override value to add to the actionBox height\n * @param {Number} [content_override=0] override the value from _content block if it exists\n */\n// @ts-ignore\nfunction adjustActionBoxHeight(target_id = 'actionBox', override = 0, content_override = 0) // eslint-disable-line no-unused-vars\n{\n\tab.adjustActionBoxHeight(target_id, override, content_override);\n}\n\n/* END */\n"], + "mappings": "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", + "names": ["i18n", "hec", "l10n", "hec", "l10n", "escapeHtml", "roundPrecision", "formatString", "unescapeHtml", "loadEl", "pop", "expandTA", "getWindowSize", "getScrollOffset", "getScrollOffsetOpener", "setCenter", "goToPos", "goTo", "numberWithCommas", "convertLBtoBR", "getTimestamp", "dec2hex", "generateId", "randomIdF", "getRandomIntInclusive", "isFunction", "executeFunctionByName", "isObject", "getObjectCount", "keyInObject", "getKeyByValue", "valueInObject", "deepCopyFunction", "exists", "formatBytes", "formatBytesLong", "stringByteFormat", "errorCatch", "actionIndicator", "actionIndicatorShow", "actionIndicatorHide", "overlayBoxShow", "overlayBoxHide", "setOverlayBox", "hideOverlayBox", "ClearCall", "html_options", "html_options_block", "html_options_refill", "parseQueryString", "getQueryStringParam", "updateUrlParameter", "removeUrlParameter", "hasUrlParameter", "getUrlParameter", "loginLogout"] } From 426afdc1ffee762c3435ba0f32828b84ce8e0d7d Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 29 May 2025 11:23:50 +0900 Subject: [PATCH 03/20] class test array file updated --- www/admin/class_test.array.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/www/admin/class_test.array.php b/www/admin/class_test.array.php index 2e826f4d..c292355e 100644 --- a/www/admin/class_test.array.php +++ b/www/admin/class_test.array.php @@ -60,6 +60,8 @@ echo "ARRAYSEARCHRECURSIVE(email, [array], type): " . DgS::printAr(ArrayHandler::arraySearchRecursive('email', $test_array, 'type')) . "
"; echo "ARRAYSEARCHRECURSIVE(email, [array]['input'], type): " . DgS::printAr(ArrayHandler::arraySearchRecursive('email', $test_array['input'], 'type')) . "
"; +echo "ARRAYSEARCHRECURSIVE(email, [array]['input'], wrong): " + . DgS::printAr(ArrayHandler::arraySearchRecursive('email', $test_array['input'], 'wrong')) . "
"; // all return echo "ARRAYSEARCHRECURSIVEALL(email, [array], type): " . Dgs::printAr((array)ArrayHandler::arraySearchRecursiveAll('email', $test_array, 'type')) . "
"; @@ -168,6 +170,31 @@ $data = [ $search = ['image', 'result_image', 'nothing', 'EMPTY']; $result = ArrayHandler::arraySearchKey($data, $search); print "ARRAYSEARCHKEY: Search: " . DgS::printAr($search) . ", Found: " . DgS::printAr($result) . "
"; +$result = ArrayHandler::arraySearchKey($data, $search, true); +print "ARRAYSEARCHKEY: FLAT: Search: " . DgS::printAr($search) . ", Found: " . DgS::printAr($result) . "
"; +$result = ArrayHandler::arraySearchKey($data, $search, true, true); +print "ARRAYSEARCHKEY: FLAT:PREFIX: Search: " . DgS::printAr($search) . ", Found: " . DgS::printAr($result) . "
"; +$result = ArrayHandler::arraySearchKey($data, ["EMPTY"], true); +print "ARRAYSEARCHKEY: FLAT:PREFIX: Search: " . DgS::printAr(["EMPTY"]) . ", Found: " . DgS::printAr($result) . "
"; + +// $data = [ +// [ +// [name] => qrc_apcd, +// [value] => 5834367225, +// ], +// [ +// [name] => qrc_other, +// [value] => test, +// ], +// [ +// [name] => qrc_car_type, +// [value] => T33P17, +// ], +// [ +// [name] => qrc_deaer_store, +// [value] => 9990:001, +// ] +// ] // $test = [ // 'A' => [ From 991750aa5f0a717e658961a19128d454c9413499 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 4 Jun 2025 11:39:58 +0900 Subject: [PATCH 04/20] Update create random key class with custom character string - remove set random key length methods, this is now set only during call - add random key character list update, either via method set or during call --- .../Create/CoreLibsCreateRandomKeyTest.php | 203 ++++++------------ www/admin/class_test.randomkey.php | 16 +- www/lib/CoreLibs/Create/RandomKey.php | 137 ++++++++---- 3 files changed, 176 insertions(+), 180 deletions(-) diff --git a/4dev/tests/Create/CoreLibsCreateRandomKeyTest.php b/4dev/tests/Create/CoreLibsCreateRandomKeyTest.php index 3662aa65..e2714008 100644 --- a/4dev/tests/Create/CoreLibsCreateRandomKeyTest.php +++ b/4dev/tests/Create/CoreLibsCreateRandomKeyTest.php @@ -13,32 +13,6 @@ use PHPUnit\Framework\TestCase; */ final class CoreLibsCreateRandomKeyTest extends TestCase { - /** - * Undocumented function - * - * @return array - */ - public function keyLenghtProvider(): array - { - return [ - 'valid key length' => [ - 0 => 6, - 1 => true, - 2 => 6, - ], - 'negative key length' => [ - 0 => -1, - 1 => false, - 2 => 4, - ], - 'tpp big key length' => [ - 0 => 300, - 1 => false, - 2 => 4, - ], - ]; - } - /** * Undocumented function * @@ -47,109 +21,60 @@ final class CoreLibsCreateRandomKeyTest extends TestCase public function randomKeyGenProvider(): array { return [ - 'default key length' => [ + // just key length + 'default key length, default char set' => [ 0 => null, - 1 => 4 + 1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT ], - 'set -1 key length default' => [ + 'set -1 key length, default char set' => [ 0 => -1, - 1 => 4, + 1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT, ], - 'set too large key length' => [ + 'set 0 key length, default char set' => [ + 0 => -1, + 1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT, + ], + 'set too large key length, default char set' => [ 0 => 300, - 1 => 4, + 1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT, ], - 'set override key lenght' => [ + 'set override key lenght, default char set' => [ 0 => 6, 1 => 6, ], + // just character set + 'default key length, different char set A' => [ + 0 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT, + 1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT, + 2 => [ + 'A', 'B', 'C' + ], + ], + 'different key length, different char set B' => [ + 0 => 16, + 1 => 16, + 2 => [ + 'A', 'B', 'C' + ], + 3 => [ + '1', '2', '3' + ] + ], ]; } - /** - * 1 - * - * @return array - */ - public function keepKeyLengthProvider(): array + // Alternative more efficient version using strpos + private function allCharsInSet(string $input, string $allowedChars): bool { - return [ - 'set too large' => [ - 0 => 6, - 1 => 300, - 2 => 6, - ], - 'set too small' => [ - 0 => 8, - 1 => -2, - 2 => 8, - ], - 'change valid' => [ - 0 => 10, - 1 => 6, - 2 => 6, - ] - ]; - } + $inputLength = strlen($input); - /** - * run before each test and reset to default 4 - * - * @before - * - * @return void - */ - public function resetKeyLength(): void - { - \CoreLibs\Create\RandomKey::setRandomKeyLength(4); - } - - /** - * check that first length is 4 - * - * @covers ::getRandomKeyLength - * @testWith [4] - * @testdox getRandomKeyLength on init will be $expected [$_dataName] - * - * @param integer $expected - * @return void - */ - public function testGetRandomKeyLengthInit(int $expected): void - { - $this->assertEquals( - $expected, - \CoreLibs\Create\RandomKey::getRandomKeyLength() - ); - } - - /** - * Undocumented function - * - * @covers ::setRandomKeyLength - * @covers ::getRandomKeyLength - * @dataProvider keyLenghtProvider - * @testdox setRandomKeyLength $input will be $expected, compare to $compare [$_dataName] - * - * @param integer $input - * @param boolean $expected - * @param integer $compare - * @return void - */ - public function testSetRandomKeyLength(int $input, bool $expected, int $compare): void - { - // set - $this->assertEquals( - $expected, - \CoreLibs\Create\RandomKey::setRandomKeyLength($input) - ); - // read test, if false, use compare check - if ($expected === false) { - $input = $compare; + for ($i = 0; $i < $inputLength; $i++) { + if (strpos($allowedChars, $input[$i]) === false) { + return false; + } } - $this->assertEquals( - $input, - \CoreLibs\Create\RandomKey::getRandomKeyLength() - ); + + return true; } /** @@ -163,43 +88,41 @@ final class CoreLibsCreateRandomKeyTest extends TestCase * @param integer $expected * @return void */ - public function testRandomKeyGen(?int $input, int $expected): void + public function testRandomKeyGen(?int $input, int $expected, array ...$key_range): void { + $__key_data = \CoreLibs\Create\RandomKey::KEY_CHARACTER_RANGE_DEFAULT; + if (count($key_range)) { + $__key_data = join('', array_unique(array_merge(...$key_range))); + } if ($input === null) { $this->assertEquals( $expected, strlen(\CoreLibs\Create\RandomKey::randomKeyGen()) ); - } else { + } elseif ($input !== null && !count($key_range)) { + $random_key = \CoreLibs\Create\RandomKey::randomKeyGen($input); + $this->assertTrue( + $this->allCharsInSet($random_key, $__key_data), + 'Characters not valid' + ); $this->assertEquals( $expected, - strlen(\CoreLibs\Create\RandomKey::randomKeyGen($input)) + strlen($random_key), + 'String length not matching' + ); + } elseif (count($key_range)) { + $random_key = \CoreLibs\Create\RandomKey::randomKeyGen($input, ...$key_range); + $this->assertTrue( + $this->allCharsInSet($random_key, $__key_data), + 'Characters not valid' + ); + $this->assertEquals( + $expected, + strlen($random_key), + 'String length not matching' ); } } - - /** - * Check that if set to n and then invalid, it keeps the previous one - * or if second change valid, second will be shown - * - * @covers ::setRandomKeyLength - * @dataProvider keepKeyLengthProvider - * @testdox keep setRandomKeyLength set with $input_valid and then $input_invalid will be $expected [$_dataName] - * - * @param integer $input_valid - * @param integer $input_invalid - * @param integer $expected - * @return void - */ - public function testKeepKeyLength(int $input_valid, int $input_invalid, int $expected): void - { - \CoreLibs\Create\RandomKey::setRandomKeyLength($input_valid); - \CoreLibs\Create\RandomKey::setRandomKeyLength($input_invalid); - $this->assertEquals( - $expected, - \CoreLibs\Create\RandomKey::getRandomKeyLength() - ); - } } // __END__ diff --git a/www/admin/class_test.randomkey.php b/www/admin/class_test.randomkey.php index 4416b63d..c0c531a3 100644 --- a/www/admin/class_test.randomkey.php +++ b/www/admin/class_test.randomkey.php @@ -38,13 +38,21 @@ $key_length = 10; $key_length_b = 5; $key_lenght_long = 64; print "S::RANDOMKEYGEN(auto): " . RandomKey::randomKeyGen() . "
"; -print "S::SETRANDOMKEYLENGTH($key_length): " . RandomKey::setRandomKeyLength($key_length) . "
"; -print "S::RANDOMKEYGEN($key_length): " . RandomKey::randomKeyGen() . "
"; +// print "S::SETRANDOMKEYLENGTH($key_length): " . RandomKey::setRandomKeyLength($key_length) . "
"; +print "S::RANDOMKEYGEN($key_length): " . RandomKey::randomKeyGen($key_length) . "
"; print "S::RANDOMKEYGEN($key_length_b): " . RandomKey::randomKeyGen($key_length_b) . "
"; -print "S::RANDOMKEYGEN($key_length): " . RandomKey::randomKeyGen() . "
"; +print "S::RANDOMKEYGEN($key_length): " . RandomKey::randomKeyGen($key_length) . "
"; print "S::RANDOMKEYGEN($key_lenght_long): " . RandomKey::randomKeyGen($key_lenght_long) . "
"; +print "S::RANDOMKEYGEN($key_lenght_long, list data): " + . RandomKey::randomKeyGen($key_lenght_long, ['A', 'B', 'C'], ['7', '8', '9']) . "
"; +print "S::RANDOMKEYGEN(auto): " . RandomKey::randomKeyGen() . "
"; +print "===
"; $_array = new CoreLibs\Create\RandomKey(); -print "C->RANDOMKEYGEN(auto): " . $_array->randomKeyGen() . "
"; +print "C->RANDOMKEYGEN(default): " . $_array->randomKeyGen() . "
"; +print "===
"; +// CHANGE key characters +$_array = new CoreLibs\Create\RandomKey(['A', 'F', 'B'], ['1', '5', '9']); +print "C->RANDOMKEYGEN(pre set): " . $_array->randomKeyGen() . "
"; print ""; diff --git a/www/lib/CoreLibs/Create/RandomKey.php b/www/lib/CoreLibs/Create/RandomKey.php index 6d57f754..116e1c21 100644 --- a/www/lib/CoreLibs/Create/RandomKey.php +++ b/www/lib/CoreLibs/Create/RandomKey.php @@ -10,37 +10,91 @@ namespace CoreLibs\Create; class RandomKey { + /** @var int set the default key length it nothing else is set */ + public const int KEY_LENGTH_DEFAULT = 4; + /** @var int the maximum key length allowed */ + public const int KEY_LENGTH_MAX = 256; + /** @var string the default characters in the key range */ + public const string KEY_CHARACTER_RANGE_DEFAULT = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + . 'abcdefghijklmnopqrstuvwxyz' + . '0123456789'; // key generation - /** @var string */ - private static string $key_range = ''; - /** @var int */ - private static int $one_key_length; - /** @var int */ - private static int $key_length = 4; // default key length - /** @var int */ - private static int $max_key_length = 256; // max allowed length + /** @var string all the characters that are int he current radnom key range */ + private static string $key_character_range = ''; + /** @var int character count in they key character range */ + private static int $key_character_range_length = 0; + /** @var int default key lenghth */ + /** @deprecated Will be removed */ + private static int $key_length = 4; /** * if launched as class, init random key data first */ - public function __construct() + public function __construct(array ...$key_range) { - $this->initRandomKeyData(); + $this->setRandomKeyData(...$key_range); } + + /** + * internal key range validation + * + * @param array> ...$key_range + * @return string + */ + private static function validateRandomKeyData(array ...$key_range): string + { + $key_character_range = join('', array_unique(array_merge(...$key_range))); + if (strlen(self::$key_character_range) <= 1) { + return ''; + } + return $key_character_range; + } + /** * sets the random key range with the default values * + * @param array> $key_range a list of key ranges as array * @return void has no return + * @throws \LengthException If the string length is only 1 abort */ - private static function initRandomKeyData(): void + public static function setRandomKeyData(array ...$key_range): void { - // random key generation base string - self::$key_range = join('', array_merge( - range('A', 'Z'), - range('a', 'z'), - range('0', '9') - )); - self::$one_key_length = strlen(self::$key_range); + // if key range is not set + if (!count($key_range)) { + self::$key_character_range = self::KEY_CHARACTER_RANGE_DEFAULT; + } else { + self::$key_character_range = self::validateRandomKeyData(...$key_range); + // random key generation base string + } + self::$key_character_range_length = strlen(self::$key_character_range); + if (self::$key_character_range_length <= 1) { + throw new \LengthException( + "The given key character range '" . self::$key_character_range . "' " + . "is too small, must be at lest two characters: " + . self::$key_character_range_length + ); + } + } + + /** + * get the characters for the current key characters + * + * @return string + */ + public static function getRandomKeyData(): string + { + return self::$key_character_range; + } + + /** + * get the length of all random characters + * + * @return int + */ + public static function getRandomKeyDataLength(): int + { + return self::$key_character_range_length; } /** @@ -53,7 +107,7 @@ class RandomKey { if ( $key_length > 0 && - $key_length <= self::$max_key_length + $key_length <= self::KEY_LENGTH_MAX ) { return true; } else { @@ -67,6 +121,7 @@ class RandomKey * * @param int $key_length key length * @return bool true/false for set status + * @deprecated This function does no longer set the key length, the randomKeyGen parameter has to b used */ public static function setRandomKeyLength(int $key_length): bool { @@ -83,6 +138,7 @@ class RandomKey * get the current set random key length * * @return int Current set key length + * @deprecated Key length is set during randomKeyGen call, this nethid is deprecated */ public static function getRandomKeyLength(): int { @@ -94,28 +150,37 @@ class RandomKey * if override key length is set, it will check on valid key and use this * this will not set the class key length variable * - * @param int $key_length key length override, -1 for use default - * @return string random key + * @param int $key_length [default=-1] key length override, + * if not set use default [LEGACY] + * @param array> $key_range a list of key ranges as array, + * if not set use previous set data + * @return string random key */ - public static function randomKeyGen(int $key_length = -1): string - { - // init random key strings if not set - if ( - !isset(self::$one_key_length) - ) { - self::initRandomKeyData(); - } - $use_key_length = 0; - // only if valid int key with valid length - if (self::validateRandomKeyLenght($key_length) === true) { - $use_key_length = $key_length; + public static function randomKeyGen( + int $key_length = self::KEY_LENGTH_DEFAULT, + array ...$key_range + ): string { + $key_character_range = ''; + if (count($key_range)) { + $key_character_range = self::validateRandomKeyData(...$key_range); + $key_character_range_length = strlen($key_character_range); } else { - $use_key_length = self::$key_length; + if (!self::$key_character_range_length) { + self::setRandomKeyData(); + } + $key_character_range = self::getRandomKeyData(); + $key_character_range_length = self::getRandomKeyDataLength(); + } + // if not valid key length, fallback to default + if (!self::validateRandomKeyLenght($key_length)) { + $key_length = self::KEY_LENGTH_DEFAULT; } // create random string $random_string = ''; - for ($i = 1; $i <= $use_key_length; $i++) { - $random_string .= self::$key_range[random_int(0, self::$one_key_length - 1)]; + for ($i = 1; $i <= $key_length; $i++) { + $random_string .= $key_character_range[ + random_int(0, $key_character_range_length - 1) + ]; } return $random_string; } From 4bebec2b47cd8f18340fa2bb42831409111da374 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 4 Jun 2025 11:58:20 +0900 Subject: [PATCH 05/20] random key fixes for phpstan checks --- .../tests/Create/CoreLibsCreateRandomKeyTest.php | 11 +++++++++-- www/lib/CoreLibs/Create/RandomKey.php | 16 +++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/4dev/tests/Create/CoreLibsCreateRandomKeyTest.php b/4dev/tests/Create/CoreLibsCreateRandomKeyTest.php index e2714008..353ec25b 100644 --- a/4dev/tests/Create/CoreLibsCreateRandomKeyTest.php +++ b/4dev/tests/Create/CoreLibsCreateRandomKeyTest.php @@ -64,12 +64,19 @@ final class CoreLibsCreateRandomKeyTest extends TestCase } // Alternative more efficient version using strpos - private function allCharsInSet(string $input, string $allowedChars): bool + /** + * check if all characters are in set + * + * @param string $input + * @param string $allowed_chars + * @return bool + */ + private function allCharsInSet(string $input, string $allowed_chars): bool { $inputLength = strlen($input); for ($i = 0; $i < $inputLength; $i++) { - if (strpos($allowedChars, $input[$i]) === false) { + if (strpos($allowed_chars, $input[$i]) === false) { return false; } } diff --git a/www/lib/CoreLibs/Create/RandomKey.php b/www/lib/CoreLibs/Create/RandomKey.php index 116e1c21..59943ce2 100644 --- a/www/lib/CoreLibs/Create/RandomKey.php +++ b/www/lib/CoreLibs/Create/RandomKey.php @@ -30,6 +30,8 @@ class RandomKey /** * if launched as class, init random key data first + * + * @param array ...$key_range */ public function __construct(array ...$key_range) { @@ -39,7 +41,7 @@ class RandomKey /** * internal key range validation * - * @param array> ...$key_range + * @param array ...$key_range * @return string */ private static function validateRandomKeyData(array ...$key_range): string @@ -54,7 +56,7 @@ class RandomKey /** * sets the random key range with the default values * - * @param array> $key_range a list of key ranges as array + * @param array $key_range a list of key ranges as array * @return void has no return * @throws \LengthException If the string length is only 1 abort */ @@ -150,11 +152,11 @@ class RandomKey * if override key length is set, it will check on valid key and use this * this will not set the class key length variable * - * @param int $key_length [default=-1] key length override, - * if not set use default [LEGACY] - * @param array> $key_range a list of key ranges as array, - * if not set use previous set data - * @return string random key + * @param int $key_length [default=-1] key length override, + * if not set use default [LEGACY] + * @param array $key_range a list of key ranges as array, + * if not set use previous set data + * @return string random key */ public static function randomKeyGen( int $key_length = self::KEY_LENGTH_DEFAULT, From 57aae073d76e8f90f2872e5270449124a54c81eb Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 4 Jun 2025 14:11:57 +0900 Subject: [PATCH 06/20] Use string buildCharStringsFromList function --- www/lib/CoreLibs/Create/RandomKey.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/lib/CoreLibs/Create/RandomKey.php b/www/lib/CoreLibs/Create/RandomKey.php index 116e1c21..ddfab955 100644 --- a/www/lib/CoreLibs/Create/RandomKey.php +++ b/www/lib/CoreLibs/Create/RandomKey.php @@ -8,6 +8,8 @@ declare(strict_types=1); namespace CoreLibs\Create; +use CoreLibs\Convert\Strings; + class RandomKey { /** @var int set the default key length it nothing else is set */ @@ -44,7 +46,7 @@ class RandomKey */ private static function validateRandomKeyData(array ...$key_range): string { - $key_character_range = join('', array_unique(array_merge(...$key_range))); + $key_character_range = Strings::buildCharStringFromLists(...$key_range); if (strlen(self::$key_character_range) <= 1) { return ''; } From d4db235e5b3f3864bc4cd346747cccec44fca8b1 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 4 Jun 2025 14:15:45 +0900 Subject: [PATCH 07/20] Add new split string, update split string format, add create string from array list, char in char list, remove duplicates NEW: - remove duplicates in string - check character string list in other character string list - build character string from array (or nested array) values - split string with fixed split length UPDATE: - split string with format * throw exceptions for wrong paramters * remove the "split chracters", as they get extracted from the format string --- .../Convert/CoreLibsConvertStringsTest.php | 323 +++++++++++++++--- www/admin/class_test.strings.php | 75 +++- www/lib/CoreLibs/Convert/Strings.php | 148 ++++++-- 3 files changed, 478 insertions(+), 68 deletions(-) diff --git a/4dev/tests/Convert/CoreLibsConvertStringsTest.php b/4dev/tests/Convert/CoreLibsConvertStringsTest.php index c6c92251..1eb0cc29 100644 --- a/4dev/tests/Convert/CoreLibsConvertStringsTest.php +++ b/4dev/tests/Convert/CoreLibsConvertStringsTest.php @@ -24,117 +24,83 @@ final class CoreLibsConvertStringsTest extends TestCase { // 0: input // 1: format - // 2: split characters as string, null for default // 3: expected return [ 'all empty string' => [ '', '', - null, '' ], 'empty input string' => [ '', '2-2', - null, '' ], 'empty format string string' => [ '1234', '', - null, '1234' ], 'string format match' => [ '1234', '2-2', - null, '12-34' ], 'string format trailing match' => [ '1234', '2-2-', - null, '12-34' ], 'string format leading match' => [ '1234', '-2-2', - null, '12-34' ], 'string format double inside match' => [ '1234', '2--2', - null, '12--34', ], 'string format short first' => [ '1', '2-2', - null, '1' ], 'string format match first' => [ '12', '2-2', - null, '12' ], 'string format short second' => [ '123', '2-2', - null, '12-3' ], 'string format too long' => [ '1234567', '2-2', - null, '12-34-567' ], - 'string format invalid format string' => [ - '1234', - '2_2', - null, - '1234' - ], 'different split character' => [ '1234', '2_2', - '_', '12_34' ], 'mixed split characters' => [ '123456', '2-2_2', - '-_', '12-34_56' ], 'length mixed' => [ 'ABCD12345568ABC13', '2-4_5-2#4', - '-_#', 'AB-CD12_34556-8A#BC13' ], 'split with split chars in string' => [ '12-34', '2-2', - null, '12--3-4' ], - 'mutltibyte string' => [ - 'あいうえ', - '2-2', - null, - 'あいうえ' - ], - 'mutltibyte split string' => [ - '1234', - '2-2', - null, - '1234' - ], ]; } @@ -143,29 +109,132 @@ final class CoreLibsConvertStringsTest extends TestCase * * @covers ::splitFormatString * @dataProvider splitFormatStringProvider - * @testdox splitFormatString $input with format $format and splitters $split_characters will be $expected [$_dataName] + * @testdox splitFormatString $input with format $format will be $expected [$_dataName] * * @param string $input * @param string $format - * @param string|null $split_characters * @param string $expected * @return void */ public function testSplitFormatString( string $input, string $format, + string $expected + ): void { + $output = \CoreLibs\Convert\Strings::splitFormatString( + $input, + $format, + ); + $this->assertEquals( + $expected, + $output + ); + } + + /** check exceptions */ + public function splitFormatStringExceptionProvider(): array + { + return [ + 'invalid format string' => [ + '1234', + '2あ2', + ], + 'mutltibyte string' => [ + 'あいうえ', + '2-2', + ], + 'mutltibyte split string' => [ + '1234', + '2-2', + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::splitFormatStringFixed + * @dataProvider splitFormatStringExceptionProvider + * @testdox splitFormatString Exception catch checks for $input with $format[$_dataName] + * + * @return void + */ + public function testSplitFormatStringExceptions(string $input, string $format): void + { + // catch exception + $this->expectException(\InvalidArgumentException::class); + \CoreLibs\Convert\Strings::splitFormatString($input, $format); + } + + /** + * test for split Format string fixed length + * + * @return array + */ + public function splitFormatStringFixedProvider(): array + { + return [ + 'normal split, default split char' => [ + 'abcdefg', + 4, + null, + 'abcd-efg' + ], + 'noraml split, other single split char' => [ + 'abcdefg', + 4, + "=", + 'abcd=efg' + ], + 'noraml split, other multiple split char' => [ + 'abcdefg', + 4, + "-=-", + 'abcd-=-efg' + ], + 'non ascii characters' => [ + 'あいうえお', + 2, + "-", + 'あい-うえ-お' + ], + 'empty string' => [ + '', + 4, + "-", + '' + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::splitFormatStringFixed + * @dataProvider splitFormatStringFixedProvider + * @testdox splitFormatStringFixed $input with length $split_length and split chars $split_characters will be $expected [$_dataName] + * + * @param string $input + * @param int $split_length + * @param string|null $split_characters + * @param string $expected + * @return void + */ + public function testSplitFormatStringFixed( + string $input, + int $split_length, ?string $split_characters, string $expected ): void { if ($split_characters === null) { - $output = \CoreLibs\Convert\Strings::splitFormatString( + $output = \CoreLibs\Convert\Strings::splitFormatStringFixed( $input, - $format + $split_length ); } else { - $output = \CoreLibs\Convert\Strings::splitFormatString( + $output = \CoreLibs\Convert\Strings::splitFormatStringFixed( $input, - $format, + $split_length, $split_characters ); } @@ -175,6 +244,36 @@ final class CoreLibsConvertStringsTest extends TestCase ); } + public function splitFormatStringFixedExceptionProvider(): array + { + return [ + 'split length too short' => [ + 'abcdefg', + -1, + ], + 'split length longer than string' => [ + 'abcdefg', + 20, + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::splitFormatStringFixed + * @dataProvider splitFormatStringFixedExceptionProvider + * @testdox splitFormatStringFixed Exception catch checks for $input with $length [$_dataName] + * + * @return void + */ + public function testSplitFormatStringFixedExceptions(string $input, int $length): void + { + // catch exception + $this->expectException(\InvalidArgumentException::class); + \CoreLibs\Convert\Strings::splitFormatStringFixed($input, $length); + } + /** * Undocumented function * @@ -378,6 +477,150 @@ final class CoreLibsConvertStringsTest extends TestCase \CoreLibs\Convert\Strings::stripUTF8BomBytes($file) ); } + + /** + * Undocumented function + * + * @return array + */ + public function allCharsInSetProvider(): array + { + return [ + 'find' => [ + 'abc', + 'abcdef', + true + ], + 'not found' => [ + 'abcz', + 'abcdef', + false + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::allCharsInSet + * @dataProvider allCharsInSetProvider + * @testdox allCharsInSet $input in $haystack with expected $expected [$_dataName] + * + * @param string $needle + * @param string $haystack + * @param bool $expected + * @return void + */ + public function testAllCharsInSet(string $needle, string $haystack, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Strings::allCharsInSet($needle, $haystack) + ); + } + + public function buildCharStringFromListsProvider(): array + { + return [ + 'test a' => [ + 'abc', + ['a', 'b', 'c'], + ], + 'test b' => [ + 'abc123', + ['a', 'b', 'c'], + ['1', '2', '3'], + ], + 'test c: no params' => [ + '', + ], + 'test c: empty 1' => [ + '', + [] + ], + 'test nested' => [ + 'abc', + [['a'], ['b'], ['c']], + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::buildCharStringFromLists + * @dataProvider buildCharStringFromListsProvider + * @testdox buildCharStringFromLists all $input convert to $expected [$_dataName] + * + * @param string $expected + * @param array ...$input + * @return void + */ + public function testBuildCharStringFromLists(string $expected, array ...$input): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Strings::buildCharStringFromLists(...$input) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function removeDuplicatesProvider(): array + { + return [ + 'test no change' => [ + 'ABCDEFG', + 'ABCDEFG', + ], + 'test simple' => [ + 'aa', + 'a' + ], + 'test keep lower and uppwer case' => [ + 'AaBbCc', + 'AaBbCc' + ], + 'test unqiue' => [ + 'aabbcc', + 'abc' + ], + 'test multibyte no change' => [ + 'あいうえお', + 'あいうえお', + ], + 'test multibyte' => [ + 'ああいいううええおお', + 'あいうえお', + ], + 'test multibyte special' => [ + 'あぁいぃうぅえぇおぉ', + 'あぁいぃうぅえぇおぉ', + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::removeDuplicates + * @dataProvider removeDuplicatesProvider + * @testdox removeDuplicates make $input unqiue to $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testRemoveDuplicates(string $input, string $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Strings::removeDuplicates($input) + ); + } } // __END__ diff --git a/www/admin/class_test.strings.php b/www/admin/class_test.strings.php index f1ffb689..47f3ed82 100644 --- a/www/admin/class_test.strings.php +++ b/www/admin/class_test.strings.php @@ -14,6 +14,9 @@ require 'config.php'; $LOG_FILE_ID = 'classTest-string'; ob_end_flush(); +use CoreLibs\Convert\Strings; +use CoreLibs\Debug\Support as DgS; + $log = new CoreLibs\Logging\Logging([ 'log_folder' => BASE . LOG, 'log_file_id' => $LOG_FILE_ID, @@ -29,6 +32,7 @@ print ''; print '

' . $PAGE_NAME . '

'; $split = '4-4-4'; +$split_length = 4; $test_strings = [ '13', '1234', @@ -40,20 +44,59 @@ $test_strings = [ ]; foreach ($test_strings as $string) { - print "Convert: $string with $split to: " - . \CoreLibs\Convert\Strings::splitFormatString($string, $split) + print "A) Convert: $string with $split to: " + . Strings::splitFormatString($string, $split) . "
"; + try { + print "B) Convert: $string with $split_length to: " + . Strings::splitFormatStringFixed($string, $split_length) + . "
"; + } catch (Exception $e) { + print "Split not possible: " . $e->getMessage() . "
"; + } } $split = '2_2'; +$split_length = 2; $string = '1234'; -print "Convert: $string with $split to: " - . \CoreLibs\Convert\Strings::splitFormatString($string, $split) +print "A) Convert: $string with $split to: " + . Strings::splitFormatString($string, $split) + . "
"; +print "B) Convert: $string with $split_length to: " + . Strings::splitFormatStringFixed($string, $split_length, "_") . "
"; $split = '2-2'; $string = 'あいうえ'; -print "Convert: $string with $split to: " - . \CoreLibs\Convert\Strings::splitFormatString($string, $split) +try { + print "Convert: $string with $split to: " + . Strings::splitFormatString($string, $split) + . "
"; +} catch (\Exception $e) { + print "Cannot split string: " . $e->getMessage() . "
"; +} +print "B) Convert: $string with $split_length to: " + . Strings::splitFormatStringFixed($string, $split_length, "-") + . "
"; + +$string = 'ABCD12345568ABC13'; +$format = '2-4_5-2#4'; +$output = 'AB-CD12_34556-8A#BC13'; +print "A) Convert: $string with $format to: " + . Strings::splitFormatString($string, $format) + . "
"; + +// try other split calls +$string = "ABCDE"; +$split_length = 2; +$split_char = "-=-"; +print "Convert: $string with $split_length / $split_char to: " + . Strings::splitFormatStringFixed($string, $split_length, $split_char) + . "
"; +$string = "あいうえお"; +$split_length = 2; +$split_char = "-=-"; +print "Convert: $string with $split_length / $split_char to: " + . Strings::splitFormatStringFixed($string, $split_length, $split_char) . "
"; $test_splits = [ @@ -63,9 +106,27 @@ $test_splits = [ '2-3-4', ]; foreach ($test_splits as $split) { - print "$split with count: " . \CoreLibs\Convert\Strings::countSplitParts($split) . "
"; + print "$split with count: " . Strings::countSplitParts($split) . "
"; } +// check char list in list +$needle = "abc"; +$haystack = "abcdefg"; +print "Needle: " . $needle . ", Haysteck: " . $haystack . ": " + . DgS::prBl(Strings::allCharsInSet($needle, $haystack)) . "
"; +$needle = "abcz"; +print "Needle: " . $needle . ", Haysteck: " . $haystack . ": " + . DgS::prBl(Strings::allCharsInSet($needle, $haystack)) . "
"; + +print "Combined strings A: " + . Strings::buildCharStringFromLists(['A', 'B', 'C'], ['0', '1', '2']) . "
"; +print "Combined strings B: " + . Strings::buildCharStringFromLists([['F'], ['G'], 'H'], [['5', ['6']], ['0'], '1', '2']) . "
"; + +$input_string = "AaBbCc"; +print "Unique: " . Strings::removeDuplicates($input_string) . "
"; +print "Unique: " . Strings::removeDuplicates(strtolower($input_string)) . "
"; + print ""; // __END__ diff --git a/www/lib/CoreLibs/Convert/Strings.php b/www/lib/CoreLibs/Convert/Strings.php index 81632392..ddff7a4e 100644 --- a/www/lib/CoreLibs/Convert/Strings.php +++ b/www/lib/CoreLibs/Convert/Strings.php @@ -8,6 +8,8 @@ declare(strict_types=1); namespace CoreLibs\Convert; +use CoreLibs\Combined\ArrayHandler; + class Strings { /** @@ -52,29 +54,37 @@ class Strings * Note a string LONGER then the maxium will be attached with the LAST * split character. In above exmaple * ABCD1234EFGHTOOLONG will be ABCD-1234-EFGH-TOOLONG + * If the characters are NOT ASCII it will return the string as is * - * @param string $value string value to split + * @param string $string string value to split * @param string $split_format split format - * @param string $split_characters list of charcters with which we split - * if not set uses dash ('-') * @return string split formatted string or original value if not chnaged + * @throws \InvalidArgumentException for empty split format, invalid values, split characters or split format */ public static function splitFormatString( - string $value, + string $string, string $split_format, - string $split_characters = '-' ): string { - if ( - // abort if split format is empty - empty($split_format) || - // if not in the valid ASCII character range for any of the strings - preg_match('/[^\x20-\x7e]/', $value) || - // preg_match('/[^\x20-\x7e]/', $split_format) || - preg_match('/[^\x20-\x7e]/', $split_characters) || - // only numbers and split characters in split_format - !preg_match("/[0-9" . $split_characters . "]/", $split_format) - ) { - return $value; + // skip if string or split format is empty is empty + if (empty($string) || empty($split_format)) { + return $string; + } + if (preg_match('/[^\x20-\x7e]/', $string)) { + throw new \InvalidArgumentException( + "The string to split can only be ascii characters: " . $string + ); + } + // get the split characters that are not numerical and check they are ascii + $split_characters = self::removeDuplicates(preg_replace('/[0-9]/', '', $split_format)); + if (preg_match('/[^\x20-\x7e]/', $split_characters)) { + throw new \InvalidArgumentException( + "The split character has to be a valid ascii character: " . $split_characters + ); + } + if (!preg_match("/^[0-9" . $split_characters . "]+$/", $split_format)) { + throw new \InvalidArgumentException( + "The split format can only be numbers and the split characters: " . $split_format + ); } // split format list $split_list = preg_split( @@ -86,14 +96,14 @@ class Strings ); // if this is false, or only one array, abort split if (!is_array($split_list) || count($split_list) == 1) { - return $value; + return $string; } $out = ''; $pos = 0; $last_split = ''; foreach ($split_list as $offset) { if (is_numeric($offset)) { - $_part = substr($value, $pos, (int)$offset); + $_part = substr($string, $pos, (int)$offset); if (empty($_part)) { break; } @@ -104,8 +114,8 @@ class Strings $last_split = $offset; } } - if (!empty($out) && $pos < strlen($value)) { - $out .= $last_split . substr($value, $pos); + if (!empty($out) && $pos < strlen($string)) { + $out .= $last_split . substr($string, $pos); } // if last is not alphanumeric remove, remove if (!strcspn(substr($out, -1, 1), $split_characters)) { @@ -115,10 +125,49 @@ class Strings if (!empty($out)) { return $out; } else { - return $value; + return $string; } } + /** + * Split a string into n-length blocks with a split character inbetween + * This is simplified version from splitFormatString that uses + * fixed split length with a characters, this evenly splits the string out into the + * given length + * This works with non ASCII characters too + * + * @param string $string string to split + * @param int $split_length split length, must be smaller than string and larger than 0 + * @param string $split_characters [default=-] the character to split, can be more than one + * @return string + * @throws \InvalidArgumentException Thrown if split length style is invalid + */ + public static function splitFormatStringFixed( + string $string, + int $split_length, + string $split_characters = '-' + ): string { + // if empty string or if split lenght is 0 or empty split characters + // then we skip any splitting + if (empty($string) || $split_length == 0 || empty($split_characters)) { + return $string; + } + $return_string = ''; + $string_length = mb_strlen($string); + // check that the length is not too short + if ($split_length < 1 || $split_length >= $string_length) { + throw new \InvalidArgumentException( + "The split length must be at least 1 character and less than the string length to split. " + . "Split length: " . $split_length . ", string length: " . $string_length + ); + } + for ($i = 0; $i < $string_length; $i += $split_length) { + $return_string .= mb_substr($string, $i, $split_length) . $split_characters; + } + // remove last trailing character which is always the split char length + return mb_substr($return_string, 0, -1 * mb_strlen($split_characters)); + } + /** * Strip any duplicated slahes from a path * eg: //foo///bar/foo.inc -> /foo/bar/foo.inc @@ -146,6 +195,63 @@ class Strings { return trim($text, pack('H*', 'EFBBBF')); } + + /** + * Make as string of characters unique + * + * @param string $string + * @return string + */ + public static function removeDuplicates(string $string): string + { + // combine again + $result = implode( + '', + // unique list + array_unique( + // split into array + mb_str_split($string) + ) + ); + + return $result; + } + + /** + * check if all characters are in set + * + * @param string $needle Needle to search + * @param string $haystack Haystack to search in + * @return bool True on found, False if not in haystack + */ + public static function allCharsInSet(string $needle, string $haystack): bool + { + $input_length = strlen($needle); + + for ($i = 0; $i < $input_length; $i++) { + if (strpos($haystack, $needle[$i]) === false) { + return false; + } + } + + return true; + } + + /** + * converts a list of arrays of strings into a string of unique entries + * input arrays can be nested, only values are used + * + * @param array ...$char_lists + * @return string + */ + public static function buildCharStringFromLists(array ...$char_lists): string + { + return implode('', array_unique( + ArrayHandler::flattenArray( + array_merge(...$char_lists) + ) + )); + } } // __END__ From a501fa25debcbe507b0872240d40b3275a644272 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 4 Jun 2025 15:09:20 +0900 Subject: [PATCH 08/20] Add jsonPrettyPrint for formatted JSON output Can be used for any debug output when needed --- www/admin/class_test.json.php | 2 ++ www/lib/CoreLibs/Convert/Json.php | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/www/admin/class_test.json.php b/www/admin/class_test.json.php index 6c596280..21906015 100644 --- a/www/admin/class_test.json.php +++ b/www/admin/class_test.json.php @@ -67,6 +67,8 @@ print "J/S::E-JSON ERROR: " . $json_class::jsonGetLastError() . ": " . $json_cla $array = ['foo' => 'bar']; $output = Json::jsonConvertArrayTo($array); print "S::JSON: " . DgS::printAr($array) . " => " . $output . "
"; +$array = ['foo' => 'bar', 'sub' => ['other' => 'this', 'foo' => 'bar', 'set' => [12, 34, true]]]; +print "Pretty:
" . Json::jsonPrettyPrint($array) . "

"; print ""; diff --git a/www/lib/CoreLibs/Convert/Json.php b/www/lib/CoreLibs/Convert/Json.php index 7f885df4..356e5db3 100644 --- a/www/lib/CoreLibs/Convert/Json.php +++ b/www/lib/CoreLibs/Convert/Json.php @@ -119,6 +119,23 @@ class Json } return $return_string === true ? $json_error_string : self::$json_last_error; } + + /** + * wrapper to call convert array to json with pretty print + * + * @param array $data + * @return string + */ + public static function jsonPrettyPrint(array $data): string + { + return self::jsonConvertArrayTo( + $data, + JSON_PRETTY_PRINT | + JSON_UNESCAPED_LINE_TERMINATORS | + JSON_UNESCAPED_SLASHES | + JSON_UNESCAPED_UNICODE + ); + } } // __END__ From 73ac0b68b6c12f412f4bf747fc8d583da8a80e2a Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 4 Jun 2025 15:48:08 +0900 Subject: [PATCH 09/20] Add valid regex string check --- www/admin/class_test.strings.php | 7 + www/lib/CoreLibs/Combined/ArrayHandler.php | 149 +++++++++++++++++++-- www/lib/CoreLibs/Convert/Strings.php | 17 +++ 3 files changed, 164 insertions(+), 9 deletions(-) diff --git a/www/admin/class_test.strings.php b/www/admin/class_test.strings.php index 47f3ed82..f49ea6ad 100644 --- a/www/admin/class_test.strings.php +++ b/www/admin/class_test.strings.php @@ -127,6 +127,13 @@ $input_string = "AaBbCc"; print "Unique: " . Strings::removeDuplicates($input_string) . "
"; print "Unique: " . Strings::removeDuplicates(strtolower($input_string)) . "
"; +$regex_string = "/^[A-z]$/"; +print "Regex valid: " . $regex_string . ": " + . DgS::prBl(Strings::isValidRegexSimple($regex_string)) . "
"; +$regex_string = "/^[A-z"; +print "Regex valid: " . $regex_string . ": " + . DgS::prBl(Strings::isValidRegexSimple($regex_string)) . "
"; + print ""; // __END__ diff --git a/www/lib/CoreLibs/Combined/ArrayHandler.php b/www/lib/CoreLibs/Combined/ArrayHandler.php index dae3e1e9..fc2e468e 100644 --- a/www/lib/CoreLibs/Combined/ArrayHandler.php +++ b/www/lib/CoreLibs/Combined/ArrayHandler.php @@ -10,6 +10,8 @@ namespace CoreLibs\Combined; class ArrayHandler { + public const string DATA_SEPARATOR = '#'; + /** * searches key = value in an array / array * only returns the first one found @@ -148,18 +150,22 @@ class ArrayHandler * array search simple. looks for key, value combination, if found, returns true * on default does not strict check, so string '4' will match int 4 and vica versa * - * @param array $array search in as array - * @param string|int $key key (key to search in) - * @param string|int|bool $value value (what to find) - * @param bool $strict [false], if set to true, will strict check key/value - * @return bool true on found, false on not found + * @param array $array search in as array + * @param string|int $key key (key to search in) + * @param string|int|bool|array $value values list (what to find) + * @param bool $strict [false], if set to true, will strict check key/value + * @return bool true on found, false on not found */ public static function arraySearchSimple( array $array, string|int $key, - string|int|bool $value, + string|int|bool|array $value, bool $strict = false ): bool { + // convert to array + if (!is_array($value)) { + $value = [$value]; + } foreach ($array as $_key => $_value) { // if value is an array, we search if (is_array($_value)) { @@ -167,9 +173,9 @@ class ArrayHandler if (($result = self::arraySearchSimple($_value, $key, $value, $strict)) !== false) { return $result; } - } elseif ($strict === false && $_key == $key && $_value == $value) { + } elseif ($strict === false && $_key == $key && in_array($_value, $value)) { return true; - } elseif ($strict === true && $_key === $key && $_value === $value) { + } elseif ($strict === true && $_key === $key && in_array($_value, $value, true)) { return true; } } @@ -236,6 +242,95 @@ class ArrayHandler return $hit_list; } + /** + * TODO: move to CoreLibs + * Search in an array for value and check in the same array block for the required key + * If not found return an array with the array block there the required key is missing, + * the path as string with seperator block set and the missing key entry + * + * @param array $array + * @param string|int|float|bool $search_value + * @param string $required_key + * @param string $current_path + * @return array,path?:string,missing_key?:string}> + */ + public static function findArraysMissingKey( + array $array, + string|int|float|bool $search_value, + string $required_key, + string $current_path = '' + ): array { + $results = []; + + foreach ($array as $key => $value) { + $path = $current_path ? $current_path . self::DATA_SEPARATOR . $key : $key; + + if (is_array($value)) { + // Check if this array contains the search value + $containsValue = in_array($search_value, $value, true); + + // If it contains the value but doesn't have the required key + if ($containsValue && !array_key_exists($required_key, $value)) { + $results[] = [ + 'content' => $value, + 'path' => $path, + 'missing_key' => $required_key + ]; + } + + // Recursively search nested arrays + $results = array_merge( + $results, + self::findArraysMissingKey($value, $search_value, $required_key, $path) + ); + } + } + + return $results; + } + + /** + * TODO: move to CoreLibs + * currenly only over one level, find key => value entry and return set with key + * for all matching + * + * @param array $array + * @param string $lookup + * @param int|string|float|bool $search + * @param bool $strict [default=false] + * @param bool $case_senstivie [default=false] + * @return array + */ + public static function selectArrayFromOption( + array $array, + string $lookup, + int|string|float|bool $search, + bool $strict = false, + bool $case_senstivie = false, + ): array { + $result = []; + if ($case_senstivie && is_string($search)) { + $search = strtolower($search); + } + foreach ($array as $key => $value) { + // skip on not set + if (!isset($value[$lookup])) { + continue; + } + if ($case_senstivie && is_string($value[$value])) { + $value[$lookup] = strtolower($value[$value]); + } + if ( + ($strict && $search === $value[$lookup]) || + (!$strict && $search == $value[$lookup]) + ) { + $result[$key] = $value; + continue; + } + } + return $result; + } + /** * main wrapper function for next/prev key * @@ -553,7 +648,8 @@ class ArrayHandler } /** - * Modifieds the key of an array with a prefix and/or suffix and returns it with the original value + * Modifieds the key of an array with a prefix and/or suffix and + * returns it with the original value * does not change order in array * * @param array $in_array @@ -581,6 +677,41 @@ class ArrayHandler array_values($in_array) ); } + + /** + * sort array and return in same call + * sort ascending, value + * + * @param array $array array to sort by values + * @param int $params sort flags + * @return array + */ + public function sortArray(array $array, int $params = SORT_REGULAR): array + { + return sort($array, $params) ? $array : $array; + } + + /** + * sort by key ascending and return + * + * @param array $array + * @param bool $lower_case [default=false] + * @return array + */ + public static function ksortArray(array $array, bool $lower_case = false): array + { + $fk_sort_lower_case = function (string $a, string $b): int { + return strtolower($a) <=> strtolower($b); + }; + $fk_sort = function (string $a, string $b): int { + return $a <=> $b; + }; + uksort( + $array, + $lower_case ? $fk_sort_lower_case : $fk_sort + ); + return $array; + } } // __END__ diff --git a/www/lib/CoreLibs/Convert/Strings.php b/www/lib/CoreLibs/Convert/Strings.php index ddff7a4e..9a4905ae 100644 --- a/www/lib/CoreLibs/Convert/Strings.php +++ b/www/lib/CoreLibs/Convert/Strings.php @@ -252,6 +252,23 @@ class Strings ) )); } + + /** + * Check if a regex is valid. Does not return the detail regex parser error + * + * @param string $pattern Any regex string + * @return bool False on invalid regex + */ + public static function isValidRegexSimple(string $pattern): bool + { + try { + $var = ''; + @preg_match($pattern, $var); + return preg_last_error() === PREG_NO_ERROR; + } catch (\Error $e) { + return false; + } + } } // __END__ From 4707427ff45de4988757836eb40a5e63ed5179c7 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 4 Jun 2025 15:48:32 +0900 Subject: [PATCH 10/20] Add phpunit tests for checking valid regex --- .../Convert/CoreLibsConvertStringsTest.php | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/4dev/tests/Convert/CoreLibsConvertStringsTest.php b/4dev/tests/Convert/CoreLibsConvertStringsTest.php index 1eb0cc29..eb34d4b2 100644 --- a/4dev/tests/Convert/CoreLibsConvertStringsTest.php +++ b/4dev/tests/Convert/CoreLibsConvertStringsTest.php @@ -621,6 +621,48 @@ final class CoreLibsConvertStringsTest extends TestCase \CoreLibs\Convert\Strings::removeDuplicates($input) ); } + + /** + * Undocumented function + * + * @return array + */ + public function isValidRegexSimpleProvider(): array + { + return [ + 'valid regex' => [ + '/^[A-z]$/', + true + ], + 'invalid regex A' => [ + '/^[A-z]$', + false + ], + 'invalid regex B' => [ + '/^[A-z$', + false + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::isValidRegexSimple + * @dataProvider isValidRegexSimpleProvider + * @testdox isValidRegexSimple make $input unqiue to $expected [$_dataName] + * + * @param string $input + * @param bool $expected + * @return void + */ + public function testIsValidRegexSimple(string $input, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Strings::isValidRegexSimple($input) + ); + } } // __END__ From 3be3519e45271ccfd3423c9d9a38f61a3022acf4 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Jun 2025 13:26:18 +0900 Subject: [PATCH 11/20] Add new funtions and update - ksortArray and sortArray Sort array and return sorted output in one flow. Allows for case insensitve sort, reverse sort - selectArrayFromOption select array blocks based on a "key": "value" match. Can do recusrive with flat or not flat output, strict matching, case insenstivie The flat combined character can be changed - findArraysMissingKey Search an array for a matching value with optional key match and return array block if in this block a key (or keys) are missing The matching for key and value is always strict, eg 2 and '2' are different, Found path is added with ":" separators, can be overridden by parameter - arraySearchSimple Allow search values to be array for multiple matching (any match) --- ...edArrayHandlerFindArraysMissingKeyTest.php | 402 +++++++ ...LibsCombinedArrayHandlerKsortArrayTest.php | 333 ++++++ ...dArrayHandlerSelectArrayFromOptionTest.php | 383 +++++++ ...eLibsCombinedArrayHandlerSortArrayTest.php | 328 ++++++ .../CoreLibsCombinedArrayHandlerTest.php | 1002 ++++++++++++++++- www/admin/class_test.array.php | 238 +++- www/lib/CoreLibs/Combined/ArrayHandler.php | 182 ++- 7 files changed, 2813 insertions(+), 55 deletions(-) create mode 100644 4dev/tests/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php create mode 100644 4dev/tests/Combined/CoreLibsCombinedArrayHandlerKsortArrayTest.php create mode 100644 4dev/tests/Combined/CoreLibsCombinedArrayHandlerSelectArrayFromOptionTest.php create mode 100644 4dev/tests/Combined/CoreLibsCombinedArrayHandlerSortArrayTest.php diff --git a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php new file mode 100644 index 00000000..1bde4bad --- /dev/null +++ b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php @@ -0,0 +1,402 @@ + [ + 'name' => 'John', + 'age' => 25 + // missing 'email' key + ], + 'item2' => [ + 'name' => 'Jane', + 'age' => 30, + 'email' => 'jane@example.com' + ], + 'item3' => [ + 'name' => 'John', // same value as item1 + 'age' => 35, + 'email' => 'john2@example.com' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'John', 'email'); + + $this->assertCount(1, $result); + $this->assertEquals($array['item1'], $result[0]['content']); + $this->assertEquals('item1', $result[0]['path']); + $this->assertEquals(['email'], $result[0]['missing_key']); + } + + /** + * Test finding missing single key when searching by specific key-value pair + */ + public function testFindMissingSingleKeyWithKeyValueSearch() + { + $array = [ + 'user1' => [ + 'id' => 1, + 'name' => 'Alice' + // missing 'status' key + ], + 'user2' => [ + 'id' => 2, + 'name' => 'Bob', + 'status' => 'active' + ], + 'user3' => [ + 'id' => 1, // same id as user1 + 'name' => 'Charlie', + 'status' => 'inactive' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 1, 'status', 'id'); + + $this->assertCount(1, $result); + $this->assertEquals($array['user1'], $result[0]['content']); + $this->assertEquals('user1', $result[0]['path']); + $this->assertEquals(['status'], $result[0]['missing_key']); + } + + /** + * Test finding missing multiple keys + */ + public function testFindMissingMultipleKeys() + { + $array = [ + 'record1' => [ + 'name' => 'Test', + 'value' => 100 + // missing both 'date' and 'status' keys + ], + 'record2' => [ + 'name' => 'Test', + 'value' => 200, + 'date' => '2023-01-01' + // missing 'status' key + ], + 'record3' => [ + 'name' => 'Test', + 'value' => 300, + 'date' => '2023-01-02', + 'status' => 'complete' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'Test', ['date', 'status']); + + $this->assertCount(2, $result); + + // First result should be record1 missing both keys + $this->assertEquals($array['record1'], $result[0]['content']); + $this->assertEquals('record1', $result[0]['path']); + $this->assertContains('date', $result[0]['missing_key']); + $this->assertContains('status', $result[0]['missing_key']); + $this->assertCount(2, $result[0]['missing_key']); + + // Second result should be record2 missing status key + $this->assertEquals($array['record2'], $result[1]['content']); + $this->assertEquals('record2', $result[1]['path']); + $this->assertEquals(['status'], $result[1]['missing_key']); + } + + /** + * Test with nested arrays + */ + public function testFindMissingKeyInNestedArrays() + { + $array = [ + 'section1' => [ + 'items' => [ + 'item1' => [ + 'name' => 'Product A', + 'price' => 99.99 + // missing 'category' key + ], + 'item2' => [ + 'name' => 'Product B', + 'price' => 149.99, + 'category' => 'electronics' + ] + ] + ], + 'section2' => [ + 'data' => [ + 'name' => 'Product A', // same name as nested item + 'category' => 'books' + ] + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'Product A', 'category'); + + $this->assertCount(1, $result); + $this->assertEquals($array['section1']['items']['item1'], $result[0]['content']); + $this->assertEquals('section1:items:item1', $result[0]['path']); + $this->assertEquals(['category'], $result[0]['missing_key']); + } + + /** + * Test when no arrays are missing the required key + */ + public function testNoMissingKeys() + { + $array = [ + 'item1' => [ + 'name' => 'John', + 'email' => 'john@example.com' + ], + 'item2' => [ + 'name' => 'Jane', + 'email' => 'jane@example.com' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'John', 'email'); + + $this->assertEmpty($result); + } + + /** + * Test when search value is not found in any array + */ + public function testSearchValueNotFound() + { + $array = [ + 'item1' => [ + 'name' => 'John', + 'age' => 25 + ], + 'item2' => [ + 'name' => 'Jane', + 'age' => 30 + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'Bob', 'email'); + + $this->assertEmpty($result); + } + + /** + * Test with different data types for search value + */ + public function testDifferentSearchValueTypes() + { + $array = [ + 'item1' => [ + 'active' => true, + 'count' => 5 + // missing 'label' key + ], + 'item2' => [ + 'active' => false, + 'count' => 10, + 'label' => 'test' + ], + 'item3' => [ + 'active' => true, // same boolean as item1 + 'count' => 15, + 'label' => 'another' + ] + ]; + + // Test with boolean + $result = ArrayHandler::findArraysMissingKey($array, true, 'label', 'active'); + $this->assertCount(1, $result); + $this->assertEquals('item1', $result[0]['path']); + + // Test with integer + $result = ArrayHandler::findArraysMissingKey($array, 5, 'label', 'count'); + $this->assertCount(1, $result); + $this->assertEquals('item1', $result[0]['path']); + } + + /** + * Test with empty array + */ + public function testEmptyArray() + { + $array = []; + + $result = ArrayHandler::findArraysMissingKey($array, 'test', 'key'); + + $this->assertEmpty($result); + } + + /** + * Test with array containing non-array values + */ + public function testMixedArrayTypes() + { + $array = [ + 'string_value' => 'hello', + 'numeric_value' => 123, + 'array_value' => [ + 'name' => 'test', + // missing 'type' key + ], + 'another_array' => [ + 'name' => 'test', + 'type' => 'example' + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'test', 'type'); + + $this->assertCount(1, $result); + $this->assertEquals($array['array_value'], $result[0]['content']); + $this->assertEquals('array_value', $result[0]['path']); + $this->assertEquals(['type'], $result[0]['missing_key']); + } + + /** + * Test path building with deeper nesting + */ + public function testDeepNestingPathBuilding() + { + $array = [ + 'level1' => [ + 'level2' => [ + 'level3' => [ + 'items' => [ + 'target_item' => [ + 'name' => 'deep_test', + // missing 'required_field' + ] + ] + ] + ] + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'deep_test', 'required_field'); + + $this->assertCount(1, $result); + $this->assertEquals('level1:level2:level3:items:target_item', $result[0]['path']); + } + + /** + * Test with custom path separator + */ + public function testCustomPathSeparator() + { + $array = [ + 'level1' => [ + 'level2' => [ + 'item' => [ + 'name' => 'test', + // missing 'type' key + ] + ] + ] + ]; + + $result = ArrayHandler::findArraysMissingKey($array, 'test', 'type', null, '/'); + + $this->assertCount(1, $result); + $this->assertEquals('level1/level2/item', $result[0]['path']); + } + + /** + * Test default path separator behavior + */ + public function testDefaultPathSeparator() + { + $array = [ + 'parent' => [ + 'child' => [ + 'name' => 'test', + // missing 'value' key + ] + ] + ]; + + // Using default separator (should be ':') + $result = ArrayHandler::findArraysMissingKey($array, 'test', 'value'); + + $this->assertCount(1, $result); + $this->assertEquals('parent:child', $result[0]['path']); + } + + /** + * Test different path separators don't affect search logic + */ + public function testPathSeparatorDoesNotAffectSearchLogic() + { + $array = [ + 'section' => [ + 'data' => [ + 'id' => 123, + 'name' => 'item' + // missing 'status' + ] + ] + ]; + + // Test with different separators - results should be identical except for path + $result1 = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id', ':'); + $result2 = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id', '.'); + $result3 = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id', '/'); + + $this->assertCount(1, $result1); + $this->assertCount(1, $result2); + $this->assertCount(1, $result3); + + // Content and missing_key should be the same + $this->assertEquals($result1[0]['content'], $result2[0]['content']); + $this->assertEquals($result1[0]['content'], $result3[0]['content']); + $this->assertEquals($result1[0]['missing_key'], $result2[0]['missing_key']); + $this->assertEquals($result1[0]['missing_key'], $result3[0]['missing_key']); + + // Paths should be different based on separator + $this->assertEquals('section:data', $result1[0]['path']); + $this->assertEquals('section.data', $result2[0]['path']); + $this->assertEquals('section/data', $result3[0]['path']); + } + + /** + * test type checking + */ + public function testStrictTypeChecking() + { + $array = [ + 'item1' => [ + 'id' => '123', // string + 'name' => 'test' + // missing 'status' + ], + 'item2' => [ + 'id' => 123, // integer + 'name' => 'test2', + 'status' => 'active' + ] + ]; + + // Search for integer 123 - should only match item2 + $result = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id'); + $this->assertEmpty($result); // item2 has the status key + + // Search for string '123' - should only match item1 + $result = ArrayHandler::findArraysMissingKey($array, '123', 'status', 'id'); + $this->assertCount(1, $result); + $this->assertEquals('item1', $result[0]['path']); + } +} + +// __END__ diff --git a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerKsortArrayTest.php b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerKsortArrayTest.php new file mode 100644 index 00000000..9560fadc --- /dev/null +++ b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerKsortArrayTest.php @@ -0,0 +1,333 @@ + 'value1', + 'apple' => 'value2', + 'banana' => 'value3', + 'cherry' => 'value4' + ]; + + $expected = [ + 'apple' => 'value2', + 'banana' => 'value3', + 'cherry' => 'value4', + 'zebra' => 'value1' + ]; + + $result = ArrayHandler::ksortArray($input); + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test descending sort with reverse=true + */ + public function testKsortArrayDescending(): void + { + $input = [ + 'zebra' => 'value1', + 'apple' => 'value2', + 'banana' => 'value3', + 'cherry' => 'value4' + ]; + + $expected = [ + 'zebra' => 'value1', + 'cherry' => 'value4', + 'banana' => 'value3', + 'apple' => 'value2' + ]; + + $result = ArrayHandler::ksortArray($input, false, true); + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test case-insensitive ascending sort + */ + public function testKsortArrayCaseInsensitiveAscending(): void + { + $input = [ + 'Zebra' => 'value1', + 'apple' => 'value2', + 'Banana' => 'value3', + 'cherry' => 'value4' + ]; + + $expected = [ + 'apple' => 'value2', + 'Banana' => 'value3', + 'cherry' => 'value4', + 'Zebra' => 'value1' + ]; + + $result = ArrayHandler::ksortArray($input, true); + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test case-insensitive descending sort + */ + public function testKsortArrayCaseInsensitiveDescending(): void + { + $input = [ + 'Zebra' => 'value1', + 'apple' => 'value2', + 'Banana' => 'value3', + 'cherry' => 'value4' + ]; + + $expected = [ + 'Zebra' => 'value1', + 'cherry' => 'value4', + 'Banana' => 'value3', + 'apple' => 'value2' + ]; + + $result = ArrayHandler::ksortArray($input, true, true); + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test with mixed case keys to verify case sensitivity behavior + */ + public function testKsortArrayCaseSensitivityComparison(): void + { + $input = [ + 'B' => 'value1', + 'a' => 'value2', + 'C' => 'value3', + 'b' => 'value4' + ]; + + // Case-sensitive sort (uppercase comes before lowercase in ASCII) + $expectedCaseSensitive = [ + 'B' => 'value1', + 'C' => 'value3', + 'a' => 'value2', + 'b' => 'value4' + ]; + + // Case-insensitive sort + $expectedCaseInsensitive = [ + 'a' => 'value2', + 'B' => 'value1', + 'b' => 'value4', + 'C' => 'value3' + ]; + + $resultCaseSensitive = ArrayHandler::ksortArray($input, false); + $resultCaseInsensitive = ArrayHandler::ksortArray($input, true); + + $this->assertEquals($expectedCaseSensitive, $resultCaseSensitive); + $this->assertEquals($expectedCaseInsensitive, $resultCaseInsensitive); + } + + /** + * Test with numeric string keys + */ + public function testKsortArrayNumericStringKeys(): void + { + $input = [ + '10' => 'value1', + '2' => 'value2', + '1' => 'value3', + '20' => 'value4' + ]; + + // String comparison, not numeric + $expected = [ + '1' => 'value3', + '10' => 'value1', + '2' => 'value2', + '20' => 'value4' + ]; + + $result = ArrayHandler::ksortArray($input); + $this->assertEquals($expected, $result); + } + + /** + * Test with special characters in keys + */ + public function testKsortArraySpecialCharacters(): void + { + $input = [ + 'key_with_underscore' => 'value1', + 'key-with-dash' => 'value2', + 'key.with.dot' => 'value3', + 'key with space' => 'value4', + 'keyWithCamelCase' => 'value5' + ]; + + $result = ArrayHandler::ksortArray($input); + + // Verify it doesn't throw an error and maintains all keys + $this->assertCount(5, $result); + $this->assertArrayHasKey('key_with_underscore', $result); + $this->assertArrayHasKey('key-with-dash', $result); + $this->assertArrayHasKey('key.with.dot', $result); + $this->assertArrayHasKey('key with space', $result); + $this->assertArrayHasKey('keyWithCamelCase', $result); + } + + /** + * Test with empty array + */ + public function testKsortArrayEmpty(): void + { + $input = []; + $result = ArrayHandler::ksortArray($input); + $this->assertEquals([], $result); + $this->assertIsArray($result); + } + + /** + * Test with single element array + */ + public function testKsortArraySingleElement(): void + { + $input = ['onlykey' => 'onlyvalue']; + $result = ArrayHandler::ksortArray($input); + $this->assertEquals($input, $result); + } + + /** + * Test that original array is not modified (function returns new array) + */ + public function testKsortArrayDoesNotModifyOriginal(): void + { + $original = [ + 'zebra' => 'value1', + 'apple' => 'value2', + 'banana' => 'value3' + ]; + + $originalCopy = $original; // Keep a copy for comparison + $result = ArrayHandler::ksortArray($original); + + // Original array should remain unchanged + $this->assertEquals($originalCopy, $original); + $this->assertNotEquals(array_keys($original), array_keys($result)); + } + + /** + * Test with complex mixed data types as values + */ + public function testKsortArrayMixedValueTypes(): void + { + $input = [ + 'string_key' => 'string_value', + 'array_key' => ['nested', 'array'], + 'int_key' => 42, + 'bool_key' => true, + 'null_key' => null + ]; + + $result = ArrayHandler::ksortArray($input); + + // Check that all keys are preserved and sorted + $expectedKeys = ['array_key', 'bool_key', 'int_key', 'null_key', 'string_key']; + $this->assertEquals($expectedKeys, array_keys($result)); + + // Check that values are preserved correctly + $this->assertEquals('string_value', $result['string_key']); + $this->assertEquals(['nested', 'array'], $result['array_key']); + $this->assertEquals(42, $result['int_key']); + $this->assertTrue($result['bool_key']); + $this->assertNull($result['null_key']); + } + + /** + * Test all parameter combinations + */ + public function testKsortArrayAllParameterCombinations(): void + { + $input = [ + 'Delta' => 'value1', + 'alpha' => 'value2', + 'Charlie' => 'value3', + 'bravo' => 'value4' + ]; + + // Test all 4 combinations + $result1 = ArrayHandler::ksortArray($input, false, false); // default + $result2 = ArrayHandler::ksortArray($input, false, true); // reverse only + $result3 = ArrayHandler::ksortArray($input, true, false); // lowercase only + $result4 = ArrayHandler::ksortArray($input, true, true); // both + + // Each should produce different ordering + $this->assertNotEquals(array_keys($result1), array_keys($result2)); + $this->assertNotEquals(array_keys($result1), array_keys($result3)); + $this->assertNotEquals(array_keys($result1), array_keys($result4)); + $this->assertNotEquals(array_keys($result2), array_keys($result3)); + $this->assertNotEquals(array_keys($result2), array_keys($result4)); + $this->assertNotEquals(array_keys($result3), array_keys($result4)); + + // But all should have same keys and values, just different order + $this->assertEqualsCanonicalizing(array_values($input), array_values($result1)); + $this->assertEqualsCanonicalizing(array_values($input), array_values($result2)); + $this->assertEqualsCanonicalizing(array_values($input), array_values($result3)); + $this->assertEqualsCanonicalizing(array_values($input), array_values($result4)); + } + + /** + * Data provider for comprehensive testing + */ + public function sortingParametersProvider(): array + { + return [ + 'default' => [false, false], + 'reverse' => [false, true], + 'lowercase' => [true, false], + 'lowercase_reverse' => [true, true], + ]; + } + + /** + * Test that function works with all parameter combinations using data provider + * + * @dataProvider sortingParametersProvider + */ + public function testKsortArrayWithDataProvider(bool $lowerCase, bool $reverse): void + { + $input = [ + 'Zebra' => 'animal1', + 'apple' => 'fruit1', + 'Banana' => 'fruit2', + 'cat' => 'animal2' + ]; + + $result = ArrayHandler::ksortArray($input, $lowerCase, $reverse); + + // Basic assertions that apply to all combinations + $this->assertIsArray($result); + $this->assertCount(4, $result); + $this->assertArrayHasKey('Zebra', $result); + $this->assertArrayHasKey('apple', $result); + $this->assertArrayHasKey('Banana', $result); + $this->assertArrayHasKey('cat', $result); + } +} + +// __END__ diff --git a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerSelectArrayFromOptionTest.php b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerSelectArrayFromOptionTest.php new file mode 100644 index 00000000..b8055986 --- /dev/null +++ b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerSelectArrayFromOptionTest.php @@ -0,0 +1,383 @@ +testData = [ + 'item1' => [ + 'name' => 'John', + 'age' => 25, + 'status' => 'active', + 'score' => 85.5 + ], + 'item2' => [ + 'name' => 'jane', + 'age' => 30, + 'status' => 'inactive', + 'score' => 92.0 + ], + 'item3' => [ + 'name' => 'Bob', + 'age' => 25, + 'status' => 'active', + 'score' => 78.3 + ], + 'item4' => [ + 'name' => 'Alice', + 'age' => 35, + 'status' => 'pending', + 'score' => 88.7 + ] + ]; + + $this->nestedTestData = [ + 'level1_a' => [ + 'name' => 'Level1A', + 'type' => 'parent', + 'children' => [ + 'child1' => [ + 'name' => 'Child1', + 'type' => 'child', + 'active' => true + ], + 'child2' => [ + 'name' => 'Child2', + 'type' => 'child', + 'active' => false + ] + ] + ], + 'level1_b' => [ + 'name' => 'Level1B', + 'type' => 'parent', + 'children' => [ + 'child3' => [ + 'name' => 'Child3', + 'type' => 'child', + 'active' => true, + 'nested' => [ + 'deep1' => [ + 'name' => 'Deep1', + 'type' => 'deep', + 'active' => true + ] + ] + ] + ] + ], + 'item5' => [ + 'name' => 'Direct', + 'type' => 'child', + 'active' => false + ] + ]; + } + + public function testEmptyArrayReturnsEmpty(): void + { + $result = ArrayHandler::selectArrayFromOption([], 'name', 'John'); + $this->assertEmpty($result); + } + + public function testBasicStringSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'John'); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertEquals('John', $result['item1']['name']); + } + + public function testBasicIntegerSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'age', 25); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertArrayHasKey('item3', $result); + } + + public function testBasicFloatSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'score', 85.5); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertEquals(85.5, $result['item1']['score']); + } + + public function testBasicBooleanSearch(): void + { + $data = [ + 'item1' => ['enabled' => true, 'name' => 'Test1'], + 'item2' => ['enabled' => false, 'name' => 'Test2'], + 'item3' => ['enabled' => true, 'name' => 'Test3'] + ]; + + $result = ArrayHandler::selectArrayFromOption($data, 'enabled', true); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertArrayHasKey('item3', $result); + } + + public function testStrictComparison(): void + { + $data = [ + 'item1' => ['value' => '25', 'name' => 'String25'], + 'item2' => ['value' => 25, 'name' => 'Int25'], + 'item3' => ['value' => 25.0, 'name' => 'Float25'] + ]; + + // Non-strict should match all + $nonStrictResult = ArrayHandler::selectArrayFromOption($data, 'value', 25, false); + $this->assertCount(3, $nonStrictResult); + + // Strict should only match exact type + $strictResult = ArrayHandler::selectArrayFromOption($data, 'value', 25, true); + $this->assertCount(1, $strictResult); + $this->assertArrayHasKey('item2', $strictResult); + } + + public function testCaseInsensitiveSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'JANE', false, true); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('item2', $result); + $this->assertEquals('jane', $result['item2']['name']); + } + + public function testCaseSensitiveSearch(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'JANE', false, false); + + $this->assertEmpty($result); + } + + public function testRecursiveSearchWithFlatResult(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'child', + false, + false, + true, + true, + ':*' + ); + + $this->assertCount(4, $result); + $this->assertArrayHasKey('level1_a:*children:*child1', $result); + $this->assertArrayHasKey('level1_a:*children:*child2', $result); + $this->assertArrayHasKey('level1_b:*children:*child3', $result); + $this->assertArrayHasKey('item5', $result); + } + + public function testRecursiveSearchWithNestedResult(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'child', + false, + false, + true, + false + ); + + $this->assertCount(3, $result); + $this->assertArrayHasKey('level1_a', $result); + $this->assertArrayHasKey('level1_b', $result); + $this->assertArrayHasKey('item5', $result); + + // Check nested structure is preserved + $this->assertArrayHasKey('children', $result['level1_a']); + $this->assertArrayHasKey('child1', $result['level1_a']['children']); + $this->assertArrayHasKey('child2', $result['level1_a']['children']); + } + + public function testRecursiveSearchDeepNesting(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'deep', + false, + false, + true, + true, + ':*' + ); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('level1_b:*children:*child3:*nested:*deep1', $result); + $this->assertEquals('Deep1', $result['level1_b:*children:*child3:*nested:*deep1']['name']); + } + + public function testCustomFlatSeparator(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'child', + false, + false, + true, + true, + '|' + ); + + $this->assertArrayHasKey('level1_a|children|child1', $result); + $this->assertArrayHasKey('level1_a|children|child2', $result); + $this->assertArrayHasKey('level1_b|children|child3', $result); + } + + public function testNonRecursiveSearch(): void + { + $result = ArrayHandler::selectArrayFromOption( + $this->nestedTestData, + 'type', + 'child', + false, + false, + false + ); + + // Should only find direct matches, not nested ones + $this->assertCount(1, $result); + $this->assertArrayHasKey('item5', $result); + } + + public function testNoMatchesFound(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'NonExistent'); + + $this->assertEmpty($result); + } + + public function testMissingLookupKey(): void + { + $result = ArrayHandler::selectArrayFromOption($this->testData, 'nonexistent_key', 'value'); + + $this->assertEmpty($result); + } + + public function testCombinedStrictAndCaseInsensitive(): void + { + $data = [ + 'item1' => ['name' => 'Test', 'id' => '123'], + 'item2' => ['name' => 'test', 'id' => 123], + 'item3' => ['name' => 'TEST', 'id' => '123'] + ]; + + // Case insensitive but strict type matching + $result = ArrayHandler::selectArrayFromOption($data, 'id', '123', true, true); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('item1', $result); + $this->assertArrayHasKey('item3', $result); + } + + public function testBooleanWithCaseInsensitive(): void + { + $data = [ + 'item1' => ['active' => true, 'name' => 'Test1'], + 'item2' => ['active' => false, 'name' => 'Test2'] + ]; + + // Case insensitive flag should not affect boolean comparison + $result = ArrayHandler::selectArrayFromOption($data, 'active', true, false, true); + + $this->assertCount(1, $result); + $this->assertArrayHasKey('item1', $result); + } + + public function testArrayWithNumericKeys(): void + { + $data = [ + 0 => ['name' => 'First', 'type' => 'test'], + 1 => ['name' => 'Second', 'type' => 'test'], + 2 => ['name' => 'Third', 'type' => 'other'] + ]; + + $result = ArrayHandler::selectArrayFromOption($data, 'type', 'test'); + + $this->assertCount(2, $result); + $this->assertArrayHasKey(0, $result); + $this->assertArrayHasKey(1, $result); + } + + public function testRecursiveWithMixedKeyTypes(): void + { + $data = [ + 'string_key' => [ + 'name' => 'Parent', + 'type' => 'parent', + 0 => [ + 'name' => 'Child0', + 'type' => 'child' + ], + 'child_key' => [ + 'name' => 'ChildKey', + 'type' => 'child' + ] + ] + ]; + + $result = ArrayHandler::selectArrayFromOption($data, 'type', 'child', false, false, true, true, ':*'); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('string_key:*0', $result); + $this->assertArrayHasKey('string_key:*child_key', $result); + } + + public function testAllParametersCombined(): void + { + $data = [ + 'parent1' => [ + 'name' => 'Parent1', + 'status' => 'ACTIVE', + 'children' => [ + 'child1' => [ + 'name' => 'Child1', + 'status' => 'active' + ] + ] + ] + ]; + + $result = ArrayHandler::selectArrayFromOption( + $data, + 'status', + 'active', + false, // not strict + true, // case insensitive + true, // recursive + true, // flat result + '|' // custom separator + ); + + $this->assertCount(2, $result); + $this->assertArrayHasKey('parent1', $result); + $this->assertArrayHasKey('parent1|children|child1', $result); + } +} + +// __END__ diff --git a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerSortArrayTest.php b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerSortArrayTest.php new file mode 100644 index 00000000..3572c115 --- /dev/null +++ b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerSortArrayTest.php @@ -0,0 +1,328 @@ +assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test basic descending sort without maintaining keys + */ + public function testBasicDescendingSort() + { + $input = [3, 1, 4, 1, 5, 9]; + $expected = [9, 5, 4, 3, 1, 1]; + + $result = ArrayHandler::sortArray($input, false, true); + + $this->assertEquals($expected, $result); + $this->assertEquals(array_keys($expected), array_keys($result)); + } + + /** + * Test ascending sort with key maintenance + */ + public function testAscendingSortWithKeyMaintenance() + { + $input = ['c' => 3, 'a' => 1, 'd' => 4, 'b' => 1, 'e' => 5]; + $expected = ['a' => 1, 'b' => 1, 'c' => 3, 'd' => 4, 'e' => 5]; + + $result = ArrayHandler::sortArray($input, false, false, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test descending sort with key maintenance + */ + public function testDescendingSortWithKeyMaintenance() + { + $input = ['c' => 3, 'a' => 1, 'd' => 4, 'b' => 1, 'e' => 5]; + $expected = ['e' => 5, 'd' => 4, 'c' => 3, 'a' => 1, 'b' => 1]; + + $result = ArrayHandler::sortArray($input, false, true, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test string sorting with lowercase conversion + */ + public function testStringLowerCaseSort() + { + $input = ['Banana', 'apple', 'Cherry', 'date']; + $expected = ['apple', 'Banana', 'Cherry', 'date']; + + $result = ArrayHandler::sortArray($input, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test string sorting with lowercase conversion in reverse + */ + public function testStringLowerCaseSortReverse() + { + $input = ['Banana', 'apple', 'Cherry', 'date']; + $expected = ['date', 'Cherry', 'Banana', 'apple']; + + $result = ArrayHandler::sortArray($input, true, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test string sorting with lowercase conversion and key maintenance + */ + public function testStringLowerCaseSortWithKeys() + { + $input = ['b' => 'Banana', 'a' => 'apple', 'c' => 'Cherry', 'd' => 'date']; + $expected = ['a' => 'apple', 'b' => 'Banana', 'c' => 'Cherry', 'd' => 'date']; + + $result = ArrayHandler::sortArray($input, true, false, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test string sorting with lowercase conversion, reverse, and key maintenance + */ + public function testStringLowerCaseSortReverseWithKeys() + { + $input = ['b' => 'Banana', 'a' => 'apple', 'c' => 'Cherry', 'd' => 'date']; + $expected = ['d' => 'date', 'c' => 'Cherry', 'b' => 'Banana', 'a' => 'apple']; + + $result = ArrayHandler::sortArray($input, true, true, true); + + $this->assertEquals($expected, $result); + } + + /** + * Test numeric string sorting with SORT_NUMERIC flag + */ + public function testNumericStringSorting() + { + $input = ['10', '2', '1', '20']; + $expected = ['1', '2', '10', '20']; + + $result = ArrayHandler::sortArray($input, false, false, false, SORT_NUMERIC); + + $this->assertEquals($expected, $result); + } + + /** + * Test natural string sorting with SORT_NATURAL flag + */ + public function testNaturalStringSorting() + { + $input = ['img1.png', 'img10.png', 'img2.png', 'img20.png']; + $expected = ['img1.png', 'img2.png', 'img10.png', 'img20.png']; + + $result = ArrayHandler::sortArray($input, false, false, false, SORT_NATURAL); + + $this->assertEquals($expected, $result); + } + + /** + * Test with empty array + */ + public function testEmptyArray() + { + $input = []; + $expected = []; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($expected, $result); + } + + /** + * Test with single element array + */ + public function testSingleElementArray() + { + $input = [42]; + $expected = [42]; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($expected, $result); + } + + /** + * Test with array containing null values + */ + public function testArrayWithNullValues() + { + $input = [3, null, 1, null, 2]; + $expected = [null, null, 1, 2, 3]; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($expected, $result); + } + + /** + * Test with mixed data types + */ + public function testMixedDataTypes() + { + $input = [3, '1', 4.5, '2', 1]; + + $result = ArrayHandler::sortArray($input); + + // Should sort according to PHP's natural comparison rules + $this->assertIsArray($result); + $this->assertCount(5, $result); + } + + /** + * Test that original array is not modified (immutability) + */ + public function testOriginalArrayNotModified() + { + $original = [3, 1, 4, 1, 5, 9]; + $input = $original; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($original, $input); + $this->assertNotEquals($input, $result); + } + + /** + * Test case sensitivity without lowercase flag + */ + public function testCaseSensitivityWithoutLowercase() + { + $input = ['Banana', 'apple', 'Cherry']; + + $result = ArrayHandler::sortArray($input); + + // Capital letters should come before lowercase in ASCII sort + $this->assertEquals('Banana', $result[0]); + $this->assertEquals('Cherry', $result[1]); + $this->assertEquals('apple', $result[2]); + } + + /** + * Test all parameters combination + */ + public function testAllParametersCombination() + { + $input = ['z' => 'Zebra', 'a' => 'apple', 'b' => 'Banana']; + + $result = ArrayHandler::sortArray($input, true, true, true, SORT_REGULAR); + + // Should be sorted by lowercase, reversed, with keys maintained + $keys = array_keys($result); + $values = array_values($result); + + $this->assertEquals(['z', 'b', 'a'], $keys); + $this->assertEquals(['Zebra', 'Banana', 'apple'], $values); + } + + /** + * Test floating point numbers + */ + public function testFloatingPointNumbers() + { + $input = [3.14, 2.71, 1.41, 1.73]; + $expected = [1.41, 1.73, 2.71, 3.14]; + + $result = ArrayHandler::sortArray($input); + + $this->assertEquals($expected, $result); + } + + /** + * Test with duplicate values and key maintenance + */ + public function testDuplicateValuesWithKeyMaintenance() + { + $input = ['first' => 1, 'second' => 2, 'third' => 1, 'fourth' => 2]; + + $result = ArrayHandler::sortArray($input, false, false, true); + + $this->assertCount(4, $result); + $this->assertEquals([1, 1, 2, 2], array_values($result)); + // Keys should be preserved + $this->assertArrayHasKey('first', $result); + $this->assertArrayHasKey('second', $result); + $this->assertArrayHasKey('third', $result); + $this->assertArrayHasKey('fourth', $result); + } + + /** + * Data provider for comprehensive parameter testing + */ + public function sortParameterProvider(): array + { + return [ + 'basic_ascending' => [ + [3, 1, 4, 2], + false, false, false, SORT_REGULAR, + [1, 2, 3, 4] + ], + 'basic_descending' => [ + [3, 1, 4, 2], + false, true, false, SORT_REGULAR, + [4, 3, 2, 1] + ], + 'lowercase_ascending' => [ + ['Banana', 'apple', 'Cherry'], + true, false, false, SORT_REGULAR, + ['apple', 'Banana', 'Cherry'] + ], + 'lowercase_descending' => [ + ['Banana', 'apple', 'Cherry'], + true, true, false, SORT_REGULAR, + ['Cherry', 'Banana', 'apple'] + ] + ]; + } + + /** + * Test various parameter combinations using data provider + * + * @dataProvider sortParameterProvider + */ + public function testSortParameterCombinations( + array $input, + bool $lowercase, + bool $reverse, + bool $maintainKeys, + int $params, + array $expected + ) { + $result = ArrayHandler::sortArray($input, $lowercase, $reverse, $maintainKeys, $params); + + if (!$maintainKeys) { + $this->assertEquals($expected, $result); + } else { + $this->assertEquals($expected, array_values($result)); + } + } +} + +// __END__ diff --git a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php index e11aab8e..8843393c 100644 --- a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php +++ b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php @@ -7,7 +7,6 @@ declare(strict_types=1); namespace tests; -use Exception; use PHPUnit\Framework\TestCase; /** @@ -25,7 +24,11 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase 'same' => 'same', 3 => 'foobar', 'foobar' => 4, + 'barbaz' => 5, + 'zapzap' => '6', + 'zipzip' => 7, 'true' => true, + 'more' => 'other', ], 'd', 4, @@ -37,7 +40,10 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase 'sub' => [ 'nested' => 'bar', 'same' => 'same', - 'more' => 'test' + 'more' => 'test', + 'barbaz' => '5', + 'zapzap' => '6', + 'zipzip' => 7, ] ] ]; @@ -184,7 +190,7 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase 0: array $input, 1: $key, 2: $value, - 3: bool $flag, + 3: bool $strict, 4: bool $expected */ return [ @@ -286,6 +292,91 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase 3 => true, 4 => false, ], + // array tyep search + 'array type, both exist' => [ + 0 => self::$array, + 1 => 'more', + 2 => ['other', 'test'], + 3 => false, + 4 => true, + ], + 'array type, one exist' => [ + 0 => self::$array, + 1 => 'more', + 2 => ['other', 'not'], + 3 => false, + 4 => true, + ], + 'array type, none exist' => [ + 0 => self::$array, + 1 => 'more', + 2 => ['never', 'not'], + 3 => false, + 4 => false, + ], + 'array type, both exist, not strict, int and string' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => [5, '5'], + 3 => false, + 4 => true, + ], + 'array type, both exist, not strict, both string' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => ['5', '5'], + 3 => false, + 4 => true, + ], + 'array type, both exist, not strict, int and int' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => [5, 5], + 3 => false, + 4 => true, + ], + 'array type, both exist, strict, int and string' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => [5, '5'], + 3 => true, + 4 => true, + ], + 'array type, both exist, strict, both string' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => ['5', '5'], + 3 => true, + 4 => true, + ], + 'array type, both exist, strict, int and int' => [ + 0 => self::$array, + 1 => 'barbaz', + 2 => [5, 5], + 3 => true, + 4 => true, + ], + 'array type, both exist, strict, int and int to string and string' => [ + 0 => self::$array, + 1 => 'zapzap', + 2 => [6, 6], + 3 => true, + 4 => false, + ], + 'array type, both exist, strict, string and string to string and string' => [ + 0 => self::$array, + 1 => 'zapzap', + 2 => ['6', '6'], + 3 => true, + 4 => true, + ], + 'array type, both exist, not strict, int and int to string and string' => [ + 0 => self::$array, + 1 => 'zapzap', + 2 => [6, 6], + 3 => false, + 4 => true, + ], ]; } @@ -842,13 +933,13 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase * @dataProvider arraySearchRecursiveAllProvider * @testdox arraySearchRecursiveAll $needle (key $key_search_for) in $input and will be $expected (old: $flag) [$_dataName] * - * @param string|null $needle + * @param string|int|null $needle * @param array $input - * @param string|null $key_search_for + * @param string|int|null $key_search_for * @param bool $flag * @return void */ - public function testArraySearchRecursiveAll($needle, array $input, ?string $key_search_for, bool $flag, array $expected): void + public function testArraySearchRecursiveAll(string|int|null $needle, array $input, string|int|null $key_search_for, bool $flag, array $expected): void { $this->assertEquals( $expected, @@ -865,15 +956,16 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase * * @param array $input * @param string|int $key - * @param string|int|bool $value + * @param string|int|bool|array $value + * @param bool $strict * @param bool $expected * @return void */ - public function testArraySearchSimple(array $input, $key, $value, bool $flag, bool $expected): void + public function testArraySearchSimple(array $input, string|int $key, string|int|bool|array $value, bool $strict, bool $expected): void { $this->assertEquals( $expected, - \CoreLibs\Combined\ArrayHandler::arraySearchSimple($input, $key, $value, $flag) + \CoreLibs\Combined\ArrayHandler::arraySearchSimple($input, $key, $value, $strict) ); } @@ -1394,8 +1486,896 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase array $expected ): void { $this->assertEquals( - \CoreLibs\Combined\ArrayHandler::arrayModifyKey($in_array, $key_mod_prefix, $key_mod_suffix), - $expected + $expected, + \CoreLibs\Combined\ArrayHandler::arrayModifyKey($in_array, $key_mod_prefix, $key_mod_suffix) + ); + } + + /** + * sort + * + * @return array + */ + public function providerSortArray(): array + { + $unsorted = [9, 5, 'A', 4, 'B', 6, 'c', 'C', 'a']; + // for lower case the initial order of the elmenet is important: + // A, a => A, a + // d, D => d, D + $unsorted_keys = ['A' => 9, 'B' => 5, 'C' => 'A', 'D' => 4, 'E' => 'B', 'F' => 6, 'G' => 'c', 'H' => 'C', 'I' => 'a']; + return [ + // sort default + 'sort default' => [ + $unsorted, + null, + null, + null, + [4, 5, 6, 9, 'A', 'B', 'C', 'a', 'c'], + ], + // sort param set + 'sort param set' => [ + $unsorted, + false, + false, + false, + [4, 5, 6, 9, 'A', 'B', 'C', 'a', 'c'], + ], + // sort lower case + 'sort lower case' => [ + $unsorted, + true, + false, + false, + [4, 5, 6, 9, 'A', 'a', 'B', 'c', 'C'], + ], + // sort reverse + 'sort reverse' => [ + $unsorted, + false, + true, + false, + ['c', 'a', 'C', 'B', 'A', 9, 6, 5, 4], + ], + // sort lower case + reverse + 'sort lower case + reverse' => [ + $unsorted, + true, + true, + false, + ['c', 'C', 'B', 'A', 'a', 9, 6, 5, 4], + ], + // keys, do not maintain, default + 'keys, do not maintain, default' => [ + $unsorted_keys, + false, + false, + false, + [4, 5, 6, 9, 'A', 'B', 'C', 'a', 'c'], + ], + // sort maintain keys + 'sort maintain keys' => [ + $unsorted_keys, + false, + false, + true, + [ + 'D' => 4, + 'B' => 5, + 'F' => 6, + 'A' => 9, + 'C' => 'A', + 'E' => 'B', + 'H' => 'C', + 'I' => 'a', + 'G' => 'c' + ], + ], + // sort maintain keys + lower case + 'sort maintain keys + lower case' => [ + $unsorted_keys, + true, + false, + true, + [ + 'D' => 4, + 'B' => 5, + 'F' => 6, + 'A' => 9, + 'C' => 'A', + 'I' => 'a', + 'E' => 'B', + 'H' => 'C', + 'G' => 'c' + ], + ], + // sort maintain keys + reverse + 'sort maintain keys + reverse' => [ + $unsorted_keys, + false, + true, + true, + [ + 'G' => 'c', + 'H' => 'C', + 'E' => 'B', + 'I' => 'a', + 'C' => 'A', + 'A' => 9, + 'F' => 6, + 'B' => 5, + 'D' => 4, + ], + ], + // sort maintain keys + lower case + reverse + 'sort maintain keys + lower case + reverse' => [ + $unsorted_keys, + true, + true, + true, + [ + 'G' => 'c', + 'H' => 'C', + 'E' => 'B', + 'I' => 'a', + 'C' => 'A', + 'A' => 9, + 'F' => 6, + 'B' => 5, + 'D' => 4, + ], + ], + // emtpy + 'empty' => [ + [], + false, + false, + false, + [] + ], + // with nulls + 'null entries' => [ + ['d', null, 'a', null, 1], + false, + false, + false, + [null, null, 1, 'a', 'd'], + ], + // double entries + 'double entries' => [ + [1, 2, 2, 1, 'B', 'A', 'a', 'b', 'A', 'B', 'b', 'a'], + false, + false, + false, + [1, 1, 2, 2, 'A', 'A', 'B', 'B', 'a', 'a', 'b', 'b'], + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::sortArray + * @dataProvider providerSortArray + * @testdox sortArray sort $input with lower case $lower_case, reverse $reverse, maintain keys $maintain_keys with expeted $expected [$_dataName] + * + * @param array $input + * @param ?bool $lower_case + * @param ?bool $reverse + * @param ?bool $maintain_keys + * @param array $expected + * @return void + */ + public function testSortArray(array $input, ?bool $lower_case, ?bool $reverse, ?bool $maintain_keys, array $expected): void + { + $original = $input; + if ($lower_case === null && $reverse === null && $maintain_keys === null) { + $sorted_array = \CoreLibs\Combined\ArrayHandler::sortArray($input); + } else { + $sorted_array = \CoreLibs\Combined\ArrayHandler::sortArray($input, $lower_case, $reverse, $maintain_keys); + } + $expected_count = count($expected); + $this->assertIsArray( + $sorted_array, + 'sortArray: result not array' + ); + $this->assertCount( + $expected_count, + $sorted_array, + 'sortArray: count not matching' + ); + $this->assertEquals( + $expected, + $sorted_array, + 'sortArray: result not matching' + ); + $this->assertEquals( + $original, + $input, + 'sortArray: original - input was modified' + ); + if ($maintain_keys) { + $this->assertEqualsCanonicalizing( + array_keys($input), + array_keys($sorted_array), + 'sortArray: keys are not modified', + ); + } + if ($input != []) { + // we only care about array values + $this->assertNotEquals( + array_values($input), + array_values($sorted_array), + 'sortArray: output - input was modified' + ); + } + } + +/** + * sort + * + * @return array + */ + public function providerKsortArray(): array + { + // for lower case the initial order of the elmenet is important: + // A, a => A, a + // d, D => d, D + $unsorted_keys = [ + 9 => 'A', + 5 => 'B', + 'A' => 'C', + 4 => 'D', + 'B' => 'E', + 6 => 'F', + 'c' => 'G', + 'C' => 'H', + 'a' => 'I', + ]; + return [ + // sort keys + 'sort keys' => [ + $unsorted_keys, + false, + false, + [ + 4 => 'D', + 5 => 'B', + 6 => 'F', + 9 => 'A', + 'A' => 'C', + 'B' => 'E', + 'C' => 'H', + 'a' => 'I', + 'c' => 'G', + ], + ], + // sort keys + lower case + 'sort keys + lower case' => [ + $unsorted_keys, + true, + false, + [ + 4 => 'D', + 5 => 'B', + 6 => 'F', + 9 => 'A', + 'A' => 'C', + 'a' => 'I', + 'B' => 'E', + 'c' => 'G', + 'C' => 'H', + ], + ], + // sort keys + reverse + 'sort keys + reverse' => [ + $unsorted_keys, + false, + true, + [ + 'c' => 'G', + 'a' => 'I', + 'C' => 'H', + 'B' => 'E', + 'A' => 'C', + 9 => 'A', + 6 => 'F', + 5 => 'B', + 4 => 'D', + ], + ], + // sort keys + lower case + reverse + 'sort keys + lower case + reverse' => [ + $unsorted_keys, + true, + true, + [ + 'C' => 'H', + 'c' => 'G', + 'B' => 'E', + 'a' => 'I', + 'A' => 'C', + 9 => 'A', + 6 => 'F', + 5 => 'B', + 4 => 'D', + ], + ], + // emtpy + 'empty' => [ + [], + false, + false, + [] + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::ksortArray + * @dataProvider providerKsortArray + * @testdox ksortArray sort $input with lower case $lower_case, reverse $reverse with expeted $expected [$_dataName] + * + * @param array $input + * @param ?bool $lower_case + * @param ?bool $reverse + * @param array $expected + * @return void + */ + public function testKsortArray(array $input, ?bool $lower_case, ?bool $reverse, array $expected): void + { + $original = $input; + if ($lower_case === null && $reverse === null) { + $sorted_array = \CoreLibs\Combined\ArrayHandler::ksortArray($input); + } else { + $sorted_array = \CoreLibs\Combined\ArrayHandler::ksortArray($input, $lower_case, $reverse); + } + $expected_count = count($expected); + $this->assertIsArray( + $sorted_array, + 'ksortArray: result not array' + ); + $this->assertCount( + $expected_count, + $sorted_array, + 'ksortArray: count not matching' + ); + $this->assertEquals( + $expected, + $sorted_array, + 'ksortArray: result not matching' + ); + $this->assertEquals( + $original, + $input, + 'ksortArray: original - input was modified' + ); + $this->assertEqualsCanonicalizing( + array_values($original), + array_values($sorted_array), + 'ksortArray: values are not modified' + ); + if ($input != []) { + // we only care about array keys + $this->assertNotEquals( + array_keys($input), + array_keys($sorted_array), + 'sortArray: output - input was modified' + ); + } + } + + /** + * Undocumented function + * + * @return array + */ + public function providerFindArraysMissingKey(): array + { + $search_array = [ + 'table_lookup' => [ + 'match' => [ + ['param' => 'access_d_cd', 'data' => 'a_cd', 'time_validation' => 'on_load',], + ['param' => 'other_block', 'data' => 'b_cd'], + ['pflaume' => 'other_block', 'data' => 'c_cd'], + ['param' => 'third_block', 'data' => 'd_cd', 'time_validation' => 'cool'], + ['special' => 'other_block', 'data' => 'e_cd', 'time_validation' => 'other'], + ] + ] + ]; + return [ + 'find, no key set' => [ + $search_array, + 'other_block', + 'time_validation', + null, + null, + [ + [ + 'content' => [ + 'param' => 'other_block', + 'data' => 'b_cd', + ], + 'path' => 'table_lookup:match:1', + 'missing_key' => ['time_validation'], + ], + [ + 'content' => [ + 'data' => 'c_cd', + 'pflaume' => 'other_block', + ], + 'path' => 'table_lookup:match:2', + 'missing_key' => ['time_validation'], + ], + ] + ], + 'find, key set' => [ + $search_array, + 'other_block', + 'time_validation', + 'pflaume', + null, + [ + [ + 'content' => [ + 'data' => 'c_cd', + 'pflaume' => 'other_block', + ], + 'path' => 'table_lookup:match:2', + 'missing_key' => ['time_validation'], + ], + ] + ], + 'find, key set, different separator' => [ + $search_array, + 'other_block', + 'time_validation', + 'pflaume', + '#', + [ + [ + 'content' => [ + 'data' => 'c_cd', + 'pflaume' => 'other_block', + ], + 'path' => 'table_lookup#match#2', + 'missing_key' => ['time_validation'], + ], + ] + ], + 'find, key set, multiple check' => [ + $search_array, + 'other_block', + ['data', 'time_validation'], + 'pflaume', + null, + [ + [ + 'content' => [ + 'data' => 'c_cd', + 'pflaume' => 'other_block', + ], + 'path' => 'table_lookup:match:2', + 'missing_key' => ['time_validation'], + ], + ] + ], + 'has set' => [ + $search_array, + 'access_d_cd', + 'time_validation', + null, + null, + [] + ], + 'not found' => [ + $search_array, + 'not_found', + 'value', + null, + null, + [] + ], + 'empty' => [ + [], + 'something', + 'other', + null, + null, + [] + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::findArraysMissingKey + * @dataProvider providerFindArraysMissingKey + * @testdox findArraysMissingKey $input find $search_value with $search_key and missing $required_key [$_dataName] + * + * @param array $input + * @param string|int|float|bool $search_value + * @param string|array $required_key + * @param string|null $search_key + * @param string|null $path_separator + * @param array $expected + * @return void + */ + public function testFindArraysMissingKey( + array $input, + string|int|float|bool $search_value, + string|array $required_key, + ?string $search_key, + ?string $path_separator, + array $expected + ): void { + if ($path_separator === null) { + $result = \CoreLibs\Combined\ArrayHandler::findArraysMissingKey( + $input, + $search_value, + $required_key, + $search_key + ); + } else { + $result = \CoreLibs\Combined\ArrayHandler::findArraysMissingKey( + $input, + $search_value, + $required_key, + $search_key, + $path_separator + ); + } + $this->assertEquals( + $expected, + $result + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function providerSelectArrayFromOption(): array + { + $search_array = [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'c' => [ + 'lookup' => 0, + 'value' => 'CCC', + 'other' => 'OTHER', + ], + 'd' => [ + 'd-1' => [ + 'lookup' => 1, + 'value' => 'D SUB 1', + 'other' => 'Other B', + ], + 'd-2' => [ + 'lookup' => 0, + 'value' => 'D SUB 2', + 'other' => 'Other B', + ], + 'more' => [ + 'd-more-1' => [ + 'lookup' => 1, + 'value' => 'D MORE SUB 1', + 'other' => 'Other C', + ], + 'd-more-2' => [ + 'lookup' => 0, + 'value' => 'D MORE SUB 0', + 'other' => 'Other C', + ], + ] + ] + ]; + /* + 0: input + 1: lookup + 2: search + 3: strict [false] + 4: case insensitive [false] + 5: recursive [false] + 6: flat_result [true] + 7: flat_separator [:] + 8: expected + */ + return [ + 'search, flat with found' => [ + $search_array, + 'lookup' => 'lookup', + 'search' => 1, + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + ] + ], + 'search, recusrive with found' => [ + $search_array, + 'lookup' => 'lookup', + 'search' => 1, + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => true, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'd:d-1' => [ + 'lookup' => 1, + 'value' => 'D SUB 1', + 'other' => 'Other B', + ], + 'd:more:d-more-1' => [ + 'lookup' => 1, + 'value' => 'D MORE SUB 1', + 'other' => 'Other C', + ], + ] + ], + 'search, recusrive with found, other separator' => [ + $search_array, + 'lookup' => 'lookup', + 'search' => 1, + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => true, + 'flat_result' => true, + 'flag_separator' => '+', + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'd+d-1' => [ + 'lookup' => 1, + 'value' => 'D SUB 1', + 'other' => 'Other B', + ], + 'd+more+d-more-1' => [ + 'lookup' => 1, + 'value' => 'D MORE SUB 1', + 'other' => 'Other C', + ], + ] + ], + 'search, recusrive with found, not flat result' => [ + $search_array, + 'lookup' => 'lookup', + 'search' => 1, + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => true, + 'flat_result' => false, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'd' => [ + 'd-1' => [ + 'lookup' => 1, + 'value' => 'D SUB 1', + 'other' => 'Other B', + ], + 'more' => [ + 'd-more-1' => [ + 'lookup' => 1, + 'value' => 'D MORE SUB 1', + 'other' => 'Other C', + ], + ], + ], + ], + ], + 'search case insensitive' => [ + $search_array, + 'lookup' => 'other', + 'search' => 'Other', + 'strict' => false, + 'case_insenstivie' => true, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + 'c' => [ + 'lookup' => 0, + 'value' => 'CCC', + 'other' => 'OTHER', + ], + ] + ], + 'search case sensitiv' => [ + $search_array, + 'lookup' => 'other', + 'search' => 'Other', + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + ] + ], + 'search strict' => [ + $search_array, + 'lookup' => 'strict', + 'search' => '2', + 'strict' => true, + 'case_insenstivie' => false, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + ] + ], + 'search not strict' => [ + $search_array, + 'lookup' => 'strict', + 'search' => '2', + 'strict' => false, + 'case_insenstivie' => false, + 'recursive' => false, + 'flat_result' => true, + 'flag_separator' => null, + [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + 'strict' => '2', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + 'strict' => 2, + ], + ] + ], + 'empty' => [ + [], + 'something', + 'NOT_SET_AT_ALL', + false, + false, + false, + true, + null, + [] + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::selectArrayFromOption + * @dataProvider providerSelectArrayFromOption + * @testdox selectArrayFromOption $input find $lookup with $search, strict: $strict, case sensitive: $case_sensitive, recursive: $recursive, flag result: $flag_result, flag separator: $flat_separator amd expected $expected [$_dataName] + * + * @param array $input + * @param string $lookup + * @param int|string|float|bool $search + * @param bool $strict + * @param bool $case_sensitive + * @param bool $recursive + * @param bool $flat_result + * @param string|null $flat_separator + * @param array $expected + * @return void + */ + public function testSelectArrayFromOption( + array $input, + string $lookup, + int|string|float|bool $search, + bool $strict, + bool $case_sensitive, + bool $recursive, + bool $flat_result, + ?string $flat_separator, + array $expected + ): void { + if ($flat_separator === null) { + $result = \CoreLibs\Combined\ArrayHandler::selectArrayFromOption( + $input, + $lookup, + $search, + $strict, + $case_sensitive, + $recursive, + $flat_result + ); + } else { + $result = \CoreLibs\Combined\ArrayHandler::selectArrayFromOption( + $input, + $lookup, + $search, + $strict, + $case_sensitive, + $recursive, + $flat_result, + $flat_separator + ); + } + $this->assertEquals( + $expected, + $result ); } } diff --git a/www/admin/class_test.array.php b/www/admin/class_test.array.php index c292355e..87146eaf 100644 --- a/www/admin/class_test.array.php +++ b/www/admin/class_test.array.php @@ -51,6 +51,9 @@ $test_array = [ 'element_c' => [ 'type' => 'email' ], + 'element_d' => [ + 'type' => 'butter' + ], ], ]; @@ -70,7 +73,15 @@ echo "ARRAYSEARCHRECURSIVEALL(email, [array], type): " // simple search echo "ARRAYSEARCHSIMPLE([array], type, email): " - . (string)ArrayHandler::arraySearchSimple($test_array, 'type', 'email') . "
"; + . Dgs::prBl(ArrayHandler::arraySearchSimple($test_array, 'type', 'email')) . "
"; +echo "ARRAYSEARCHSIMPLE([array], type, not): " + . Dgs::prBl(ArrayHandler::arraySearchSimple($test_array, 'type', 'not')) . "
"; +echo "ARRAYSEARCHSIMPLE([array], type, [email,butter]): " + . Dgs::prBl(ArrayHandler::arraySearchSimple($test_array, 'type', ['email', 'butter'])) . "
"; +echo "ARRAYSEARCHSIMPLE([array], type, [email,not]): " + . Dgs::prBl(ArrayHandler::arraySearchSimple($test_array, 'type', ['email', 'not'])) . "
"; +echo "ARRAYSEARCHSIMPLE([array], type, [never,not]): " + . Dgs::prBl(ArrayHandler::arraySearchSimple($test_array, 'type', ['never', 'not'])) . "
"; $array_1 = [ 'foo' => 'bar' @@ -292,6 +303,231 @@ print "array intersect key: " . DgS::printAr($keys) . ": " . DgS::printAr($out) print "array + suffix: " . DgS::printAr(ArrayHandler::arrayModifyKey($array, key_mod_suffix:'_attached')) . "
"; +print "
"; +$unsorted = [9, 5, 'A', 4, 'B', 6, 'c', 'C', 'a']; +$unsorted_keys = [ + 'A' => 9, 'B' => 5, 'C' => 'A', 'D' => 4, 'E' => 'B', 'F' => 6, 'G' => 'c', + 'H1' => 'D', 'B1' => 'd', 'H' => 'C', 'I' => 'a' +]; +print "Unsorted: " . DgS::printAr($unsorted) . "
"; +print "(sort): " . DgS::printAr(ArrayHandler::sortArray($unsorted)) . "
"; +print "(sort, lower): " . DgS::printAr(ArrayHandler::sortArray($unsorted, case_insensitive:true)) . "
"; +print "(sort, reverse): " . DgS::printAr(ArrayHandler::sortArray($unsorted, reverse:true)) . "
"; +print "(sort, lower, reverse): " + . DgS::printAr(ArrayHandler::sortArray($unsorted, case_insensitive:true, reverse:true)) . "
"; +print "(sort, keys): " . DgS::printAr(ArrayHandler::sortArray($unsorted_keys, maintain_keys:true)) . "
"; +print "(sort, keys, lower): " + . DgS::printAr(ArrayHandler::sortArray($unsorted_keys, maintain_keys:true, case_insensitive:true)) . "
"; + +print "
"; +$unsorted = [9 => 'A', 5 => 'B', 'A' => 'C', 4 => 'D', 'B' => 'E', 6 => 'F', 'c' => 'G', 'C' => 'H', 'a' => 'I']; +print "Unsorted Keys: " . DgS::printAr($unsorted) . "
"; +print "(sort): " . DgS::printAr(ArrayHandler::sortArray($unsorted)) . "
"; +print "(sort, keys): " . DgS::printAr(ArrayHandler::sortArray($unsorted, maintain_keys:true)) . "
"; +print "(kosrt): " . DgS::printAr(ArrayHandler::ksortArray($unsorted)) . "
"; +print "(kosrt, reverse): " . DgS::printAr(ArrayHandler::ksortArray($unsorted, reverse:true)) . "
"; +print "(kosrt, lower case, reverse): " + . DgS::printAr(ArrayHandler::ksortArray($unsorted, case_insensitive:true, reverse:true)) . "
"; + + +print "
"; +$nested = [ + 'B' => 'foo', 'a', '0', 9, + 1 => ['z', 'b', 'a'], + 'd' => ['zaip', 'bar', 'baz'] +]; +print "Nested: " . DgS::printAr($nested) . "
"; +print "(sort): " . DgS::printAr(ArrayHandler::sortArray($nested)) . "
"; +print "(ksort): " . DgS::printAr(ArrayHandler::ksortArray($nested)) . "
"; + +print "
"; + +$search_array = [ + 'table_lookup' => [ + 'match' => [ + ['param' => 'access_d_cd', 'data' => 'a_cd', 'time_validation' => 'on_load',], + ['param' => 'other_block', 'data' => 'b_cd'], + ['pflaume' => 'other_block', 'data' => 'c_cd'], + ['param' => 'third_block', 'data' => 'd_cd', 'time_validation' => 'cool'], + ['special' => 'other_block', 'data' => 'e_cd', 'time_validation' => 'other'], + ] + ] +]; +print "Search: " . DgS::printAr($search_array) . "
"; +print "Result (all): " . Dgs::printAr(ArrayHandler::findArraysMissingKey( + $search_array, + 'other_block', + 'time_validation' +)) . "
"; +print "Result (key): " . Dgs::printAr(ArrayHandler::findArraysMissingKey( + $search_array, + 'other_block', + 'time_validation', + 'pflaume' +)) . "
"; +print "Result (key): " . Dgs::printAr(ArrayHandler::findArraysMissingKey( + $search_array, + 'other_block', + ['data', 'time_validation'], + 'pflaume' +)) . "
"; + +print "
"; + +$search_array = [ + 'a' => [ + 'lookup' => 1, + 'value' => 'Foo', + 'other' => 'Bar', + ], + 'b' => [ + 'lookup' => 1, + 'value' => 'AAA', + 'other' => 'Other', + ], + 'c' => [ + 'lookup' => 0, + 'value' => 'CCC', + 'other' => 'OTHER', + ], + 'd' => [ + 'd-1' => [ + 'lookup' => 1, + 'value' => 'D SUB 1', + 'other' => 'Other B', + ], + 'd-2' => [ + 'lookup' => 0, + 'value' => 'D SUB 2', + 'other' => 'Other C', + ], + 'more' => [ + 'lookup' => 1, + 'd-more-1' => [ + 'lookup' => 1, + 'value' => 'D MORE SUB 1', + 'other' => 'Other C', + ], + 'd-more-2' => [ + 'lookup' => 0, + 'value' => 'D MORE SUB 0', + 'other' => 'Other C', + ], + ] + ] +]; + +print "Search: " . DgS::printAr($search_array) . "
"; +print "Result: " . DgS::printAr(ArrayHandler::selectArrayFromOption( + $search_array, + 'lookup', + 1, +)) . "
"; +print "Result: " . DgS::printAr(ArrayHandler::selectArrayFromOption( + $search_array, + 'lookup', + 1, + recursive:true +)) . "
"; +print "Result: " . DgS::printAr(ArrayHandler::selectArrayFromOption( + $search_array, + 'lookup', + 1, + recursive:true, + flat_separator:'-=-' +)) . "
"; +print "Result: " . DgS::printAr(ArrayHandler::selectArrayFromOption( + $search_array, + 'lookup', + 1, + recursive:true, + flat_result:false +)) . "
"; +print "Result: " . DgS::printAr(ArrayHandler::selectArrayFromOption( + $search_array, + 'other', + 'Other', + case_insensitive:false, +)) . "
"; + +$nestedTestData = [ + 'level1_a' => [ + 'name' => 'Level1A', + 'type' => 'parent', + 'children' => [ + 'child1' => [ + 'name' => 'Child1', + 'type' => 'child', + 'active' => true + ], + 'child2' => [ + 'name' => 'Child2', + 'type' => 'child', + 'active' => false + ] + ] + ], + 'level1_b' => [ + 'name' => 'Level1B', + 'type' => 'parent', + 'children' => [ + 'child3' => [ + 'name' => 'Child3', + 'type' => 'child', + 'active' => true, + 'nested' => [ + 'deep1' => [ + 'name' => 'Deep1', + 'type' => 'deep', + 'active' => true + ] + ] + ] + ] + ], + 'item5' => [ + 'name' => 'Direct', + 'type' => 'child', + 'active' => false + ] +]; + +$result = ArrayHandler::selectArrayFromOption( + $nestedTestData, + 'type', + 'child', + false, + false, + true, + true, + ':*' +); +print "*1*Result: " . DgS::printAr($result) . "
"; +$data = [ + 'parent1' => [ + 'name' => 'Parent1', + 'status' => 'ACTIVE', + 'children' => [ + 'child1' => [ + 'name' => 'Child1', + 'status' => 'active' + ] + ] + ] +]; + +$result = ArrayHandler::selectArrayFromOption( + $data, + 'status', + 'active', + false, // not strict + true, // case insensitive + true, // recursive + true, // flat result + '|' // custom separator +); +print "*2*Result: " . DgS::printAr($result) . "
"; + print ""; // __END__ diff --git a/www/lib/CoreLibs/Combined/ArrayHandler.php b/www/lib/CoreLibs/Combined/ArrayHandler.php index fc2e468e..16d2c3ab 100644 --- a/www/lib/CoreLibs/Combined/ArrayHandler.php +++ b/www/lib/CoreLibs/Combined/ArrayHandler.php @@ -10,7 +10,7 @@ namespace CoreLibs\Combined; class ArrayHandler { - public const string DATA_SEPARATOR = '#'; + public const string DATA_SEPARATOR = ':'; /** * searches key = value in an array / array @@ -151,7 +151,7 @@ class ArrayHandler * on default does not strict check, so string '4' will match int 4 and vica versa * * @param array $array search in as array - * @param string|int $key key (key to search in) + * @param string|int $key key (key to search in) * @param string|int|bool|array $value values list (what to find) * @param bool $strict [false], if set to true, will strict check key/value * @return bool true on found, false on not found @@ -243,45 +243,73 @@ class ArrayHandler } /** - * TODO: move to CoreLibs - * Search in an array for value and check in the same array block for the required key + * Search in an array for value with or without key and + * check in the same array block for the required key * If not found return an array with the array block there the required key is missing, * the path as string with seperator block set and the missing key entry * * @param array $array * @param string|int|float|bool $search_value - * @param string $required_key + * @param string|array $required_key + * @param ?string $serach_key [null] + * @param string $path_separator [DATA_SEPARATOR] * @param string $current_path * @return array,path?:string,missing_key?:string}> */ public static function findArraysMissingKey( array $array, string|int|float|bool $search_value, - string $required_key, + string|array $required_key, + ?string $search_key = null, + string $path_separator = self::DATA_SEPARATOR, string $current_path = '' ): array { $results = []; - foreach ($array as $key => $value) { - $path = $current_path ? $current_path . self::DATA_SEPARATOR . $key : $key; + $path = $current_path ? $current_path . $path_separator . $key : $key; if (is_array($value)) { // Check if this array contains the search value - $containsValue = in_array($search_value, $value, true); + // either any value match or with key + if ($search_key === null) { + $containsValue = in_array($search_value, $value, true); + } else { + $containsValue = array_key_exists($search_key, $value) && $value[$search_key] === $search_value; + } // If it contains the value but doesn't have the required key - if ($containsValue && !array_key_exists($required_key, $value)) { + if ( + $containsValue && + ( + ( + is_string($required_key) && + !array_key_exists($required_key, $value) + ) || ( + is_array($required_key) && + count(array_intersect($required_key, array_keys($value))) !== count($required_key) + ) + ) + ) { $results[] = [ 'content' => $value, 'path' => $path, - 'missing_key' => $required_key + 'missing_key' => is_array($required_key) ? + array_values(array_diff($required_key, array_keys($value))) : + [$required_key] ]; } // Recursively search nested arrays $results = array_merge( $results, - self::findArraysMissingKey($value, $search_value, $required_key, $path) + self::findArraysMissingKey( + $value, + $search_value, + $required_key, + $search_key, + $path_separator, + $path + ) ); } } @@ -290,15 +318,17 @@ class ArrayHandler } /** - * TODO: move to CoreLibs - * currenly only over one level, find key => value entry and return set with key - * for all matching + * Find key => value entry and return set with key for all matching + * Can search recursively through nested arrays if recursive flag is set * - * @param array $array - * @param string $lookup + * @param array $array + * @param string $lookup * @param int|string|float|bool $search - * @param bool $strict [default=false] - * @param bool $case_senstivie [default=false] + * @param bool $strict [false] + * @param bool $case_insensitive [false] + * @param bool $recursive [false] + * @param bool $flat_result [true] If set to false and recursive is on the result is a nested array + * @param string $flat_separator [DATA_SEPARATOR] if flat result is true, can be any string * @return array */ public static function selectArrayFromOption( @@ -306,28 +336,63 @@ class ArrayHandler string $lookup, int|string|float|bool $search, bool $strict = false, - bool $case_senstivie = false, + bool $case_insensitive = false, + bool $recursive = false, + bool $flat_result = true, + string $flat_separator = self::DATA_SEPARATOR ): array { + // skip on empty + if ($array == []) { + return []; + } + // init return result $result = []; - if ($case_senstivie && is_string($search)) { + // case sensitive convert if string + if ($case_insensitive && is_string($search)) { $search = strtolower($search); } + foreach ($array as $key => $value) { - // skip on not set - if (!isset($value[$lookup])) { - continue; + // Handle current level search + if (isset($value[$lookup])) { + $compareValue = $value[$lookup]; + + if ($case_insensitive && is_string($compareValue)) { + $compareValue = strtolower($compareValue); + } + + if ( + ($strict && $search === $compareValue) || + (!$strict && $search == $compareValue) + ) { + $result[$key] = $value; + } } - if ($case_senstivie && is_string($value[$value])) { - $value[$lookup] = strtolower($value[$value]); - } - if ( - ($strict && $search === $value[$lookup]) || - (!$strict && $search == $value[$lookup]) - ) { - $result[$key] = $value; - continue; + // Handle recursive search if flag is set + if ($recursive && is_array($value)) { + $recursiveResults = self::selectArrayFromOption( + $value, + $lookup, + $search, + $strict, + $case_insensitive, + true, + $flat_result, + $flat_separator + ); + + // Merge recursive results with current results + // Preserve keys by using array_merge with string keys or + operator + foreach ($recursiveResults as $recKey => $recValue) { + if ($flat_result) { + $result[$key . $flat_separator . $recKey] = $recValue; + } else { + $result[$key][$recKey] = $recValue; + } + } } } + return $result; } @@ -653,8 +718,8 @@ class ArrayHandler * does not change order in array * * @param array $in_array - * @param string $key_mod_prefix [default=''] key prefix string - * @param string $key_mod_suffix [default=''] key suffix string + * @param string $key_mod_prefix [''] key prefix string + * @param string $key_mod_suffix [''] key suffix string * @return array */ public static function arrayModifyKey( @@ -680,35 +745,66 @@ class ArrayHandler /** * sort array and return in same call - * sort ascending, value + * sort ascending or descending with or without lower case convert + * value only, will loose key connections unless preserve_keys is set to true * * @param array $array array to sort by values * @param int $params sort flags * @return array */ - public function sortArray(array $array, int $params = SORT_REGULAR): array - { - return sort($array, $params) ? $array : $array; + public static function sortArray( + array $array, + bool $case_insensitive = false, + bool $reverse = false, + bool $maintain_keys = false, + int $params = SORT_REGULAR + ): array { + $fk_sort_lower_case = function (string $a, string $b): int { + return strtolower($a) <=> strtolower($b); + }; + $fk_sort_lower_case_reverse = function (string $a, string $b): int { + return strtolower($b) <=> strtolower($a); + }; + $case_insensitive ? ( + $maintain_keys ? + (uasort($array, $reverse ? $fk_sort_lower_case_reverse : $fk_sort_lower_case)) : + (usort($array, $reverse ? $fk_sort_lower_case_reverse : $fk_sort_lower_case)) + ) : + ( + $maintain_keys ? + ($reverse ? arsort($array, $params) : asort($array, $params)) : + ($reverse ? rsort($array, $params) : sort($array, $params)) + ); + return $array; } /** - * sort by key ascending and return + * sort by key ascending or descending and return * * @param array $array - * @param bool $lower_case [default=false] + * @param bool $case_insensitive [false] + * @param bool $reverse [false] * @return array */ - public static function ksortArray(array $array, bool $lower_case = false): array + public static function ksortArray(array $array, bool $case_insensitive = false, bool $reverse = false): array { $fk_sort_lower_case = function (string $a, string $b): int { return strtolower($a) <=> strtolower($b); }; + $fk_sort_lower_case_reverse = function (string $a, string $b): int { + return strtolower($b) <=> strtolower($a); + }; $fk_sort = function (string $a, string $b): int { return $a <=> $b; }; + $fk_sort_reverse = function (string $a, string $b): int { + return $b <=> $a; + }; uksort( $array, - $lower_case ? $fk_sort_lower_case : $fk_sort + $case_insensitive ? + ($reverse ? $fk_sort_lower_case_reverse : $fk_sort_lower_case) : + ($reverse ? $fk_sort_reverse : $fk_sort) ); return $array; } From dbc72472f994e7e73d51d2a707af1ee3a0fbc393 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Jun 2025 14:29:04 +0900 Subject: [PATCH 12/20] Strings regex validation Update regex validation and add validation class to return full information. Add helper to return last error message from defined constant --- ...edArrayHandlerFindArraysMissingKeyTest.php | 2 + ...LibsCombinedArrayHandlerKsortArrayTest.php | 2 +- ...dArrayHandlerSelectArrayFromOptionTest.php | 2 +- ...eLibsCombinedArrayHandlerSortArrayTest.php | 2 +- ...oreLibsConvertStringsRegexValidateTest.php | 283 ++++++++++++++++++ .../Convert/CoreLibsConvertStringsTest.php | 40 ++- www/admin/class_test.strings.php | 14 +- www/lib/CoreLibs/Convert/Strings.php | 48 ++- 8 files changed, 380 insertions(+), 13 deletions(-) create mode 100644 4dev/tests/Convert/CoreLibsConvertStringsRegexValidateTest.php diff --git a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php index 1bde4bad..9553c0af 100644 --- a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php +++ b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest.php @@ -1,5 +1,7 @@ this is valid +// '/test{1,}/', // Invalid quantifier -> this is valid + +declare(strict_types=1); + +namespace tests; + +use PHPUnit\Framework\TestCase; +use CoreLibs\Convert\Strings; + +/** + * Test class for CoreLibs\Convert\Strings regex validation methods + */ +class CoreLibsConvertStringsRegexValidateTest extends TestCase +{ + /** + * Test isValidRegex with valid regex patterns + */ + public function testIsValidRegexWithValidPatterns(): void + { + $validPatterns = [ + '/^[a-zA-Z0-9]+$/', + '/test/', + '/\d+/', + '/^hello.*world$/', + '/[0-9]{3}-[0-9]{3}-[0-9]{4}/', + '#^https?://.*#i', + '~^[a-z]+~', + '|test|', + '/^$/m', + '/\w+/u', + ]; + + foreach ($validPatterns as $pattern) { + $this->assertTrue( + Strings::isValidRegex($pattern), + "Pattern '{$pattern}' should be valid" + ); + } + } + + /** + * Test isValidRegex with invalid regex patterns + */ + public function testIsValidRegexWithInvalidPatterns(): void + { + $invalidPatterns = [ + '/[/', // Unmatched bracket + '/test[/', // Unmatched bracket + '/(?P/', // Unmatched parenthesis + '/(?P<>test)/', // Invalid named group + '/test\\/', // Invalid escape at end + '/(test/', // Unmatched parenthesis + '/test)/', // Unmatched parenthesis + // '/test{/', // Unmatched brace -> this is valid + // '/test{1,}/', // Invalid quantifier -> this is valid + '/[z-a]/', // Invalid character range + 'invalid', // No delimiters + '', // Empty string + '/(?P<123>test)/', // Invalid named group name + ]; + + foreach ($invalidPatterns as $pattern) { + $this->assertFalse( + Strings::isValidRegex($pattern), + "Pattern '{$pattern}' should be invalid" + ); + } + } + + /** + * Test getLastRegexErrorString returns correct error messages + */ + public function testGetLastRegexErrorStringReturnsCorrectMessages(): void + { + // Test with a valid regex first to ensure clean state + Strings::isValidRegex('/valid/'); + $this->assertEquals('No error', Strings::getLastRegexErrorString()); + + // Test with invalid regex to trigger an error + Strings::isValidRegex('/[/'); + $errorMessage = Strings::getLastRegexErrorString(); + + // The error message should be one of the defined messages + $this->assertContains($errorMessage, array_values(Strings::PREG_ERROR_MESSAGES)); + $this->assertNotEquals('Unknown error', $errorMessage); + } + + /** + * Test getLastRegexErrorString with unknown error + */ + public function testGetLastRegexErrorStringWithUnknownError(): void + { + // This is harder to test directly since we can't easily mock preg_last_error() + // but we can test the fallback behavior by reflection or assume it works + + // At minimum, ensure it returns a string + $result = Strings::getLastRegexErrorString(); + $this->assertIsString($result); + $this->assertNotEmpty($result); + } + + /** + * Test validateRegex with valid patterns + */ + public function testValidateRegexWithValidPatterns(): void + { + $validPatterns = [ + '/^test$/', + '/\d+/', + '/[a-z]+/i', + ]; + + foreach ($validPatterns as $pattern) { + $result = Strings::validateRegex($pattern); + + $this->assertIsArray($result); + $this->assertArrayHasKey('valid', $result); + $this->assertArrayHasKey('preg_error', $result); + $this->assertArrayHasKey('error', $result); + + $this->assertTrue($result['valid'], "Pattern '{$pattern}' should be valid"); + $this->assertEquals(PREG_NO_ERROR, $result['preg_error']); + $this->assertNull($result['error']); + } + } + + /** + * Test validateRegex with invalid patterns + */ + public function testValidateRegexWithInvalidPatterns(): void + { + $invalidPatterns = [ + '/[/', // Unmatched bracket + '/(?P/', // Unmatched parenthesis + '/test\\/', // Invalid escape at end + '/(test/', // Unmatched parenthesis + ]; + + foreach ($invalidPatterns as $pattern) { + $result = Strings::validateRegex($pattern); + + $this->assertIsArray($result); + $this->assertArrayHasKey('valid', $result); + $this->assertArrayHasKey('preg_error', $result); + $this->assertArrayHasKey('error', $result); + $this->assertArrayHasKey('pcre_error', $result); + + $this->assertFalse($result['valid'], "Pattern '{$pattern}' should be invalid"); + $this->assertNotEquals(PREG_NO_ERROR, $result['preg_error']); + $this->assertIsString($result['error']); + $this->assertNotNull($result['error']); + $this->assertNotEmpty($result['error']); + + // Verify error message is from our defined messages or 'Unknown error' + $this->assertTrue( + in_array($result['error'], array_values(Strings::PREG_ERROR_MESSAGES)) || + $result['error'] === 'Unknown error' + ); + } + } + + /** + * Test validateRegex array structure + */ + public function testValidateRegexArrayStructure(): void + { + $result = Strings::validateRegex('/test/'); + + // Test array structure for valid regex + $this->assertIsArray($result); + $this->assertCount(4, $result); + $this->assertArrayHasKey('valid', $result); + $this->assertArrayHasKey('preg_error', $result); + $this->assertArrayHasKey('error', $result); + + $result = Strings::validateRegex('/[/'); + + // Test array structure for invalid regex + $this->assertIsArray($result); + $this->assertCount(4, $result); + $this->assertArrayHasKey('valid', $result); + $this->assertArrayHasKey('preg_error', $result); + $this->assertArrayHasKey('error', $result); + $this->assertArrayHasKey('pcre_error', $result); + } + + /** + * Test that methods handle edge cases properly + */ + public function testEdgeCases(): void + { + // Empty string + $this->assertFalse(Strings::isValidRegex('')); + + $result = Strings::validateRegex(''); + $this->assertFalse($result['valid']); + + // Very long pattern + $longPattern = '/' . str_repeat('a', 1000) . '/'; + $this->assertTrue(Strings::isValidRegex($longPattern)); + + // Unicode patterns + $this->assertTrue(Strings::isValidRegex('/\p{L}+/u')); + $this->assertTrue(Strings::isValidRegex('/[α-ω]+/u')); + } + + /** + * Test PREG_ERROR_MESSAGES constant accessibility + */ + public function testPregErrorMessagesConstant(): void + { + $this->assertIsArray(Strings::PREG_ERROR_MESSAGES); + $this->assertNotEmpty(Strings::PREG_ERROR_MESSAGES); + + // Check that all expected PREG constants are defined + $expectedKeys = [ + PREG_NO_ERROR, + PREG_INTERNAL_ERROR, + PREG_BACKTRACK_LIMIT_ERROR, + PREG_RECURSION_LIMIT_ERROR, + PREG_BAD_UTF8_ERROR, + PREG_BAD_UTF8_OFFSET_ERROR, + PREG_JIT_STACKLIMIT_ERROR, + ]; + + foreach ($expectedKeys as $key) { + $this->assertArrayHasKey($key, Strings::PREG_ERROR_MESSAGES); + $this->assertIsString(Strings::PREG_ERROR_MESSAGES[$key]); + $this->assertNotEmpty(Strings::PREG_ERROR_MESSAGES[$key]); + } + } + + /** + * Test error state isolation between method calls + */ + public function testErrorStateIsolation(): void + { + // Start with invalid regex + Strings::isValidRegex('/[/'); + $firstError = Strings::getLastRegexErrorString(); + $this->assertNotEquals('No error', $firstError); + + // Use valid regex + Strings::isValidRegex('/valid/'); + $secondError = Strings::getLastRegexErrorString(); + $this->assertEquals('No error', $secondError); + + // Verify validateRegex clears previous errors + $result = Strings::validateRegex('/valid/'); + $this->assertTrue($result['valid']); + $this->assertEquals(PREG_NO_ERROR, $result['preg_error']); + } + + /** + * Test various regex delimiters + */ + public function testDifferentDelimiters(): void + { + $patterns = [ + '/test/', // forward slash + '#test#', // hash + '~test~', // tilde + '|test|', // pipe + '@test@', // at symbol + '!test!', // exclamation + '%test%', // percent + ]; + + foreach ($patterns as $pattern) { + $this->assertTrue( + Strings::isValidRegex($pattern), + "Pattern with delimiter '{$pattern}' should be valid" + ); + } + } +} + +// __END__ diff --git a/4dev/tests/Convert/CoreLibsConvertStringsTest.php b/4dev/tests/Convert/CoreLibsConvertStringsTest.php index eb34d4b2..3233ce07 100644 --- a/4dev/tests/Convert/CoreLibsConvertStringsTest.php +++ b/4dev/tests/Convert/CoreLibsConvertStringsTest.php @@ -632,15 +632,33 @@ final class CoreLibsConvertStringsTest extends TestCase return [ 'valid regex' => [ '/^[A-z]$/', - true + true, + [ + 'valid' => true, + 'preg_error' => 0, + 'error' => null, + 'pcre_error' => null + ], ], 'invalid regex A' => [ '/^[A-z]$', - false + false, + [ + 'valid' => false, + 'preg_error' => 1, + 'error' => 'Internal PCRE error', + 'pcre_error' => 'Internal error' + ], ], 'invalid regex B' => [ '/^[A-z$', - false + false, + [ + 'valid' => false, + 'preg_error' => 1, + 'error' => 'Internal PCRE error', + 'pcre_error' => 'Internal error' + ], ], ]; } @@ -656,11 +674,23 @@ final class CoreLibsConvertStringsTest extends TestCase * @param bool $expected * @return void */ - public function testIsValidRegexSimple(string $input, bool $expected): void + public function testIsValidRegexSimple(string $input, bool $expected, array $expected_extended): void { $this->assertEquals( $expected, - \CoreLibs\Convert\Strings::isValidRegexSimple($input) + \CoreLibs\Convert\Strings::isValidRegex($input), + 'Regex is not valid' + ); + $this->assertEquals( + $expected_extended, + \CoreLibs\Convert\Strings::validateRegex($input), + 'Validation of regex failed' + ); + $this->assertEquals( + // for true null is set, so we get here No Error + $expected_extended['error'] ?? \CoreLibs\Convert\Strings::PREG_ERROR_MESSAGES[0], + \CoreLibs\Convert\Strings::getLastRegexErrorString(), + 'Cannot match last preg error string' ); } } diff --git a/www/admin/class_test.strings.php b/www/admin/class_test.strings.php index f49ea6ad..95e4457a 100644 --- a/www/admin/class_test.strings.php +++ b/www/admin/class_test.strings.php @@ -128,11 +128,17 @@ print "Unique: " . Strings::removeDuplicates($input_string) . "
"; print "Unique: " . Strings::removeDuplicates(strtolower($input_string)) . "
"; $regex_string = "/^[A-z]$/"; -print "Regex valid: " . $regex_string . ": " - . DgS::prBl(Strings::isValidRegexSimple($regex_string)) . "
"; +print "Regex is: " . $regex_string . ": " . DgS::prBl(Strings::isValidRegex($regex_string)) . "
"; +$regex_string = "'//test{//'"; +print "Regex is: " . $regex_string . ": " . DgS::prBl(Strings::isValidRegex($regex_string)) . "
"; +print "Regex is: " . $regex_string . ": " . DgS::printAr(Strings::validateRegex($regex_string)) . "
"; $regex_string = "/^[A-z"; -print "Regex valid: " . $regex_string . ": " - . DgS::prBl(Strings::isValidRegexSimple($regex_string)) . "
"; +print "Regex is: " . $regex_string . ": " . DgS::prBl(Strings::isValidRegex($regex_string)) . "
"; +print "[A] LAST PREGE ERROR: " . preg_last_error() . " -> " + . (Strings::PREG_ERROR_MESSAGES[preg_last_error()] ?? '-') . "
"; +$preg_error = Strings::isValidRegex($regex_string); +print "[B] LAST PREGE ERROR: " . preg_last_error() . " -> " + . Strings::getLastRegexErrorString() . " -> " . preg_last_error_msg() . "
"; print ""; diff --git a/www/lib/CoreLibs/Convert/Strings.php b/www/lib/CoreLibs/Convert/Strings.php index 9a4905ae..ae87c4d2 100644 --- a/www/lib/CoreLibs/Convert/Strings.php +++ b/www/lib/CoreLibs/Convert/Strings.php @@ -12,6 +12,16 @@ use CoreLibs\Combined\ArrayHandler; class Strings { + /** @var array all the preg error messages */ + public const array PREG_ERROR_MESSAGES = [ + PREG_NO_ERROR => 'No error', + PREG_INTERNAL_ERROR => 'Internal PCRE error', + PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit exhausted', + PREG_RECURSION_LIMIT_ERROR => 'Recursion limit exhausted', + PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data', + PREG_BAD_UTF8_OFFSET_ERROR => 'Bad UTF-8 offset', + PREG_JIT_STACKLIMIT_ERROR => 'JIT stack limit exhausted' + ]; /** * return the number of elements in the split list * 0 if nothing / invalid split @@ -259,8 +269,9 @@ class Strings * @param string $pattern Any regex string * @return bool False on invalid regex */ - public static function isValidRegexSimple(string $pattern): bool + public static function isValidRegex(string $pattern): bool { + preg_last_error(); try { $var = ''; @preg_match($pattern, $var); @@ -269,6 +280,41 @@ class Strings return false; } } + + /** + * Returns the last preg error messages as string + * all messages are defined in PREG_ERROR_MESSAGES + * + * @return string + */ + public static function getLastRegexErrorString(): string + { + return self::PREG_ERROR_MESSAGES[preg_last_error()] ?? 'Unknown error'; + } + + /** + * check if a regex is invalid, returns array with flag and error string + * + * @param string $pattern + * @return array{valid:bool,preg_error:0,error:null|string,pcre_error:null|string} + */ + public static function validateRegex(string $pattern): array + { + // Clear any previous PCRE errors + preg_last_error(); + $var = ''; + if (@preg_match($pattern, $var) === false) { + $error = preg_last_error(); + return [ + 'valid' => false, + 'preg_error' => $error, + 'error' => self::PREG_ERROR_MESSAGES[$error] ?? 'Unknown error', + 'pcre_error' => preg_last_error_msg(), + ]; + } + + return ['valid' => true, 'preg_error' => PREG_NO_ERROR, 'error' => null, 'pcre_error' => null]; + } } // __END__ From 62d9cda3d025fd949b25ca9d2995d3a9cb6a280e Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Jun 2025 14:37:42 +0900 Subject: [PATCH 13/20] Add fix for splitFormatString with format parameter without any split characters Other phpstan fixes --- 4dev/tests/Convert/CoreLibsConvertStringsTest.php | 5 +++++ www/lib/CoreLibs/Convert/Strings.php | 9 +++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/4dev/tests/Convert/CoreLibsConvertStringsTest.php b/4dev/tests/Convert/CoreLibsConvertStringsTest.php index 3233ce07..ed4bbc97 100644 --- a/4dev/tests/Convert/CoreLibsConvertStringsTest.php +++ b/4dev/tests/Convert/CoreLibsConvertStringsTest.php @@ -135,6 +135,11 @@ final class CoreLibsConvertStringsTest extends TestCase public function splitFormatStringExceptionProvider(): array { return [ + 'string format with no splitter match' => [ + '1234', + '22', + '12-34' + ], 'invalid format string' => [ '1234', '2あ2', diff --git a/www/lib/CoreLibs/Convert/Strings.php b/www/lib/CoreLibs/Convert/Strings.php index ae87c4d2..30d4ad44 100644 --- a/www/lib/CoreLibs/Convert/Strings.php +++ b/www/lib/CoreLibs/Convert/Strings.php @@ -85,7 +85,12 @@ class Strings ); } // get the split characters that are not numerical and check they are ascii - $split_characters = self::removeDuplicates(preg_replace('/[0-9]/', '', $split_format)); + $split_characters = self::removeDuplicates(preg_replace('/[0-9]/', '', $split_format) ?: ''); + if (empty($split_characters)) { + throw new \InvalidArgumentException( + "A split character must exist in the format string: " . $split_format + ); + } if (preg_match('/[^\x20-\x7e]/', $split_characters)) { throw new \InvalidArgumentException( "The split character has to be a valid ascii character: " . $split_characters @@ -296,7 +301,7 @@ class Strings * check if a regex is invalid, returns array with flag and error string * * @param string $pattern - * @return array{valid:bool,preg_error:0,error:null|string,pcre_error:null|string} + * @return array{valid:bool,preg_error:int,error:null|string,pcre_error:null|string} */ public static function validateRegex(string $pattern): array { From 9115fc9557f59c00c6f47b57d591591c2480b87d Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Jun 2025 14:37:42 +0900 Subject: [PATCH 14/20] phan and phpstan fixes --- 4dev/tests/Convert/CoreLibsConvertStringsTest.php | 5 +++++ www/admin/class_test.array.php | 2 +- www/lib/CoreLibs/Combined/ArrayHandler.php | 4 ++-- www/lib/CoreLibs/Convert/Strings.php | 9 +++++++-- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/4dev/tests/Convert/CoreLibsConvertStringsTest.php b/4dev/tests/Convert/CoreLibsConvertStringsTest.php index 3233ce07..ed4bbc97 100644 --- a/4dev/tests/Convert/CoreLibsConvertStringsTest.php +++ b/4dev/tests/Convert/CoreLibsConvertStringsTest.php @@ -135,6 +135,11 @@ final class CoreLibsConvertStringsTest extends TestCase public function splitFormatStringExceptionProvider(): array { return [ + 'string format with no splitter match' => [ + '1234', + '22', + '12-34' + ], 'invalid format string' => [ '1234', '2あ2', diff --git a/www/admin/class_test.array.php b/www/admin/class_test.array.php index 87146eaf..439252f3 100644 --- a/www/admin/class_test.array.php +++ b/www/admin/class_test.array.php @@ -333,7 +333,7 @@ print "(kosrt, lower case, reverse): " print "
"; $nested = [ 'B' => 'foo', 'a', '0', 9, - 1 => ['z', 'b', 'a'], + '1' => ['z', 'b', 'a'], 'd' => ['zaip', 'bar', 'baz'] ]; print "Nested: " . DgS::printAr($nested) . "
"; diff --git a/www/lib/CoreLibs/Combined/ArrayHandler.php b/www/lib/CoreLibs/Combined/ArrayHandler.php index 16d2c3ab..4bf3b99d 100644 --- a/www/lib/CoreLibs/Combined/ArrayHandler.php +++ b/www/lib/CoreLibs/Combined/ArrayHandler.php @@ -251,10 +251,10 @@ class ArrayHandler * @param array $array * @param string|int|float|bool $search_value * @param string|array $required_key - * @param ?string $serach_key [null] + * @param ?string $search_key [null] * @param string $path_separator [DATA_SEPARATOR] * @param string $current_path - * @return array,path?:string,missing_key?:string}> + * @return array,path?:string,missing_key?:array}> */ public static function findArraysMissingKey( array $array, diff --git a/www/lib/CoreLibs/Convert/Strings.php b/www/lib/CoreLibs/Convert/Strings.php index ae87c4d2..30d4ad44 100644 --- a/www/lib/CoreLibs/Convert/Strings.php +++ b/www/lib/CoreLibs/Convert/Strings.php @@ -85,7 +85,12 @@ class Strings ); } // get the split characters that are not numerical and check they are ascii - $split_characters = self::removeDuplicates(preg_replace('/[0-9]/', '', $split_format)); + $split_characters = self::removeDuplicates(preg_replace('/[0-9]/', '', $split_format) ?: ''); + if (empty($split_characters)) { + throw new \InvalidArgumentException( + "A split character must exist in the format string: " . $split_format + ); + } if (preg_match('/[^\x20-\x7e]/', $split_characters)) { throw new \InvalidArgumentException( "The split character has to be a valid ascii character: " . $split_characters @@ -296,7 +301,7 @@ class Strings * check if a regex is invalid, returns array with flag and error string * * @param string $pattern - * @return array{valid:bool,preg_error:0,error:null|string,pcre_error:null|string} + * @return array{valid:bool,preg_error:int,error:null|string,pcre_error:null|string} */ public static function validateRegex(string $pattern): array { From 73fc74a43a7a377184d3b7ad25adbae339d0fce5 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Jun 2025 14:47:02 +0900 Subject: [PATCH 15/20] Fix old basic class call for random key length init --- www/lib/CoreLibs/Basic.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/lib/CoreLibs/Basic.php b/www/lib/CoreLibs/Basic.php index 1eb3410f..b7f900d5 100644 --- a/www/lib/CoreLibs/Basic.php +++ b/www/lib/CoreLibs/Basic.php @@ -383,7 +383,8 @@ class Basic public function initRandomKeyLength(int $key_length): bool { trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\RandomKey::setRandomKeyLength()', E_USER_DEPRECATED); - return \CoreLibs\Create\RandomKey::setRandomKeyLength($key_length); + // no op, we do no longer pre set the random key length + return true; } /** From 6c5af9138625741cb82a7a93143d5283d94ff3af Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Jun 2025 15:32:39 +0900 Subject: [PATCH 16/20] Add new Logging option to turn on error_log write On default the level for error_log write is Emergency. This can be changed either on class creation or via set/get methods. If logging is skipped because the logging level does not match the main logging level the error_log write is also skipped. Added MARK fields in the Logging class --- .../Logging/CoreLibsLoggingLoggingTest.php | 67 ++++++++- www/admin/class_test.logging.php | 1 + www/lib/CoreLibs/Logging/Logging.php | 135 +++++++++++++++--- 3 files changed, 180 insertions(+), 23 deletions(-) diff --git a/4dev/tests/Logging/CoreLibsLoggingLoggingTest.php b/4dev/tests/Logging/CoreLibsLoggingLoggingTest.php index 3d2b3894..13b888fe 100644 --- a/4dev/tests/Logging/CoreLibsLoggingLoggingTest.php +++ b/4dev/tests/Logging/CoreLibsLoggingLoggingTest.php @@ -249,7 +249,7 @@ final class CoreLibsLoggingLoggingTest extends TestCase $this->assertFalse( $log->loggingLevelIsDebug() ); - // not set, should be debug] + // not set, should be debug $log = new \CoreLibs\Logging\Logging([ 'log_file_id' => 'testSetLoggingLevel', 'log_folder' => self::LOG_FOLDER, @@ -297,6 +297,71 @@ final class CoreLibsLoggingLoggingTest extends TestCase $log->setLoggingLevel('NotGood'); } + /** + * Undocumented function + * + * @covers ::setErrorLogWriteLevel + * @covers ::getErrorLogWriteLevel + * @testdox setErrorLogWriteLevel set/get checks + * + * @return void + */ + public function testSetErrorLogWriteLevel(): void + { + // valid that is not Debug + $log = new \CoreLibs\Logging\Logging([ + 'log_file_id' => 'testSetErrorLogWriteLevel', + 'log_folder' => self::LOG_FOLDER, + 'error_log_write_level' => Level::Error + ]); + $this->assertEquals( + Level::Error, + $log->getErrorLogWriteLevel() + ); + // not set on init + $log = new \CoreLibs\Logging\Logging([ + 'log_file_id' => 'testSetErrorLogWriteLevel', + 'log_folder' => self::LOG_FOLDER, + ]); + $this->assertEquals( + Level::Emergency, + $log->getErrorLogWriteLevel() + ); + // invalid, should be Emergency, will throw excpetion too + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage( + 'Option: "error_log_write_level" is not of instance \CoreLibs\Logging\Logger\Level' + ); + $log = new \CoreLibs\Logging\Logging([ + 'log_file_id' => 'testSetLoggingLevel', + 'log_folder' => self::LOG_FOLDER, + 'error_log_write_level' => 'I' + ]); + $this->assertEquals( + Level::Emergency, + $log->getErrorLogWriteLevel() + ); + // set valid then change + $log = new \CoreLibs\Logging\Logging([ + 'log_file_id' => 'testSetErrorLogWriteLevel', + 'log_folder' => self::LOG_FOLDER, + 'error_log_write_level' => Level::Error + ]); + $this->assertEquals( + Level::Error, + $log->getErrorLogWriteLevel() + ); + $log->setErrorLogWriteLevel(Level::Notice); + $this->assertEquals( + Level::Notice, + $log->getErrorLogWriteLevel() + ); + // illegal logging level + $this->expectException(\Psr\Log\InvalidArgumentException::class); + $this->expectExceptionMessageMatches("/^Level \"NotGood\" is not defined, use one of: /"); + $log->setErrorLogWriteLevel('NotGood'); + } + // setLogFileId // getLogFileId diff --git a/www/admin/class_test.logging.php b/www/admin/class_test.logging.php index e1c71bfa..77a47426 100644 --- a/www/admin/class_test.logging.php +++ b/www/admin/class_test.logging.php @@ -82,6 +82,7 @@ $log->error('Cannot process data', ['error' => 'log']); $log->critical('Critical message', ['critical' => 'log']); $log->alert('Alert message', ['Alert' => 'log']); $log->emergency('Emergency message', ['Emergency' => 'log']); +error_log('TRIGGER ERROR LOG MANUAL: Emergency'); print "Log File: " . $log->getLogFile() . "
"; $log->setLogFlag(Flag::per_run); diff --git a/www/lib/CoreLibs/Logging/Logging.php b/www/lib/CoreLibs/Logging/Logging.php index 7476ef56..0f9c498b 100644 --- a/www/lib/CoreLibs/Logging/Logging.php +++ b/www/lib/CoreLibs/Logging/Logging.php @@ -35,6 +35,7 @@ class Logging /** @var string log file block separator, not changeable */ private const LOG_FILE_BLOCK_SEPARATOR = '.'; + // MARK: OPTION array // NOTE: the second party array{} hs some errors /** @var array>|array{string:array{type:string,type_info?:string,mandatory:true,alias?:string,default:string|bool|Level,deprecated:bool,use?:string}} */ private const OPTIONS = [ @@ -50,6 +51,7 @@ class Logging 'type' => 'string', 'mandatory' => false, 'default' => '', 'deprecated' => true, 'use' => 'log_file_id' ], + // log level 'log_level' => [ 'type' => 'instance', 'type_info' => '\CoreLibs\Logging\Logger\Level', @@ -57,6 +59,14 @@ class Logging 'default' => Level::Debug, 'deprecated' => false ], + // level to trigger write to error_log + 'error_log_write_level' => [ + 'type' => 'instance', + 'type_info' => '\CoreLibs\Logging\Logger\Level', + 'mandatory' => false, + 'default' => Level::Emergency, + 'deprecated' => false, + ], // options 'log_per_run' => [ 'type' => 'bool', 'mandatory' => false, @@ -92,8 +102,10 @@ class Logging /** @var array */ private array $options = []; - /** @var Level set level */ + /** @var Level set logging level */ private Level $log_level; + /** @var Level set level for writing to error_log, will not write if log level lower than error log write level */ + private Level $error_log_write_level; // page and host name /** @var string */ @@ -145,12 +157,13 @@ class Logging ]; /** - * Init logger + * MARK: Init logger * * options array layout * - log_folder: * - log_file_id / file_id (will be deprecated): * - log_level: + * - error_log_write_level: at what level we write to error_log * * - log_per_run: * - log_per_date: (was print_file_date) @@ -172,6 +185,8 @@ class Logging // set log level $this->initLogLevel(); + // set error log write level + $this->initErrorLogWriteLevel(); // set log folder from options $this->initLogFolder(); // set per run UID for logging @@ -190,8 +205,10 @@ class Logging // PRIVATE METHODS // ********************************************************************* + // MARK: options check + /** - * Undocumented function + * validate options * * @param array $options * @return bool @@ -263,6 +280,8 @@ class Logging return true; } + // MARK: init log elvels + /** * init log level, just a wrapper to auto set from options * @@ -280,6 +299,24 @@ class Logging $this->setLoggingLevel($this->options['log_level']); } + /** + * init error log write level + * + * @return void + */ + private function initErrorLogWriteLevel() + { + if ( + empty($this->options['error_log_write_level']) || + !$this->options['error_log_write_level'] instanceof Level + ) { + $this->options['error_log_write_level'] = Level::Emergency; + } + $this->setErrorLogWriteLevel($this->options['error_log_write_level']); + } + + // MARK: set log folder + /** * Set the log folder * If folder is not writeable the script will throw an E_USER_ERROR @@ -321,6 +358,8 @@ class Logging return $status; } + // MARK: set host name + /** * Set the hostname and port * If port is not defaul 80 it will be added to the host name @@ -337,6 +376,8 @@ class Logging } } + // MARK: set log file id (file) + /** * set log file prefix id * @@ -395,6 +436,8 @@ class Logging return $status; } + // MARK init log flags and levels + /** * set flags from options and option flags connection internal settings * @@ -423,6 +466,19 @@ class Logging return $this->log_level->includes($level); } + /** + * Checks that given level is matchins error_log write level + * + * @param Level $level + * @return bool + */ + private function checkErrorLogWriteLevel(Level $level): bool + { + return $this->error_log_write_level->includes($level); + } + + // MARK: build log ifle name + /** * Build the file name for writing * @@ -490,6 +546,8 @@ class Logging return $fn; } + // MARK: master write log to file + /** * writes error msg data to file for current level * @@ -507,6 +565,10 @@ class Logging if (!$this->checkLogLevel($level)) { return false; } + // if we match level then write to error_log + if ($this->checkErrorLogWriteLevel($level)) { + error_log((string)$message); + } // build logging file name // fn is log folder + file name @@ -531,6 +593,8 @@ class Logging return true; } + // MARK: master prepare log + /** * Prepare the log message with all needed info blocks: * [timestamp] [host name] [file path + file::row number] [running uid] {class::/->method} @@ -610,6 +674,7 @@ class Logging // PUBLIC STATIC METHJODS // ********************************************************************* + // MARK: set log level /** * set the log level * @@ -670,7 +735,7 @@ class Logging // **** GET/SETTER - // log level set and get + // MARK: log level /** * set new log level @@ -705,7 +770,30 @@ class Logging ); } - // log file id set (file name prefix) + // MARK: error log write level + + /** + * set the error_log write level + * + * @param string|int|Level $level + * @return void + */ + public function setErrorLogWriteLevel(string|int|Level $level): void + { + $this->error_log_write_level = $this->processLogLevel($level); + } + + /** + * get the current level for error_log write + * + * @return Level + */ + public function getErrorLogWriteLevel(): Level + { + return $this->error_log_write_level; + } + + // MARK: log file id set (file name prefix) /** * sets the internal log file prefix id @@ -733,7 +821,7 @@ class Logging return $this->log_file_id; } - // log unique id set (for per run) + // MARK: log unique id set (for per run) /** * Sets a unique id based on current date (y/m/d, h:i:s) and a unique id (8 chars) @@ -768,7 +856,7 @@ class Logging return $this->log_file_unique_id; } - // general log date + // MARK: general log date /** * set the log file date to Y-m-d @@ -791,7 +879,7 @@ class Logging return $this->log_file_date; } - // general flag set + // MARK: general flag set /** * set one of the basic flags @@ -846,7 +934,7 @@ class Logging return $this->log_flags; } - // log folder/file + // MARK: log folder/file /** * set new log folder, check that folder is writeable @@ -890,7 +978,7 @@ class Logging return $this->log_file_name; } - // max log file size + // MARK: max log file size /** * set mag log file size @@ -921,7 +1009,7 @@ class Logging } // ********************************************************************* - // OPTIONS CALLS + // MARK: OPTIONS CALLS // ********************************************************************* /** @@ -939,6 +1027,8 @@ class Logging // MAIN CALLS // ********************************************************************* + // MARK: main log call + /** * Commong log interface * @@ -976,7 +1066,7 @@ class Logging } /** - * DEBUG: 100 + * MARK: DEBUG: 100 * * write debug data to error_msg array * @@ -1008,7 +1098,7 @@ class Logging } /** - * INFO: 200 + * MARK: INFO: 200 * * @param string|Stringable $message * @param mixed[] $context @@ -1027,7 +1117,7 @@ class Logging } /** - * NOTICE: 250 + * MARK: NOTICE: 250 * * @param string|Stringable $message * @param mixed[] $context @@ -1046,7 +1136,7 @@ class Logging } /** - * WARNING: 300 + * MARK: WARNING: 300 * * @param string|Stringable $message * @param mixed[] $context @@ -1065,7 +1155,7 @@ class Logging } /** - * ERROR: 400 + * MARK: ERROR: 400 * * @param string|Stringable $message * @param mixed[] $context @@ -1084,7 +1174,7 @@ class Logging } /** - * CTRITICAL: 500 + * MARK: CTRITICAL: 500 * * @param string|Stringable $message * @param mixed[] $context @@ -1103,7 +1193,7 @@ class Logging } /** - * ALERT: 550 + * MARK: ALERT: 550 * * @param string|Stringable $message * @param mixed[] $context @@ -1122,7 +1212,7 @@ class Logging } /** - * EMERGENCY: 600 + * MARK: EMERGENCY: 600 * * @param string|Stringable $message * @param mixed[] $context @@ -1141,7 +1231,7 @@ class Logging } // ********************************************************************* - // DEPRECATED SUPPORT CALLS + // MARK: DEPRECATED SUPPORT CALLS // ********************************************************************* // legacy, but there are too many implemented @@ -1199,7 +1289,7 @@ class Logging } // ********************************************************************* - // DEPRECATED METHODS + // MARK: DEPRECATED METHODS // ********************************************************************* /** @@ -1365,7 +1455,7 @@ class Logging } // ********************************************************************* - // DEBUG METHODS + // MARK: DEBUG METHODS // ********************************************************************* /** @@ -1398,6 +1488,7 @@ class Logging } // back to options level $this->initLogLevel(); + $this->initErrorLogWriteLevel(); print "OPT set level: " . $this->getLoggingLevel()->getName() . "
"; } } From a292abc2c50a2dd50737634170c465c65077ee29 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Jun 2025 15:52:43 +0900 Subject: [PATCH 17/20] phpstan updates --- www/admin/class_test.deprecated.helper.php | 3 +++ www/lib/CoreLibs/Basic.php | 4 ++-- www/lib/CoreLibs/Check/Encoding.php | 12 +++++++++++- www/lib/CoreLibs/Convert/Encoding.php | 4 ++-- www/lib/CoreLibs/Create/Email.php | 13 +++++++++++++ www/lib/CoreLibs/UrlRequests/Curl.php | 2 +- 6 files changed, 32 insertions(+), 6 deletions(-) diff --git a/www/admin/class_test.deprecated.helper.php b/www/admin/class_test.deprecated.helper.php index b629ff05..2eb1d83d 100644 --- a/www/admin/class_test.deprecated.helper.php +++ b/www/admin/class_test.deprecated.helper.php @@ -83,6 +83,9 @@ function mtParseCSV( 'UTF-8', $encoding ); + if ($string === false) { + return $lines; + } } if ($flag == 'INTERN') { // split with PHP function diff --git a/www/lib/CoreLibs/Basic.php b/www/lib/CoreLibs/Basic.php index b7f900d5..ef93875a 100644 --- a/www/lib/CoreLibs/Basic.php +++ b/www/lib/CoreLibs/Basic.php @@ -989,10 +989,10 @@ class Basic * @param bool $auto_check default true, if source encoding is set * check that the source is actually matching * to what we sav the source is - * @return string encoding converted string + * @return string|false encoding converted string * @deprecated use \CoreLibs\Convert\Encoding::convertEncoding() instead */ - public static function convertEncoding(string $string, string $to_encoding, string $source_encoding = '', bool $auto_check = true): string + public static function convertEncoding(string $string, string $to_encoding, string $source_encoding = '', bool $auto_check = true): string|false { trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Encoding::convertEncoding()', E_USER_DEPRECATED); return \CoreLibs\Convert\Encoding::convertEncoding($string, $to_encoding, $source_encoding, $auto_check); diff --git a/www/lib/CoreLibs/Check/Encoding.php b/www/lib/CoreLibs/Check/Encoding.php index 577cf9b9..08cb9dfb 100644 --- a/www/lib/CoreLibs/Check/Encoding.php +++ b/www/lib/CoreLibs/Check/Encoding.php @@ -56,7 +56,11 @@ class Encoding { // return mb_substitute_character(); if ($return_substitute_func === true) { - return mb_substitute_character(); + // if false abort with error + if (($return = mb_substitute_character()) === false) { + return self::$mb_error_char; + } + return $return; } else { return self::$mb_error_char; } @@ -88,7 +92,13 @@ class Encoding ): array|false { // convert to target encoding and convert back $temp = mb_convert_encoding($string, $to_encoding, $from_encoding); + if ($temp === false) { + return false; + } $compare = mb_convert_encoding($temp, $from_encoding, $to_encoding); + if ($compare === false) { + return false; + } // if string does not match anymore we have a convert problem if ($string == $compare) { return false; diff --git a/www/lib/CoreLibs/Convert/Encoding.php b/www/lib/CoreLibs/Convert/Encoding.php index 26e1a9de..a0a80bf9 100644 --- a/www/lib/CoreLibs/Convert/Encoding.php +++ b/www/lib/CoreLibs/Convert/Encoding.php @@ -23,14 +23,14 @@ class Encoding * @param bool $auto_check default true, if source encoding is set * check that the source is actually matching * to what we sav the source is - * @return string encoding converted string + * @return string|false encoding converted string or false on error */ public static function convertEncoding( string $string, string $to_encoding, string $source_encoding = '', bool $auto_check = true - ): string { + ): string|false { // set if not given if (!$source_encoding) { $source_encoding = mb_detect_encoding($string); diff --git a/www/lib/CoreLibs/Create/Email.php b/www/lib/CoreLibs/Create/Email.php index 5e0c6f72..2f549bbf 100644 --- a/www/lib/CoreLibs/Create/Email.php +++ b/www/lib/CoreLibs/Create/Email.php @@ -38,6 +38,7 @@ class Email * @param string $encoding Encoding, if not set UTF-8 * @param bool $kv_folding If set to true and a valid encoding, do KV folding * @return string Correctly encoded and build email string + * @throws \IntlException if email name cannot be converted to UTF-8 */ public static function encodeEmailName( string $email, @@ -52,6 +53,10 @@ class Email if ($encoding != 'UTF-8') { $email_name = mb_convert_encoding($email_name, $encoding, 'UTF-8'); } + // if we cannot transcode the name, return just the email + if ($email_name === false) { + throw new \IntlException('Cannot convert email_name to UTF-8'); + } $email_name = mb_encode_mimeheader( in_array($encoding, self::$encoding_kv_allowed) && $kv_folding ? @@ -77,6 +82,8 @@ class Email * @param bool $kv_folding If set to true and a valid encoding, * do KV folding * @return array Pos 0: Subject, Pos 1: Body + * @throws \IntlException if subject cannot be converted to UTF-8 + * @throws \IntlException if body cannot be converted to UTF-8 */ private static function replaceContent( string $subject, @@ -102,6 +109,12 @@ class Email $subject = mb_convert_encoding($subject, $encoding, 'UTF-8'); $body = mb_convert_encoding($body, $encoding, 'UTF-8'); } + if ($subject === false) { + throw new \IntlException('Cannot convert subject to UTF-8'); + } + if ($body === false) { + throw new \IntlException('Cannot convert body to UTF-8'); + } // we need to encodde the subject $subject = mb_encode_mimeheader( in_array($encoding, self::$encoding_kv_allowed) && $kv_folding ? diff --git a/www/lib/CoreLibs/UrlRequests/Curl.php b/www/lib/CoreLibs/UrlRequests/Curl.php index ced5129f..40b306f1 100644 --- a/www/lib/CoreLibs/UrlRequests/Curl.php +++ b/www/lib/CoreLibs/UrlRequests/Curl.php @@ -599,7 +599,7 @@ class Curl implements Interface\RequestsInterface // for post we set POST option if ($type == "post") { curl_setopt($handle, CURLOPT_POST, true); - } elseif (!empty($type) && in_array($type, self::CUSTOM_REQUESTS)) { + } elseif (in_array($type, self::CUSTOM_REQUESTS)) { curl_setopt($handle, CURLOPT_CUSTOMREQUEST, strtoupper($type)); } // set body data if not null, will send empty [] for empty data From c4e83f94e95cfafb0ec710bbc9a6f02dd8abe3e3 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Jun 2025 17:56:29 +0900 Subject: [PATCH 18/20] Check scripts update --- 4dev/checking/phan.sh | 2 +- 4dev/checking/phpstan.sh | 2 +- 4dev/checking/phpunit.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/4dev/checking/phan.sh b/4dev/checking/phan.sh index 856a20c3..37c72234 100755 --- a/4dev/checking/phan.sh +++ b/4dev/checking/phan.sh @@ -1,4 +1,4 @@ -base="/storage/var/www/html/developers/clemens/core_data/php_libraries/trunk/"; +base=$(pwd)"/"; # must be run in ${base} cd $base || exit; ${base}tools/phan --progress-bar -C --analyze-twice; diff --git a/4dev/checking/phpstan.sh b/4dev/checking/phpstan.sh index 00481ad8..2ad6e444 100755 --- a/4dev/checking/phpstan.sh +++ b/4dev/checking/phpstan.sh @@ -1,4 +1,4 @@ -base="/storage/var/www/html/developers/clemens/core_data/php_libraries/trunk/"; +base=$(pwd)"/"; # must be run in ${base} cd $base || exit; ${base}tools/phpstan; diff --git a/4dev/checking/phpunit.sh b/4dev/checking/phpunit.sh index 40271ec8..29205df6 100755 --- a/4dev/checking/phpunit.sh +++ b/4dev/checking/phpunit.sh @@ -23,7 +23,7 @@ EOF } # set base variables -BASE_PATH="/storage/var/www/html/developers/clemens/core_data/php_libraries/trunk/"; +BASE_PATH=$(pwd)"/"; PHPUNIT_CONFIG="${BASE_PATH}phpunit.xml"; PHP_BIN_PATH=$(which php); if [ -z "${PHP_BIN_PATH}" ]; then From c43bb0662d5558fd6ac7f27ec046ad7d03a123e1 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Jun 2025 18:01:12 +0900 Subject: [PATCH 19/20] check scripts update: phan from phive is too old --- .phive/phars.xml | 2 +- 4dev/checking/phan.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.phive/phars.xml b/.phive/phars.xml index 3c6bd04e..5a90dcfe 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -3,7 +3,7 @@ - + diff --git a/4dev/checking/phan.sh b/4dev/checking/phan.sh index 37c72234..2366b134 100755 --- a/4dev/checking/phan.sh +++ b/4dev/checking/phan.sh @@ -1,5 +1,5 @@ base=$(pwd)"/"; # must be run in ${base} cd $base || exit; -${base}tools/phan --progress-bar -C --analyze-twice; +PHAN_DISABLE_XDEBUG_WARN=1;${base}tools/phan --progress-bar -C --analyze-twice cd ~ || exit; From ad7b59e26ac2598e87a0cfeae73f273304edbd8d Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Jun 2025 18:02:03 +0900 Subject: [PATCH 20/20] phan check swich from phive to composer package --- 4dev/checking/phan.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/4dev/checking/phan.sh b/4dev/checking/phan.sh index 2366b134..40f9877e 100755 --- a/4dev/checking/phan.sh +++ b/4dev/checking/phan.sh @@ -1,5 +1,6 @@ base=$(pwd)"/"; # must be run in ${base} cd $base || exit; -PHAN_DISABLE_XDEBUG_WARN=1;${base}tools/phan --progress-bar -C --analyze-twice +#PHAN_DISABLE_XDEBUG_WARN=1;${base}tools/phan --progress-bar -C --analyze-twice +PHAN_DISABLE_XDEBUG_WARN=1;${base}vendor/bin/phan --progress-bar -C --analyze-twice cd ~ || exit;