From 545279b9fea6f9aff53013b6b0a194b1a57b721d Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 16 Oct 2024 12:17:24 +0900 Subject: [PATCH 001/105] First tests with eslint flat layout --- .eslintrc.js | 43 -- .gitignore | 1 + eslint.config.js | 141 +++++++ package-lock.json | 972 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 + 5 files changed, 1120 insertions(+), 43 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 eslint.config.js create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 48fa262f..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,43 +0,0 @@ -module.exports = { - 'env': { - 'browser': true, - 'es6': true, - 'commonjs': true, - 'jquery': true - }, - 'extends': 'eslint:recommended', - 'parserOptions': { - 'ecmaVersion': 6 - }, - 'rules': { - 'indent': [ - 'error', - 'tab', - { - 'SwitchCase': 1 - } - ], - 'linebreak-style': [ - 'error', - 'unix' - ], - 'quotes': [ - 'error', - 'single' - ], - 'semi': [ - 'error', - 'always' - ], - 'no-console': 'off', - 'no-unused-vars': [ - 'error', { - 'vars': 'all', - 'args': 'after-used', - 'ignoreRestSiblings': false - } - ], - // Requires eslint >= v8.14.0 - 'no-constant-binary-expression': 'error' - } -}; diff --git a/.gitignore b/.gitignore index 9c0e66ec..2268defa 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .libs +node_modules/ diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..5d9aa4da --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,141 @@ +// // import globals from "globals"; +// import js from '@eslint/js'; + +module.exports = { + // 'env': { + // 'browser': true, + // 'es6': true, + // 'commonjs': true, + // 'jquery': true + // }, + // 'extends': 'eslint:recommended', + // 'parserOptions': { + // 'ecmaVersion': 11 + // }, + // ...eslint.configs.recommended, + 'rules': { + 'indent': [ + 'error', + 'tab', { + 'SwitchCase': 1 + } + ], + 'linebreak-style': [ + 'error', + 'unix' + ], + 'quotes': [ + 'error', + 'single', + { 'avoidEscape': true, 'allowTemplateLiterals': true } + ], + 'semi': [ + 'error', + 'always' + ], + 'no-console': 'off', + 'no-unused-vars': [ + 'error', { + 'vars': 'all', + 'args': 'after-used', + 'ignoreRestSiblings': false + } + ], + // Requires eslint >= v8.14.0 + 'no-constant-binary-expression': 'error' + } +}; + +// export default [ +// ...js.configs.recommended, +// { +// files: ['**/*.js'], +// 'rules': { +// 'indent': [ +// 'error', +// 'tab', { +// 'SwitchCase': 1 +// } +// ], +// 'linebreak-style': [ +// 'error', +// 'unix' +// ], +// 'quotes': [ +// 'error', +// 'single', +// { 'avoidEscape': true, 'allowTemplateLiterals': true } +// ], +// 'semi': [ +// 'error', +// 'always' +// ], +// 'no-console': 'off', +// 'no-unused-vars': [ +// 'error', { +// 'vars': 'all', +// 'args': 'after-used', +// 'ignoreRestSiblings': false +// } +// ], +// // Requires eslint >= v8.14.0 +// 'no-constant-binary-expression': 'error' +// }, +// languageOptions: { +// globals: { +// ...globals.browser, +// ...globals.commonjs, +// ...globals.jquery, +// // myCustomGlobal: "readonly" +// 'ecmaVersion': 11 +// } +// } +// } +// ]; + +// import globals from "globals"; +// import js from '@eslint/js'; + +// export default [ +// js.configs.recommended, +// { +// files: ['**/*.js'], +// rules: { +// 'indent': [ +// 'error', +// 'tab', { +// 'SwitchCase': 1 +// } +// ], +// 'linebreak-style': [ +// 'error', +// 'unix' +// ], +// 'quotes': [ +// 'error', +// 'single', +// { 'avoidEscape': true, 'allowTemplateLiterals': true } +// ], +// 'semi': [ +// 'error', +// 'always' +// ], +// 'no-console': 'off', +// 'no-unused-vars': [ +// 'error', { +// 'vars': 'all', +// 'args': 'after-used', +// 'ignoreRestSiblings': false +// } +// ], +// // Requires eslint >= v8.14.0 +// 'no-constant-binary-expression': 'error' +// }/* , +// languageOptions: { +// ecmaVersion: 11, +// // globals: { +// // ...globals.browser, +// // }, +// } */ +// } +// ]; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..cf0eb92f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,972 @@ +{ + "name": "trunk", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@eslint/js": "^9.12.0", + "eslint": "^9.12.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz", + "integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.12.0.tgz", + "integrity": "sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", + "integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", + "dev": true, + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", + "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.5", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", + "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.0", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.12.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz", + "integrity": "sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/core": "^0.6.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.12.0", + "@eslint/plugin-kit": "^0.2.0", + "@humanfs/node": "^0.16.5", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.1.0", + "eslint-visitor-keys": "^4.1.0", + "espree": "^10.2.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", + "integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", + "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", + "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", + "dev": true, + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..3a7b8349 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "@eslint/js": "^9.12.0", + "eslint": "^9.12.0" + } +} From cbd47fb0151ac8b04d1096e7de7437d59e8b766a Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Dec 2024 14:59:49 +0900 Subject: [PATCH 002/105] edit log table update, Change all DB tests serial to identity for primary key --- 4dev/database/table/edit_log.sql | 4 +-- .../CoreLibsACLLogin_database_create_data.sql | 32 +++++++++---------- 4dev/tests/DB/CoreLibsDBIOTest.php | 3 +- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/4dev/database/table/edit_log.sql b/4dev/database/table/edit_log.sql index 0ebf2599..e1de32d3 100644 --- a/4dev/database/table/edit_log.sql +++ b/4dev/database/table/edit_log.sql @@ -10,10 +10,10 @@ CREATE TABLE edit_log ( edit_log_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, euid INT, -- this is a foreign key, but I don't nedd to reference to it FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL, - username VARCHAR, - password VARCHAR, ecuid VARCHAR, ecuuid UUID, + username VARCHAR, + password VARCHAR, event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, ip VARCHAR, error TEXT, diff --git a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql index d3a8d0ea..3d4a54b4 100644 --- a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql +++ b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql @@ -321,7 +321,7 @@ CREATE TABLE edit_generic ( -- DROP TABLE edit_visible_group; CREATE TABLE edit_visible_group ( - edit_visible_group_id SERIAL PRIMARY KEY, + edit_visible_group_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name VARCHAR, flag VARCHAR ) INHERITS (edit_generic) WITHOUT OIDS; @@ -336,7 +336,7 @@ CREATE TABLE edit_visible_group ( -- DROP TABLE edit_menu_group; CREATE TABLE edit_menu_group ( - edit_menu_group_id SERIAL PRIMARY KEY, + edit_menu_group_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name VARCHAR, flag VARCHAR, order_number INT NOT NULL @@ -354,7 +354,7 @@ CREATE TABLE edit_menu_group ( -- DROP TABLE edit_page; CREATE TABLE edit_page ( - edit_page_id SERIAL PRIMARY KEY, + edit_page_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, content_alias_edit_page_id INT, -- alias for page content, if the page content is defined on a different page, ege for ajax backend pages FOREIGN KEY (content_alias_edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE RESTRICT ON UPDATE CASCADE, filename VARCHAR, @@ -378,7 +378,7 @@ CREATE TABLE edit_page ( -- DROP TABLE edit_query_string; CREATE TABLE edit_query_string ( - edit_query_string_id SERIAL PRIMARY KEY, + edit_query_string_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, edit_page_id INT NOT NULL, FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, enabled SMALLINT NOT NULL DEFAULT 0, @@ -430,7 +430,7 @@ CREATE TABLE edit_page_menu_group ( -- DROP TABLE edit_access_right; CREATE TABLE edit_access_right ( - edit_access_right_id SERIAL PRIMARY KEY, + edit_access_right_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name VARCHAR, level SMALLINT, type VARCHAR, @@ -447,7 +447,7 @@ CREATE TABLE edit_access_right ( -- DROP TABLE edit_scheme; CREATE TABLE edit_scheme ( - edit_scheme_id SERIAL PRIMARY KEY, + edit_scheme_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, enabled SMALLINT NOT NULL DEFAULT 0, name VARCHAR, header_color VARCHAR, @@ -466,7 +466,7 @@ CREATE TABLE edit_scheme ( -- DROP TABLE edit_language; CREATE TABLE edit_language ( - edit_language_id SERIAL PRIMARY KEY, + edit_language_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, enabled SMALLINT NOT NULL DEFAULT 0, lang_default SMALLINT NOT NULL DEFAULT 0, long_name VARCHAR, @@ -485,7 +485,7 @@ CREATE TABLE edit_language ( -- DROP TABLE edit_group; CREATE TABLE edit_group ( - edit_group_id SERIAL PRIMARY KEY, + edit_group_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, edit_scheme_id INT, FOREIGN KEY (edit_scheme_id) REFERENCES edit_scheme (edit_scheme_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, edit_access_right_id INT NOT NULL, @@ -507,7 +507,7 @@ CREATE TABLE edit_group ( -- DROP TABLE edit_page_access; CREATE TABLE edit_page_access ( - edit_page_access_id SERIAL PRIMARY KEY, + edit_page_access_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, edit_group_id INT NOT NULL, FOREIGN KEY (edit_group_id) REFERENCES edit_group (edit_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, edit_page_id INT NOT NULL, @@ -530,7 +530,7 @@ CREATE TABLE edit_page_access ( -- DROP TABLE edit_page_content; CREATE TABLE edit_page_content ( - edit_page_content_id SERIAL PRIMARY KEY, + edit_page_content_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, edit_page_id INT NOT NULL, FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, edit_access_right_id INT NOT NULL, @@ -551,7 +551,7 @@ CREATE TABLE edit_page_content ( -- DROP TABLE edit_user; CREATE TABLE edit_user ( - edit_user_id SERIAL PRIMARY KEY, + edit_user_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, connect_edit_user_id INT, -- possible reference to other user FOREIGN KEY (connect_edit_user_id) REFERENCES edit_user (edit_user_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, edit_language_id INT NOT NULL, @@ -652,11 +652,11 @@ COMMENT ON COLUMN edit_user.additional_acl IS 'Additional Access Control List st -- DROP TABLE edit_log; CREATE TABLE edit_log ( - edit_log_id SERIAL PRIMARY KEY, + edit_log_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, euid INT, -- this is a foreign key, but I don't nedd to reference to it + FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL, ecuid VARCHAR, ecuuid UUID, - FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL, username VARCHAR, password VARCHAR, event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, @@ -712,7 +712,7 @@ ALTER TABLE edit_log_overflow ADD CONSTRAINT edit_log_overflow_euid_fkey FOREIGN -- DROP TABLE edit_access; CREATE TABLE edit_access ( - edit_access_id SERIAL PRIMARY KEY, + edit_access_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, enabled SMALLINT NOT NULL DEFAULT 0, protected SMALLINT DEFAULT 0, deleted SMALLINT DEFAULT 0, @@ -733,7 +733,7 @@ CREATE TABLE edit_access ( -- DROP TABLE edit_access_user; CREATE TABLE edit_access_user ( - edit_access_user_id SERIAL PRIMARY KEY, + edit_access_user_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, edit_access_id INT NOT NULL, FOREIGN KEY (edit_access_id) REFERENCES edit_access (edit_access_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, edit_user_id INT NOT NULL, @@ -754,7 +754,7 @@ CREATE TABLE edit_access_user ( -- DROP TABLE edit_access_data; CREATE TABLE edit_access_data ( - edit_access_data_id SERIAL PRIMARY KEY, + edit_access_data_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, edit_access_id INT NOT NULL, FOREIGN KEY (edit_access_id) REFERENCES edit_access (edit_access_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, enabled SMALLINT NOT NULL DEFAULT 0, diff --git a/4dev/tests/DB/CoreLibsDBIOTest.php b/4dev/tests/DB/CoreLibsDBIOTest.php index ec8a28e1..19420a47 100644 --- a/4dev/tests/DB/CoreLibsDBIOTest.php +++ b/4dev/tests/DB/CoreLibsDBIOTest.php @@ -17,7 +17,7 @@ Table with Primary Key: table_with_primary_key Table without Primary Key: table_without_primary_key Table with primary key has additional row: -row_primary_key SERIAL PRIMARY KEY, +row_primary_key INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, Each table has the following rows row_int INT, row_numeric NUMERIC, @@ -160,7 +160,6 @@ final class CoreLibsDBIOTest extends TestCase // create the tables $db->dbExec( // primary key name is table + '_id' - // table_with_primary_key_id SERIAL PRIMARY KEY, << Date: Fri, 6 Dec 2024 14:54:09 +0900 Subject: [PATCH 003/105] Add logout button to class.test.php for logout test, ANY placeholder db test --- www/admin/class_test.db.query-placeholder.php | 15 +++++++++++ www/admin/class_test.php | 27 ++++++++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/www/admin/class_test.db.query-placeholder.php b/www/admin/class_test.db.query-placeholder.php index f93c39eb..b2d729c4 100644 --- a/www/admin/class_test.db.query-placeholder.php +++ b/www/admin/class_test.db.query-placeholder.php @@ -115,6 +115,21 @@ echo "INSERT ALL COLUMN TYPES: " . "ERROR: " . $db->dbGetLastError(true) . "
"; echo "
"; +print "ANY call
"; +$query = <<dbReturnParams($query, [$query_value]))) { + print "Result: " . Support::prAr($res) . "
"; +} + +echo "
"; + // test connectors: = , <> () for query detection // convert placeholder tests diff --git a/www/admin/class_test.php b/www/admin/class_test.php index 8b88bfe6..4f576fb2 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -62,9 +62,30 @@ $backend = new CoreLibs\Admin\Backend( $backend->db->dbInfo(true); ob_end_flush(); -print ""; -print "TEST CLASS"; -print ""; +print << + +TEST CLASS + + + +
+ +
+HTML; // key: file name, value; name $test_files = [ From b0449997725c07854c7c3ffae6290a91717b21b7 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 6 Dec 2024 16:31:20 +0900 Subject: [PATCH 004/105] ACL Login query update to params and heredoc All queries are in Params and all SQL is in heredoc blocks Disable 1011 error entry, this is no longer used --- www/lib/CoreLibs/ACL/Login.php | 708 ++++++++++++++++++++------------- 1 file changed, 422 insertions(+), 286 deletions(-) diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 691b03d6..ae9e53d7 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -276,10 +276,10 @@ class Login 'flag' => 'e' ], // blowfish password wrong - '1011' => [ + /* '1011' => [ 'msg' => 'Login Failed - Wrong Username or Password', 'flag' => 'e' - ], + ], */ // fallback md5 password wrong '1012' => [ 'msg' => 'Login Failed - Wrong Username or Password', @@ -373,8 +373,16 @@ class Login ]; // read the current edit_access_right list into an array - $q = "SELECT level, type, name FROM edit_access_right " - . "WHERE level >= 0 ORDER BY level"; + $q = <<= 0 + ORDER BY + level + SQL; while (is_array($res = $this->db->dbReturn($q))) { // level to description format (numeric) $this->default_acl_list[$res['level']] = [ @@ -398,7 +406,7 @@ class Login } // ************************************************************************* - // **** PROTECTED INTERNAL + // **** MARK: PROTECTED INTERNAL // ************************************************************************* /** @@ -441,7 +449,7 @@ class Login } // ************************************************************************* - // **** PRIVATE INTERNAL + // **** MARK: PRIVATE INTERNAL // ************************************************************************* /** @@ -630,6 +638,8 @@ class Login return true; } + // MARK: validation checks + /** * Checks for all flags and sets error codes for each * In order: @@ -748,6 +758,8 @@ class Login return $login_id_ok; } + // MARK: login user action + /** * if user pressed login button this script is called, * but only if there is no preview euid set @@ -769,74 +781,96 @@ class Login } // have to get the global stuff here for setting it later // we have to get the themes in here too - $q = "SELECT eu.edit_user_id, eu.cuid, eu.cuuid, eu.username, eu.password, " - . "eu.edit_group_id, " - . "eg.name AS edit_group_name, eu.admin, " - // additinal acl lists - . "eu.additional_acl AS user_additional_acl, " - . "eg.additional_acl AS group_additional_acl, " - // login error + locked - . "eu.login_error_count, eu.login_error_date_last, " - . "eu.login_error_date_first, eu.strict, eu.locked, " - // date based lock - . "CASE WHEN (" - . "(eu.lock_until IS NULL " - . "OR (eu.lock_until IS NOT NULL AND NOW() >= eu.lock_until)) " - . "AND (eu.lock_after IS NULL " - . "OR (eu.lock_after IS NOT NULL AND NOW() <= eu.lock_after))" - . ") THEN 0::INT ELSE 1::INT END locked_period, " - // debug (legacy) - . "eu.debug, eu.db_debug, " - // enabled - . "eu.enabled, eu.deleted, " - // for checks only - . "eu.login_user_id, " - // login id validation - . "CASE WHEN (" - . "(eu.login_user_id_valid_from IS NULL " - . "OR (eu.login_user_id_valid_from IS NOT NULL AND NOW() >= eu.login_user_id_valid_from)) " - . "AND (eu.login_user_id_valid_until IS NULL " - . "OR (eu.login_user_id_valid_until IS NOT NULL AND NOW() <= eu.login_user_id_valid_until))" - . ") THEN 1::INT ELSE 0::INT END AS login_user_id_valid_date, " - // check if user must login - . "CASE WHEN eu.login_user_id_revalidate_after IS NOT NULL " - . "AND eu.login_user_id_revalidate_after > '0 days'::INTERVAL " - . "AND (eu.login_user_id_last_revalidate + eu.login_user_id_revalidate_after)::DATE " - . "<= NOW()::DATE " - . "THEN 1::INT ELSE 0::INT END AS login_user_id_revalidate, " - . "eu.login_user_id_locked, " - // language - . "el.short_name AS locale, el.iso_name AS encoding, " - // levels - . "eareu.level AS user_level, eareu.type AS user_type, " - . "eareg.level AS group_level, eareg.type AS group_type, " - // colors - . "first.header_color AS first_header_color, " - . "second.header_color AS second_header_color, second.template " - . "FROM edit_user eu " - . "LEFT JOIN edit_scheme second ON " - . "(second.edit_scheme_id = eu.edit_scheme_id AND second.enabled = 1), " - . "edit_language el, edit_group eg, " - . "edit_access_right eareu, " - . "edit_access_right eareg, " - . "edit_scheme first " - . "WHERE first.edit_scheme_id = eg.edit_scheme_id " - . "AND eu.edit_group_id = eg.edit_group_id " - . "AND eu.edit_language_id = el.edit_language_id " - . "AND eu.edit_access_right_id = eareu.edit_access_right_id " - . "AND eg.edit_access_right_id = eareg.edit_access_right_id " - . "AND " - // either login_user_id OR password must be given - . (!empty($this->login_user_id && empty($this->username)) ? - // check with login id if set and NO username - "eu.login_user_id = " . $this->db->dbEscapeLiteral($this->login_user_id) . " " : - // password match is done in script, against old plain or new blowfish encypted - "LOWER(username) = " . $this->db->dbEscapeLiteral(strtolower($this->username)) . " " - ); + $q = <<= eu.lock_until) + ) + AND ( + eu.lock_after IS NULL + OR (eu.lock_after IS NOT NULL AND NOW() <= eu.lock_after) + ) + ) THEN 0::INT ELSE 1::INT END locked_period, + -- debug (legacy) + eu.debug, eu.db_debug, + -- enabled + eu.enabled, eu.deleted, + -- for checks only + eu.login_user_id, + -- login id validation + CASE WHEN ( + ( + eu.login_user_id_valid_from IS NULL + OR (eu.login_user_id_valid_from IS NOT NULL AND NOW() >= eu.login_user_id_valid_from) + ) + AND ( + eu.login_user_id_valid_until IS NULL + OR (eu.login_user_id_valid_until IS NOT NULL AND NOW() <= eu.login_user_id_valid_until) + ) + ) THEN 1::INT ELSE 0::INT END AS login_user_id_valid_date, + -- check if user must login + CASE WHEN + eu.login_user_id_revalidate_after IS NOT NULL + AND eu.login_user_id_revalidate_after > '0 days'::INTERVAL + AND (eu.login_user_id_last_revalidate + eu.login_user_id_revalidate_after)::DATE + <= NOW()::DATE + THEN 1::INT ELSE 0::INT END AS login_user_id_revalidate, + eu.login_user_id_locked, + -- language + el.short_name AS locale, el.iso_name AS encoding, + -- levels + eareu.level AS user_level, eareu.type AS user_type, + eareg.level AS group_level, eareg.type AS group_type, + -- colors + first.header_color AS first_header_color, + second.header_color AS second_header_color, second.template + FROM edit_user eu + LEFT JOIN edit_scheme second ON + (second.edit_scheme_id = eu.edit_scheme_id AND second.enabled = 1), + edit_language el, edit_group eg, + edit_access_right eareu, + edit_access_right eareg, + edit_scheme first + WHERE first.edit_scheme_id = eg.edit_scheme_id + AND eu.edit_group_id = eg.edit_group_id + AND eu.edit_language_id = el.edit_language_id + AND eu.edit_access_right_id = eareu.edit_access_right_id + AND eg.edit_access_right_id = eareg.edit_access_right_id + AND {SEARCH_QUERY} + SQL; + $params = []; + $replace_string = ''; + // either login_user_id OR password must be given + if (!empty($this->login_user_id && empty($this->username))) { + // check with login id if set and NO username + $replace_string = 'eu.login_user_id = $1'; + $params = [$this->login_user_id]; + } else { + // password match is done in script, against old plain or new blowfish encypted + $replace_string = 'LOWER(username) = $1'; + $params = [strtolower($this->username)]; + } + $q = str_replace( + '{SEARCH_QUERY}', + $replace_string, + $q + ); // reset any query data that might exist - $this->db->dbCacheReset($q); + $this->db->dbCacheReset($q, $params); // never cache return data - $res = $this->db->dbReturn($q, $this->db::NO_CACHE); + $res = $this->db->dbReturnParams($q, $params, $this->db::NO_CACHE); // query was not run successful if (!empty($this->db->dbGetLastError())) { $this->login_error = 1009; @@ -893,10 +927,15 @@ class Login // .' => HASH: '.(Password::passwordRehashCheck($res['password']) ? 'NEW NEEDED' : 'OK')); if (Password::passwordRehashCheck($res['password'])) { // update password hash to new one now - $q = "UPDATE edit_user " - . "SET password = '" . $this->db->dbEscapeString(Password::passwordSet($this->password)) - . "' WHERE edit_user_id = " . $res['edit_user_id']; - $this->db->dbExec($q); + $q = <<db->dbExecParams($q, [ + Password::passwordSet($this->password), + $res['edit_user_id'] + ]); } // normal user processing // set class var and session var @@ -911,14 +950,19 @@ class Login // check if user is okay $this->loginCheckPermissions(); if ($this->login_error == 0) { + // set the dit group id + $edit_group_id = $res["edit_group_id"]; + // update last revalidate flag if ( !empty($res['login_user_id']) && !empty($this->username) && !empty($this->password) ) { - $q = "UPDATE edit_user SET " - . "login_user_id_last_revalidate = NOW() " - . "WHERE edit_user_id = " . $this->euid; - $this->db->dbExec($q); + $q = <<db->dbExecParams($q, [$this->euid]); } $locale = $res['locale'] ?? 'en'; $encoding = $res['encoding'] ?? 'UTF-8'; @@ -963,28 +1007,37 @@ class Login // Check\Colors::validateColor() // reset any login error count for this user if ($res['login_error_count'] > 0) { - $q = "UPDATE edit_user " - . "SET login_error_count = 0, login_error_date_last = NULL, " - . "login_error_date_first = NULL " - . "WHERE edit_user_id = " . $res['edit_user_id']; - $this->db->dbExec($q); + $q = <<db->dbExecParams($q, [$this->euid]); } $edit_page_ids = []; $pages = []; $pages_acl = []; // set pages access - $q = "SELECT ep.edit_page_id, ep.cuid, epca.cuid AS content_alias_uid, " - . "ep.hostname, ep.filename, ep.name AS edit_page_name, " - . "ep.order_number AS edit_page_order, ep.menu, " - . "ep.popup, ep.popup_x, ep.popup_y, ep.online, ear.level, ear.type " - . "FROM edit_page ep " - . "LEFT JOIN edit_page epca ON (epca.edit_page_id = ep.content_alias_edit_page_id)" - . ", edit_page_access epa, edit_access_right ear " - . "WHERE ep.edit_page_id = epa.edit_page_id " - . "AND ear.edit_access_right_id = epa.edit_access_right_id " - . "AND epa.enabled = 1 AND epa.edit_group_id = " . $res["edit_group_id"] . " " - . "ORDER BY ep.order_number"; - while (is_array($res = $this->db->dbReturn($q))) { + $q = <<db->dbReturnParams($q, [$edit_group_id]))) { // page id array for sub data readout $edit_page_ids[$res['edit_page_id']] = $res['cuid']; // create the array for pages @@ -1010,21 +1063,30 @@ class Login // make reference filename -> level $pages_acl[$res['filename']] = $res['level']; } // for each page + // edit page id params + $params = ['{' . join(',', array_keys($edit_page_ids)) . '}']; // get the visible groups for all pages and write them to the pages - $q = "SELECT epvg.edit_page_id, name, flag " - . "FROM edit_visible_group evp, edit_page_visible_group epvg " - . "WHERE evp.edit_visible_group_id = epvg.edit_visible_group_id " - . "AND epvg.edit_page_id IN (" . join(', ', array_keys($edit_page_ids)) . ") " - . "ORDER BY epvg.edit_page_id"; - while (is_array($res = $this->db->dbReturn($q))) { + $q = <<db->dbReturnParams($q, $params))) { $pages[$edit_page_ids[$res['edit_page_id']]]['visible'][$res['name']] = $res['flag']; } // get the same for the query strings - $q = "SELECT eqs.edit_page_id, name, value, dynamic FROM edit_query_string eqs " - . "WHERE enabled = 1 AND edit_page_id " - . "IN (" . join(', ', array_keys($edit_page_ids)) . ") " - . "ORDER BY eqs.edit_page_id"; - while (is_array($res = $this->db->dbReturn($q))) { + $q = <<db->dbReturnParams($q, $params))) { $pages[$edit_page_ids[$res['edit_page_id']]]['query'][] = [ 'name' => $res['name'], 'value' => $res['value'], @@ -1032,13 +1094,17 @@ class Login ]; } // get the page content and add them to the page - $q = "SELECT epc.edit_page_id, epc.name, epc.uid, epc.order_number, " - . "epc.online, ear.level, ear.type " - . "FROM edit_page_content epc, edit_access_right ear " - . "WHERE epc.edit_access_right_id = ear.edit_access_right_id AND " - . "epc.edit_page_id IN (" . join(', ', array_keys($edit_page_ids)) . ") " - . "ORDER BY epc.order_number"; - while (is_array($res = $this->db->dbReturn($q))) { + $q = <<db->dbReturnParams($q, $params))) { $pages[$edit_page_ids[$res['edit_page_id']]]['content'][$res['uid']] = [ 'name' => $res['name'], 'uid' => $res['uid'], @@ -1055,28 +1121,36 @@ class Login 'PAGES_ACL_LEVEL' => $pages_acl, ]); // load the edit_access user rights - $q = "SELECT ea.edit_access_id, level, type, ea.name, " - . "ea.color, ea.uid, edit_default, ea.additional_acl " - . "FROM edit_access_user eau, edit_access_right ear, edit_access ea " - . "WHERE eau.edit_access_id = ea.edit_access_id " - . "AND eau.edit_access_right_id = ear.edit_access_right_id " - . "AND eau.enabled = 1 AND edit_user_id = " . $this->euid . " " - . "ORDER BY ea.name"; + $q = <<db->dbReturn($q))) { + $unit_uid_kookup = []; + while (is_array($res = $this->db->dbReturnParams($q, [$this->euid]))) { // read edit access data fields and drop them into the unit access array - $q_sub = "SELECT name, value " - . "FROM edit_access_data " - . "WHERE enabled = 1 AND edit_access_id = " . $res['edit_access_id']; + $q_sub = <<db->dbReturn($q_sub))) { + while (is_array($res_sub = $this->db->dbReturnParams($q_sub, [$res['edit_access_id']]))) { $ea_data[$res_sub['name']] = $res_sub['value']; } + $unit_cuid_lookup[$res['edit_access_id']] = $res['cuid']; // build master unit array - $unit_access[$res['edit_access_id']] = [ + $unit_access[$res['cuid']] = [ 'id' => (int)$res['edit_access_id'], 'acl_level' => $res['level'], 'acl_type' => $res['type'], @@ -1090,14 +1164,16 @@ class Login // set the default unit if ($res['edit_default']) { $this->session->set('UNIT_DEFAULT', (int)$res['edit_access_id']); + $this->session->set('UNIT_DEFAULT_CUID', (int)$res['cuid']); } - $unit_uid[$res['uid']] = (int)$res['edit_access_id']; + $unit_uid_kookup[$res['uid']] = (int)$res['edit_access_id']; // sub arrays for simple access array_push($eauid, $res['edit_access_id']); $unit_acl[$res['edit_access_id']] = $res['level']; } $this->session->setMany([ - 'UNIT_UID' => $unit_uid, + 'UNIT_UID' => $unit_uid_kookup, + 'UNIT_CUID' => $unit_cuid_lookup, 'UNIT' => $unit_access, 'UNIT_ACL_LEVEL' => $unit_acl, 'EAID' => $eauid, @@ -1110,11 +1186,18 @@ class Login $login_error_date_first = ", login_error_date_first = NOW()"; } // update login error count for this user - $q = "UPDATE edit_user " - . "SET login_error_count = login_error_count + 1, " - . "login_error_date_last = NOW() " . $login_error_date_first . " " - . "WHERE edit_user_id = " . $res['edit_user_id']; - $this->db->dbExec($q); + $q = <<db->dbExecParams( + str_replace('{LOGIN_ERROR_SQL}', $login_error_date_first, $q), + [$res['edit_user_id']] + ); // totally lock the user if error max is reached if ( $this->max_login_error_count != -1 && @@ -1124,7 +1207,12 @@ class Login // if strict is set, lock this user // this needs manual unlocking by an admin user if ($res['strict'] && !in_array($this->username, $this->lock_deny_users)) { - $q = "UPDATE edit_user SET locked = 1 WHERE edit_user_id = " . $res['edit_user_id']; + $q = <<debug('ACL', $this->print_ar($this->acl)); } + // MARK: lgin set locale + /** * set locale * if invalid, set to empty string @@ -1323,6 +1415,8 @@ class Login ]; } + // MARK: password handling + /** * checks if the password is in a valid format * @@ -1461,6 +1555,8 @@ class Login $this->writeEditLog($event, $data, $this->login_error, $this->pw_username); } + // MARK: set HTML login page + /** * creates the login html part if no permission (error) is set * this does not print anything yet @@ -1570,6 +1666,8 @@ class Login return $html_string; } + // MARK: logout call + /** * last function called, writes log and prints out error msg and * exists script if permission 0 @@ -1593,9 +1691,13 @@ class Login // prepare for log if ($this->euid) { // get user from user table - $q = "SELECT username FROM edit_user WHERE edit_user_id = " . $this->euid; + $q = <<db->dbReturnRow($q))) { + if (is_array($res = $this->db->dbReturnRowParams($q, [$this->euid]))) { $username = $res['username']; } } // if euid is set, get username (or try) @@ -1610,6 +1712,8 @@ class Login } } + // MARK: set template for login page + /** * checks if there are external templates, if not uses internal fallback ones * @@ -1967,7 +2071,7 @@ HTML; // **** PUBLIC INTERNAL // ************************************************************************* - // MARK: LOGIN CALL + // MARK: PUBLIC LOGIN CALL /** * Main call that needs to be run to actaully check for login @@ -2153,6 +2257,108 @@ HTML; return $this->login_is_ajax_page; } + /** + * Returns current set loginUserId or empty if unset + * + * @return string loginUserId or empty string for not set + */ + public function loginGetLoginUserId(): string + { + return $this->login_user_id; + } + + /** + * Returns GET/POST for where the loginUserId was set + * + * @return string GET or POST or empty string for not set + */ + public function loginGetLoginUserIdSource(): string + { + return $this->login_user_id_source; + } + + /** + * Returns unclear login user id state. If true then illegal characters + * where present in the loginUserId parameter + * + * @return bool False for clear, True if illegal characters found + */ + public function loginGetLoginUserIdUnclean(): bool + { + return $this->login_user_id_unclear; + } + + /** + * Return locale settings with + * locale + * domain + * encoding + * path + * + * empty string if not set + * + * @return array Locale settings + */ + public function loginGetLocale(): array + { + return $this->locale; + } + + /** + * return header color or null for not set + * + * @return string|null Header color in RGB hex with leading sharp + */ + public function loginGetHeaderColor(): ?string + { + return $this->session->get('HEADER_COLOR'); + } + + /** + * Return the current loaded list of pages the user can access + * + * @return array + */ + public function loginGetPages(): array + { + + return $this->session->get('PAGES'); + } + + // MARK: logged in uid(pk)/cuid/eccuid + + /** + * Get the current set EUID (edit user id) + * + * @return string EUID as string + */ + public function loginGetEuid(): string + { + return (string)$this->euid; + } + + /** + * Get the current set ECUID (edit user cuid) + * + * @return string ECUID as string + */ + public function loginGetEcuid(): string + { + return (string)$this->ecuid; + } + + /** + * Get the current set ECUUID (edit user cuuid) + * + * @return string ECUUID as string + */ + public function loginGetEcuuid(): string + { + return (string)$this->ecuuid; + } + + // MARK: get error messages + /** * returns the last set error code * @@ -2199,6 +2405,8 @@ HTML; return $string; } + // MARK: password checks + /** * Sets the minium length and checks on valid. * Current max length is 255 characters @@ -2251,6 +2459,8 @@ HTML; return $value; } + // MARK: max login count + /** * Set the maximum login errors a user can have before getting locked * if the user has the strict lock setting turned on @@ -2277,6 +2487,8 @@ HTML; return $this->max_login_error_count; } + // MARK: LGOUT USER + /** * if a user pressed on logout, destroyes session and unsets all global vars * @@ -2298,6 +2510,8 @@ HTML; $this->permission_okay = false; } + // MARK: logged in user permssion check + /** * for every page the user access this script checks if he is allowed to do so * @@ -2316,39 +2530,53 @@ HTML; if ($this->login_error == 103) { return $this->permission_okay; } - $q = "SELECT ep.filename, eu.cuid, eu.cuuid, " - // base lock flags - . "eu.deleted, eu.enabled, eu.locked, " - // date based lock - . "CASE WHEN (" - . "(eu.lock_until IS NULL " - . "OR (eu.lock_until IS NOT NULL AND NOW() >= eu.lock_until)) " - . "AND (eu.lock_after IS NULL " - . "OR (eu.lock_after IS NOT NULL AND NOW() <= eu.lock_after))" - . ") THEN 0::INT ELSE 1::INT END locked_period, " - // login id validation - . "login_user_id, " - . "CASE WHEN (" - . "(eu.login_user_id_valid_from IS NULL " - . "OR (eu.login_user_id_valid_from IS NOT NULL AND NOW() >= eu.login_user_id_valid_from)) " - . "AND (eu.login_user_id_valid_until IS NULL " - . "OR (eu.login_user_id_valid_until IS NOT NULL AND NOW() <= eu.login_user_id_valid_until))" - . ") THEN 1::INT ELSE 0::INT END AS login_user_id_valid_date, " - // check if user must login - . "CASE WHEN eu.login_user_id_revalidate_after IS NOT NULL " - . "AND eu.login_user_id_revalidate_after > '0 days'::INTERVAL " - . "AND eu.login_user_id_last_revalidate + eu.login_user_id_revalidate_after <= NOW()::DATE " - . "THEN 1::INT ELSE 0::INT END AS login_user_id_revalidate, " - . "eu.login_user_id_locked " - // - . "FROM edit_page ep, edit_page_access epa, edit_group eg, edit_user eu " - . "WHERE ep.edit_page_id = epa.edit_page_id " - . "AND eg.edit_group_id = epa.edit_group_id " - . "AND eg.edit_group_id = eu.edit_group_id " - . "AND eu.edit_user_id = " . $this->euid . " " - . "AND ep.filename = '" . $this->page_name . "' " - . "AND eg.enabled = 1 AND epa.enabled = 1"; - $res = $this->db->dbReturnRow($q); + $q = <<= eu.lock_until) + ) + AND ( + eu.lock_after IS NULL + OR (eu.lock_after IS NOT NULL AND NOW() <= eu.lock_after) + ) + ) THEN 0::INT ELSE 1::INT END locked_period, + -- login id validation + login_user_id, + CASE WHEN ( + ( + eu.login_user_id_valid_from IS NULL + OR (eu.login_user_id_valid_from IS NOT NULL AND NOW() >= eu.login_user_id_valid_from) + ) + AND ( + eu.login_user_id_valid_until IS NULL + OR (eu.login_user_id_valid_until IS NOT NULL AND NOW() <= eu.login_user_id_valid_until) + ) + ) THEN 1::INT ELSE 0::INT END AS login_user_id_valid_date, + -- check if user must login + CASE WHEN + eu.login_user_id_revalidate_after IS NOT NULL + AND eu.login_user_id_revalidate_after > '0 days'::INTERVAL + AND eu.login_user_id_last_revalidate + eu.login_user_id_revalidate_after <= NOW()::DATE + THEN 1::INT ELSE 0::INT END AS login_user_id_revalidate, + eu.login_user_id_locked + -- + FROM + edit_page ep, edit_page_access epa, edit_group eg, edit_user eu + WHERE + ep.edit_page_id = epa.edit_page_id + AND eg.edit_group_id = epa.edit_group_id + AND eg.edit_group_id = eu.edit_group_id + AND eg.enabled = 1 AND epa.enabled = 1 + AND eu.edit_user_id = $1 + AND ep.filename = $2 + SQL; + $res = $this->db->dbReturnRowParams($q, [$this->euid, $this->page_name]); if (!is_array($res)) { $this->login_error = 109; return $this->permission_okay; @@ -2403,6 +2631,8 @@ HTML; return $this->permission_okay; } + // MARK: ACL acess check + /** * Check if source (page, base) is matching to the given min access string * min access string must be valid access level string (eg read, mod, write) @@ -2503,6 +2733,8 @@ HTML; return (int)$this->default_acl_list_type[$type]; } + // MARK: edit access helpers + /** * checks if this edit access id is valid * @@ -2563,6 +2795,21 @@ HTML; return $_SESSION['UNIT'][$edit_access_id]['data'][$data_key]; } + /** + * old name for loginGetEditAccessData + * + * @deprecated Use $login->loginGetEditAccessData() + * @param int $edit_access_id + * @param string|int $data_key + * @return bool|string + */ + public function loginSetEditAccessData( + int $edit_access_id, + string|int $data_key + ): bool|string { + return $this->loginGetEditAccessData($edit_access_id, $data_key); + } + /** * Return edit access primary key id from edit access uid * false on not found @@ -2591,6 +2838,8 @@ HTML; return false; } + // MARK: various basic login id checks + /** * Returns true if login button was pressed * @@ -2600,119 +2849,6 @@ HTML; { return empty($this->login) ? false : true; } - - /** - * Returns current set loginUserId or empty if unset - * - * @return string loginUserId or empty string for not set - */ - public function loginGetLoginUserId(): string - { - return $this->login_user_id; - } - - /** - * Returns GET/POST for where the loginUserId was set - * - * @return string GET or POST or empty string for not set - */ - public function loginGetLoginUserIdSource(): string - { - return $this->login_user_id_source; - } - - /** - * Returns unclear login user id state. If true then illegal characters - * where present in the loginUserId parameter - * - * @return bool False for clear, True if illegal characters found - */ - public function loginGetLoginUserIdUnclean(): bool - { - return $this->login_user_id_unclear; - } - - /** - * old name for loginGetEditAccessData - * - * @deprecated Use $login->loginGetEditAccessData() - * @param int $edit_access_id - * @param string|int $data_key - * @return bool|string - */ - public function loginSetEditAccessData( - int $edit_access_id, - string|int $data_key - ): bool|string { - return $this->loginGetEditAccessData($edit_access_id, $data_key); - } - - /** - * Return locale settings with - * locale - * domain - * encoding - * path - * - * empty string if not set - * - * @return array Locale settings - */ - public function loginGetLocale(): array - { - return $this->locale; - } - - /** - * return header color or null for not set - * - * @return string|null Header color in RGB hex with leading sharp - */ - public function loginGetHeaderColor(): ?string - { - return $this->session->get('HEADER_COLOR'); - } - - /** - * Return the current loaded list of pages the user can access - * - * @return array - */ - public function loginGetPages(): array - { - - return $this->session->get('PAGES'); - } - - /** - * Get the current set EUID (edit user id) - * - * @return string EUID as string - */ - public function loginGetEuid(): string - { - return (string)$this->euid; - } - - /** - * Get the current set ECUID (edit user cuid) - * - * @return string ECUID as string - */ - public function loginGetEcuid(): string - { - return (string)$this->ecuid; - } - - /** - * Get the current set ECUUID (edit user cuuid) - * - * @return string ECUUID as string - */ - public function loginGetEcuuid(): string - { - return (string)$this->ecuuid; - } } // __END__ From a84ab86e31a7085b613b99ae79f48f4bb25984b0 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 6 Dec 2024 18:07:06 +0900 Subject: [PATCH 005/105] Various fixes for ACL Login methods with deprecated calls make all calls that go through primary keys as deprecated create CUID calls for all of them Update phpunit tests with new cuid tests, keep old deprecated tests --- 4dev/tests/ACL/CoreLibsACLLoginTest.php | 105 ++++++++++++++-- www/admin/class_test.login.php | 76 ++++++++++-- www/admin/class_test.php | 51 ++------ www/lib/CoreLibs/ACL/Login.php | 157 ++++++++++++++++-------- 4 files changed, 278 insertions(+), 111 deletions(-) diff --git a/4dev/tests/ACL/CoreLibsACLLoginTest.php b/4dev/tests/ACL/CoreLibsACLLoginTest.php index 92d3d978..a60f6f62 100644 --- a/4dev/tests/ACL/CoreLibsACLLoginTest.php +++ b/4dev/tests/ACL/CoreLibsACLLoginTest.php @@ -22,8 +22,12 @@ Not yet covered tests: */ final class CoreLibsACLLoginTest extends TestCase { - private static $db; - private static $log; + private static \CoreLibs\DB\IO $db; + private static \CoreLibs\Logging\Logging $log; + + private static string $edit_access_cuid; + private static string $edit_user_cuid; + private static string $edit_user_cuuid; /** * start DB conneciton, setup DB, etc @@ -108,14 +112,40 @@ final class CoreLibsACLLoginTest extends TestCase self::$db->dbSetMaxQueryCall(-1); // insert additional content for testing (locked user, etc) $queries = [ - "INSERT INTO edit_access_data " - . "(edit_access_id, name, value, enabled) VALUES " - . "((SELECT edit_access_id FROM edit_access WHERE uid = 'AdminAccess'), " - . "'test', 'value', 1)" + <<dbExec($query); } + // read edit access cuid, edit user cuid and edit user cuuid + $row = self::$db->dbReturnRowParams( + "SELECT cuid FROM edit_access WHERE uid = $1", + ["AdminAccess"] + ); + self::$edit_access_cuid = $row['cuid'] ?? ''; + if (empty(self::$edit_access_cuid)) { + self::markTestIncomplete( + 'Cannot read edit access cuid for "AdminAccess".' + ); + } + $row = self::$db->dbReturnRowParams( + "SELECT cuid, cuuid FROM edit_user WHERE username = $1", + ["admin"] + ); + self::$edit_user_cuid = $row['cuid'] ?? ''; + self::$edit_user_cuuid = $row['cuuid'] ?? ''; + if (empty(self::$edit_user_cuid) || empty(self::$edit_user_cuuid)) { + self::markTestIncomplete( + 'Cannot read edit user cuid or cuuid for "admin".' + ); + } // define mandatory constant // must set @@ -253,6 +283,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'edit_access_uid' => 'AdminAccess', 'edit_access_data' => 'test', 'base_access' => 'list', @@ -273,10 +304,11 @@ final class CoreLibsACLLoginTest extends TestCase 'USER_ADDITIONAL_ACL' => [], 'GROUP_ADDITIONAL_ACL' => [], 'UNIT_UID' => [ - 'AdminAccess' => 1, + 'AdminAccess' => '123456789012', ], 'UNIT' => [ - 1 => [ + '123456789012' => [ + 'id' => 1, 'acl_level' => 80, 'name' => 'Admin Access', 'uid' => 'AdminAccess', @@ -297,6 +329,7 @@ final class CoreLibsACLLoginTest extends TestCase 'admin_flag' => true, 'check_access' => true, 'check_access_id' => 1, + 'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'check_access_data' => 'value', 'base_access' => true, 'page_access' => true, @@ -416,6 +449,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'base_access' => 'list', 'page_access' => 'list', 'test_deleted' => true @@ -441,6 +475,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'base_access' => 'list', 'page_access' => 'list', 'test_enabled' => true @@ -466,6 +501,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'base_access' => 'list', 'page_access' => 'list', 'test_locked' => true @@ -491,6 +527,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'base_access' => 'list', 'page_access' => 'list', 'test_get_locked' => true, @@ -515,6 +552,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'base_access' => 'list', 'page_access' => 'list', 'test_locked_period_until' => 'on' @@ -540,6 +578,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'edit_access_uid' => 'AdminAccess', 'edit_access_data' => 'test', 'base_access' => 'list', @@ -559,6 +598,7 @@ final class CoreLibsACLLoginTest extends TestCase 'admin_flag' => true, 'check_access' => true, 'check_access_id' => 1, + 'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'check_access_data' => 'value', 'base_access' => true, 'page_access' => true, @@ -569,6 +609,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'base_access' => 'list', 'page_access' => 'list', 'test_locked_period_after' => 'on' @@ -594,6 +635,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'base_access' => 'list', 'page_access' => 'list', 'test_locked_period_until' => 'on', @@ -620,6 +662,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'base_access' => 'list', 'page_access' => 'list', 'test_login_user_id_locked' => true @@ -645,6 +688,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'edit_access_uid' => 'AdminAccess', 'edit_access_data' => 'test', 'base_access' => 'list', @@ -663,6 +707,7 @@ final class CoreLibsACLLoginTest extends TestCase 'admin_flag' => true, 'check_access' => true, 'check_access_id' => 1, + 'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'check_access_data' => 'value', 'base_access' => true, 'page_access' => true, @@ -673,6 +718,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'edit_access_uid' => 'AdminAccess', 'edit_access_data' => 'test', 'base_access' => 'list', @@ -692,6 +738,7 @@ final class CoreLibsACLLoginTest extends TestCase 'admin_flag' => true, 'check_access' => true, 'check_access_id' => 1, + 'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'check_access_data' => 'value', 'base_access' => true, 'page_access' => true, @@ -702,6 +749,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'edit_access_uid' => 'AdminAccess', 'edit_access_data' => 'test', 'base_access' => 'list', @@ -721,6 +769,7 @@ final class CoreLibsACLLoginTest extends TestCase 'admin_flag' => true, 'check_access' => true, 'check_access_id' => 1, + 'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'check_access_data' => 'value', 'base_access' => true, 'page_access' => true, @@ -731,6 +780,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'edit_access_uid' => 'AdminAccess', 'edit_access_data' => 'test', 'base_access' => 'list', @@ -750,6 +800,7 @@ final class CoreLibsACLLoginTest extends TestCase 'admin_flag' => true, 'check_access' => true, 'check_access_id' => 1, + 'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'check_access_data' => 'value', 'base_access' => true, 'page_access' => true, @@ -781,6 +832,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'edit_access_uid' => 'AdminAccess', 'edit_access_data' => 'test', 'base_access' => 'list', @@ -804,6 +856,7 @@ final class CoreLibsACLLoginTest extends TestCase 'admin_flag' => true, 'check_access' => true, 'check_access_id' => 1, + 'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'check_access_data' => 'value', 'base_access' => true, 'page_access' => true, @@ -814,6 +867,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'edit_access_uid' => 'AdminAccess', 'edit_access_data' => 'test', 'base_access' => 'list', @@ -837,6 +891,7 @@ final class CoreLibsACLLoginTest extends TestCase 'admin_flag' => true, 'check_access' => true, 'check_access_id' => 1, + 'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'check_access_data' => 'value', 'base_access' => true, 'page_access' => true, @@ -847,6 +902,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'base_access' => 'list', 'page_access' => 'list', 'test_login_user_id_revalidate_after' => 'on', @@ -873,6 +929,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'edit_access_uid' => 'AdminAccess', 'edit_access_data' => 'test', 'base_access' => 'list', @@ -893,6 +950,7 @@ final class CoreLibsACLLoginTest extends TestCase 'admin_flag' => true, 'check_access' => true, 'check_access_id' => 1, + 'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'check_access_data' => 'value', 'base_access' => true, 'page_access' => true, @@ -903,6 +961,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'base_access' => 'list', 'page_access' => 'list', 'test_login_user_id_valid_from' => 'on', @@ -929,6 +988,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'edit_access_uid' => 'AdminAccess', 'edit_access_data' => 'test', 'base_access' => 'list', @@ -949,6 +1009,7 @@ final class CoreLibsACLLoginTest extends TestCase 'admin_flag' => true, 'check_access' => true, 'check_access_id' => 1, + 'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'check_access_data' => 'value', 'base_access' => true, 'page_access' => true, @@ -959,6 +1020,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'base_access' => 'list', 'page_access' => 'list', 'test_login_user_id_valid_until' => 'on', @@ -985,6 +1047,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'base_access' => 'list', 'page_access' => 'list', 'test_login_user_id_valid_from' => 'on', @@ -1012,6 +1075,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'edit_access_uid' => 'AdminAccess', 'edit_access_data' => 'test', 'base_access' => 'list', @@ -1042,6 +1106,7 @@ final class CoreLibsACLLoginTest extends TestCase 'admin_flag' => true, 'check_access' => true, 'check_access_id' => 1, + 'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'check_access_data' => 'value', 'base_access' => true, 'page_access' => true, @@ -1369,6 +1434,9 @@ final class CoreLibsACLLoginTest extends TestCase // run test try { + // preset, we cannot set that in the provider + $expected['check_access_cuid'] = self::$edit_access_cuid; + $mock_settings['edit_access_cuid'] = self::$edit_access_cuid; // if ajax call // check if parameter, or globals (old type) // else normal call @@ -1427,6 +1495,25 @@ final class CoreLibsACLLoginTest extends TestCase $login_mock->loginCheckAccessPage($mock_settings['page_access']), 'Assert page access' ); + // - loginCheckEditAccessCuid + $this->assertEquals( + $expected['check_access'], + $login_mock->loginCheckEditAccessCuid($mock_settings['edit_access_cuid']), + 'Assert check access' + ); + // - loginCheckEditAccessValidCuid + $this->assertEquals( + $expected['check_access_cuid'], + $login_mock->loginCheckEditAccessValidCuid($mock_settings['edit_access_cuid']), + 'Assert check access cuid valid' + ); + // - loginGetEditAccessCuidFromUid + $this->assertEquals( + $expected['check_access_cuid'], + $login_mock->loginGetEditAccessCuidFromUid($mock_settings['edit_access_uid']), + 'Assert check access uid to cuid valid' + ); + // Deprecated // - loginCheckEditAccess $this->assertEquals( $expected['check_access'], @@ -1449,7 +1536,7 @@ final class CoreLibsACLLoginTest extends TestCase $this->assertEquals( $expected['check_access_data'], $login_mock->loginGetEditAccessData( - $mock_settings['edit_access_id'], + $mock_settings['edit_access_uid'], $mock_settings['edit_access_data'] ), 'Assert check access id data value valid' diff --git a/www/admin/class_test.login.php b/www/admin/class_test.login.php index 3d1327d5..bdf414b5 100644 --- a/www/admin/class_test.login.php +++ b/www/admin/class_test.login.php @@ -17,6 +17,9 @@ require 'config.php'; // define log file id $LOG_FILE_ID = 'classTest-login'; $SET_SESSION_NAME = EDIT_SESSION_NAME; + +use CoreLibs\Debug\Support; + // init login & backend class $session = new CoreLibs\Create\Session($SET_SESSION_NAME); $log = new CoreLibs\Logging\Logging([ @@ -43,24 +46,81 @@ ob_end_flush(); $login->loginMainCall(); $PAGE_NAME = 'TEST CLASS: LOGIN'; -print ""; -print "" . $PAGE_NAME . ""; -print ""; -print ''; -print '

' . $PAGE_NAME . '

'; +print str_replace( + '{PAGE_NAME}', + $PAGE_NAME, +<< + +{PAGE_NAME} + + + +

{PAGE_NAME}

+HTML +); + +// button logout +print << +function loginLogout() +{ + const form = document.createElement('form'); + form.method = 'post'; + const hiddenField = document.createElement('input'); + hiddenField.type = 'hidden'; + hiddenField.name = 'login_logout'; + hiddenField.value = 'Logout'; + form.appendChild(hiddenField); + document.body.appendChild(form); + form.submit(); +} + +
+ +
+HTML; +// string logout +print << +
+Logout + +
+ +HTML; echo "CHECK PERMISSION: " . ($login->loginCheckPermissions() ? 'OK' : 'BAD') . "
"; echo "IS ADMIN: " . ($login->loginIsAdmin() ? 'OK' : 'BAD') . "
"; echo "MIN ACCESS BASE: " . ($login->loginCheckAccessBase('admin') ? 'OK' : 'BAD') . "
"; echo "MIN ACCESS PAGE: " . ($login->loginCheckAccessPage('admin') ? 'OK' : 'BAD') . "
"; -echo "ACL: " . \CoreLibs\Debug\Support::printAr($login->loginGetAcl()) . "
"; -echo "ACL (MIN): " . \CoreLibs\Debug\Support::printAr($login->loginGetAcl()['min'] ?? []) . "
"; -echo "LOCALE: " . \CoreLibs\Debug\Support::printAr($login->loginGetLocale()) . "
"; +echo "ACL: " . Support::printAr($login->loginGetAcl()) . "
"; +echo "ACL (MIN): " . Support::printAr($login->loginGetAcl()['min'] ?? []) . "
"; +echo "LOCALE: " . Support::printAr($login->loginGetLocale()) . "
"; echo "ECUID: " . $login->loginGetEcuid() . "
"; echo "ECUUID: " . $login->loginGetEcuuid() . "
"; +echo "
"; +// set + check edit access id +$edit_access_cuid = 'buRW8Gu2Lkkf'; +if (isset($login->loginGetAcl()['unit'])) { + print "EDIT ACCESS CUID: " . $edit_access_cuid . "
"; + print "ACL UNIT: " . print_r(array_keys($login->loginGetAcl()['unit']), true) . "
"; + print "ACCESS CHECK: " . Support::prBl($login->loginCheckEditAccessCuid($edit_access_cuid)) . "
"; + if ($login->loginCheckEditAccessCuid($edit_access_cuid)) { + print "Set new:" . $edit_access_cuid . "
"; + } else { + print "Load default unit id: " . $login->loginGetAcl()['unit_id'] . "
"; + } +} else { + print "Something went wrong with the login
"; +} + +echo "
"; +print "SESSION: " . Support::printAr($_SESSION) . "
"; + $login->writeLog( 'TEST LOG', [ diff --git a/www/admin/class_test.php b/www/admin/class_test.php index 4f576fb2..7bff5bb9 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -149,33 +149,20 @@ foreach ($test_files as $file => $name) { print ''; } + +print "
"; +print "ECUID: " . $session->get('ECUID') . "
"; +print "ECUUID: " . $session->get('ECUUID') . "
"; + print "
"; -print "L: " . Support::dumpVar($locale) . "
"; +print "LOCALE: " . Support::dumpVar($locale) . "
"; // print all _ENV vars set print "
READ _ENV ARRAY:
"; print Support::dumpVar(array_map('htmlentities', $_ENV)); -// set + check edit access id -$edit_access_id = 3; -if (isset($login->loginGetAcl()['unit'])) { - print "ACL UNIT: " . print_r(array_keys($login->loginGetAcl()['unit']), true) . "
"; - print "ACCESS CHECK: " . (string)$login->loginCheckEditAccess($edit_access_id) . "
"; - if ($login->loginCheckEditAccess($edit_access_id)) { - $backend->edit_access_id = $edit_access_id; - } else { - $backend->edit_access_id = $login->loginGetAcl()['unit_id']; - } -} else { - print "Something went wrong with the login
"; -} // $backend->log->debug('SESSION', \CoreLibs\Debug\Support::dumpVar($_SESSION)); -print '
'; -print 'Logout'; -print ''; -print '
'; - +print "
"; print "Log Level: " . $backend->log->getLoggingLevel()->getName() . "
"; print "Log ID: " . $backend->log->getLogFileId() . "
"; print "Log Date: " . $backend->log->getLogDate() . "
"; @@ -197,26 +184,7 @@ foreach ( $log->debug('SOME MARK', 'Some error output'); -// INTERNAL SET -print "EDIT ACCESS ID: " . $backend->edit_access_id . "
"; -// print "ACL:
".$backend->print_ar($login->loginGetAcl())."
"; -// $log->debug('ACL', "ACL: " . \CoreLibs\Debug\Support::dumpVar($login->loginGetAcl())); -// print "DEFAULT ACL:
".$backend->print_ar($login->default_acl_list)."
"; -// print "DEFAULT ACL:
".$backend->print_ar($login->default_acl_list)."
"; -// $result = array_flip( -// array_filter( -// array_flip($login->default_acl_list), -// function ($key) { -// if (is_numeric($key)) { -// return $key; -// } -// } -// ) -// ); -// print "DEFAULT ACL:
".$backend->print_ar($result)."
"; -// DEPRICATED CALL -// $backend->adbSetACL($login->loginGetAcl()); - +print "
"; print "THIS HOST: " . HOST_NAME . ", with PROTOCOL: " . HOST_PROTOCOL . " is running SSL: " . HOST_SSL . "
"; print "DIR: " . DIR . "
"; print "BASE: " . BASE . "
"; @@ -226,9 +194,6 @@ print "HOST: " . HOST_NAME . " => DB HOST: " . DB_CONFIG_NAME . " => " . Support print "DS is: " . DIRECTORY_SEPARATOR . "
"; print "SERVER HOST: " . $_SERVER['HTTP_HOST'] . "
"; -print "ECUID: " . $session->get('ECUID') . "
"; -print "ECUUID: " . $session->get('ECUUID') . "
"; - print ""; # __END__ diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index ae9e53d7..a39dbaa8 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -1132,11 +1132,14 @@ class Login AND eau.enabled = 1 AND edit_user_id = $1 ORDER BY ea.name SQL; - $unit_access = []; + $unit_access_cuid = []; + // legacy + $unit_access_eaid = []; $unit_cuid_lookup = []; - $eauid = []; + $eaid = []; + $eacuid = []; $unit_acl = []; - $unit_uid_kookup = []; + $unit_uid_lookup = []; while (is_array($res = $this->db->dbReturnParams($q, [$this->euid]))) { // read edit access data fields and drop them into the unit access array $q_sub = <<db->dbReturnParams($q_sub, [$res['edit_access_id']]))) { $ea_data[$res_sub['name']] = $res_sub['value']; } - $unit_cuid_lookup[$res['edit_access_id']] = $res['cuid']; // build master unit array - $unit_access[$res['cuid']] = [ - 'id' => (int)$res['edit_access_id'], + $unit_access_cuid[$res['cuid']] = [ + 'id' => (int)$res['edit_access_id'], // DEPRECATED 'acl_level' => $res['level'], 'acl_type' => $res['type'], 'name' => $res['name'], @@ -1161,22 +1163,29 @@ class Login 'additional_acl' => Json::jsonConvertToArray($res['additional_acl']), 'data' => $ea_data ]; + $unit_access_eaid[$res['edit_access_id']] = [ + 'cuid' => $res['cuid'], + ]; // set the default unit if ($res['edit_default']) { - $this->session->set('UNIT_DEFAULT', (int)$res['edit_access_id']); - $this->session->set('UNIT_DEFAULT_CUID', (int)$res['cuid']); + $this->session->set('UNIT_DEFAULT_EAID', (int)$res['edit_access_id']); // DEPRECATED + $this->session->set('UNIT_DEFAULT_EACUID', (int)$res['cuid']); } - $unit_uid_kookup[$res['uid']] = (int)$res['edit_access_id']; + $unit_uid_lookup[$res['uid']] = $res['edit_access_id']; // DEPRECATED + $unit_cuid_lookup[$res['uid']] = $res['cuid']; // sub arrays for simple access - array_push($eauid, $res['edit_access_id']); - $unit_acl[$res['edit_access_id']] = $res['level']; + array_push($eaid, $res['edit_access_id']); + array_push($eacuid, $res['cuid']); + $unit_acl[$res['cuid']] = $res['level']; } $this->session->setMany([ - 'UNIT_UID' => $unit_uid_kookup, + 'UNIT_UID' => $unit_uid_lookup, // DEPRECATED 'UNIT_CUID' => $unit_cuid_lookup, - 'UNIT' => $unit_access, + 'UNIT' => $unit_access_cuid, + 'UNIT_LEGACY' => $unit_access_eaid, // DEPRECATED 'UNIT_ACL_LEVEL' => $unit_acl, - 'EAID' => $eauid, + 'EAID' => $eaid, // DEPRECATED + 'EACUID' => $eacuid, ]); } // user has permission to THIS page } // user was not enabled or other login error @@ -1308,32 +1317,35 @@ class Login $this->acl['unit_name'] = null; $this->acl['unit_uid'] = null; $this->acl['unit'] = []; + $this->acl['unit_legacy'] = []; $this->acl['unit_detail'] = []; // PER ACCOUNT (UNIT/edit access)-> - foreach ($_SESSION['UNIT'] as $ea_id => $unit) { + foreach ($_SESSION['UNIT'] as $ea_cuid => $unit) { // if admin flag is set, all units are set to 100 if (!empty($this->acl['admin'])) { - $this->acl['unit'][$ea_id] = $this->acl['base']; + $this->acl['unit'][$ea_cuid] = $this->acl['base']; } else { if ($unit['acl_level'] != -1) { - $this->acl['unit'][$ea_id] = $unit['acl_level']; + $this->acl['unit'][$ea_cuid] = $unit['acl_level']; } else { - $this->acl['unit'][$ea_id] = $this->acl['base']; + $this->acl['unit'][$ea_cuid] = $this->acl['base']; } } + // legacy + $this->acl['unit_legacy'][$unit['id']] = $this->acl['unit'][$ea_cuid]; // detail name/level set - $this->acl['unit_detail'][$ea_id] = [ + $this->acl['unit_detail'][$ea_cuid] = [ 'name' => $unit['name'], 'uid' => $unit['uid'], - 'level' => $this->default_acl_list[$this->acl['unit'][$ea_id]]['name'] ?? -1, + 'level' => $this->default_acl_list[$this->acl['unit'][$ea_cuid]]['name'] ?? -1, 'default' => $unit['default'], 'data' => $unit['data'], 'additional_acl' => $unit['additional_acl'] ]; // set default if (!empty($unit['default'])) { - $this->acl['unit_id'] = $unit['id']; + $this->acl['unit_cuid'] = $ea_cuid; $this->acl['unit_name'] = $unit['name']; $this->acl['unit_uid'] = $unit['uid']; } @@ -2741,18 +2753,60 @@ HTML; * @param int|null $edit_access_id access id pk to check * @return bool true/false: if the edit access is not * in the valid list: false + * @deprecated Please switch to using edit access cuid check with ->loginCheckEditAccessCuid() */ public function loginCheckEditAccess(?int $edit_access_id): bool { if ($edit_access_id === null) { return false; } - if (array_key_exists($edit_access_id, $this->acl['unit'])) { + if (array_key_exists($edit_access_id, $this->acl['unit_legacy'])) { return true; } return false; } + /** + * check if this edit access cuid is valid + * + * @param string|null $cuid + * @return bool + */ + public function loginCheckEditAccessCuid(?string $cuid): bool + { + if ($cuid === null) { + return false; + } + if (array_key_exists($cuid, $this->acl['unit'])) { + return true; + } + return false; + } + + /** + * checks that the given edit access id is valid for this user + * return null if nothing set, or the edit access id + * + * @param string|null $cuid edit access cuid to check + * @return string|null same edit access cuid if ok + * or the default edit access id + * if given one is not valid + */ + public function loginCheckEditAccessValidCuid(?string $cuid): ?string + { + if ( + $cuid !== null && + is_array($this->session->get('UNIT')) && + !array_key_exists($cuid, $this->session->get('UNIT')) + ) { + $cuid = null; + if (!empty($this->session->get('UNIT_DEFAULT_EACUID'))) { + $cuid = $this->session->get('UNIT_DEFAULT_EACUID'); + } + } + return $cuid; + } + /** * checks that the given edit access id is valid for this user * return null if nothing set, or the edit access id @@ -2761,53 +2815,39 @@ HTML; * @return int|null same edit access id if ok * or the default edit access id * if given one is not valid + * @deprecated Please switch to using edit access cuid check with ->loginCheckEditAccessValidCuid() */ public function loginCheckEditAccessId(?int $edit_access_id): ?int { if ( $edit_access_id !== null && - is_array($this->session->get('UNIT')) && - !array_key_exists($edit_access_id, $this->session->get('UNIT')) + is_array($this->session->get('UNIT_LEGACY')) && + !array_key_exists($edit_access_id, $this->session->get('UNIT_LEGACY')) ) { $edit_access_id = null; - if (is_numeric($this->session->get('UNIT_DEFAULT'))) { - $edit_access_id = (int)$this->session->get('UNIT_DEFAULT'); + if (!empty($this->session->get('UNIT_DEFAULT_EAID'))) { + $edit_access_id = (int)$this->session->get('UNIT_DEFAULT_EAID'); } } return $edit_access_id; } /** - * return a set entry from the UNIT session for an edit access_id + * return a set entry from the UNIT session for an edit access cuid * if not found return false * - * @param int $edit_access_id edit access id - * @param string|int $data_key key value to search for - * @return bool|string false for not found or string for found data + * @param string $cuid edit access cuid + * @param string|int $data_key key value to search for + * @return false|string false for not found or string for found data */ public function loginGetEditAccessData( - int $edit_access_id, + string $cuid, string|int $data_key - ): bool|string { - if (!isset($_SESSION['UNIT'][$edit_access_id]['data'][$data_key])) { + ): false|string { + if (!isset($_SESSION['UNIT'][$cuid]['data'][$data_key])) { return false; } - return $_SESSION['UNIT'][$edit_access_id]['data'][$data_key]; - } - - /** - * old name for loginGetEditAccessData - * - * @deprecated Use $login->loginGetEditAccessData() - * @param int $edit_access_id - * @param string|int $data_key - * @return bool|string - */ - public function loginSetEditAccessData( - int $edit_access_id, - string|int $data_key - ): bool|string { - return $this->loginGetEditAccessData($edit_access_id, $data_key); + return $_SESSION['UNIT'][$cuid]['data'][$data_key]; } /** @@ -2815,9 +2855,10 @@ HTML; * false on not found * * @param string $uid Edit Access UID to look for - * @return int|bool Either primary key in int or false in bool for not found + * @return int|false Either primary key in int or false in bool for not found + * @deprecated use loginGetEditAccessCuidFromUid */ - public function loginGetEditAccessIdFromUid(string $uid): int|bool + public function loginGetEditAccessIdFromUid(string $uid): int|false { if (!isset($_SESSION['UNIT_UID'][$uid])) { return false; @@ -2825,6 +2866,20 @@ HTML; return (int)$_SESSION['UNIT_UID'][$uid]; } + /** + * Get the edit access UID from the edit access CUID + * + * @param string $uid + * @return int|false + */ + public function loginGetEditAccessCuidFromUid(string $uid): int|false + { + if (!isset($_SESSION['UNIT_CUID'][$uid])) { + return false; + } + return (int)$_SESSION['UNIT_CUID'][$uid]; + } + /** * Check if admin flag is set * From fe50a988a0f4f9f659b4efd50e381c491e6c3fec Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 6 Dec 2024 20:11:28 +0900 Subject: [PATCH 006/105] Switch session ACL Login user load check to cuuid Update tests too for using edit user cuuid instead of the primary key --- 4dev/tests/ACL/CoreLibsACLLoginTest.php | 29 ++++-- www/admin/class_test.login.php | 4 +- www/lib/CoreLibs/ACL/Login.php | 114 +++++++++++++----------- 3 files changed, 86 insertions(+), 61 deletions(-) diff --git a/4dev/tests/ACL/CoreLibsACLLoginTest.php b/4dev/tests/ACL/CoreLibsACLLoginTest.php index a60f6f62..17f78545 100644 --- a/4dev/tests/ACL/CoreLibsACLLoginTest.php +++ b/4dev/tests/ACL/CoreLibsACLLoginTest.php @@ -265,7 +265,7 @@ final class CoreLibsACLLoginTest extends TestCase 'ajax_post_action' => 'login', ], ], - 'load, session euid set only, php error' => [ + 'load, session ecuuid set only, php error' => [ [ 'page_name' => 'edit_users.php', ], @@ -273,8 +273,8 @@ final class CoreLibsACLLoginTest extends TestCase [], [ 'EUID' => 1, - 'ECUID' => 'abc', - 'ECUUID' => '1233456-1234-1234-1234-123456789012', + 'EUCUID' => 'abc', + 'EUCUUID' => '1233456-1234-1234-1234-123456789012', ], 2, [], @@ -293,8 +293,8 @@ final class CoreLibsACLLoginTest extends TestCase [], [ 'EUID' => 1, - 'ECUID' => 'abc', - 'ECUUID' => '1233456-1234-1234-1234-123456789012', + 'EUCUID' => 'abc', + 'EUCUUID' => 'SET_EUCUUID_IN_TEST', 'USER_NAME' => '', 'GROUP_NAME' => '', 'ADMIN' => 1, @@ -1176,6 +1176,11 @@ final class CoreLibsACLLoginTest extends TestCase $_POST[$post_var] = $post_value; } + // set ingoing session cuuid if requested + if (isset($session['EUCUUID']) && $session['EUCUUID'] == 'SET_EUCUUID_IN_TEST') { + $session['EUCUUID'] = self::$edit_user_cuuid; + } + // set _SESSION data foreach ($session as $session_var => $session_value) { $_SESSION[$session_var] = $session_value; @@ -1435,8 +1440,18 @@ final class CoreLibsACLLoginTest extends TestCase // run test try { // preset, we cannot set that in the provider - $expected['check_access_cuid'] = self::$edit_access_cuid; - $mock_settings['edit_access_cuid'] = self::$edit_access_cuid; + if ( + isset($expected['check_access_cuid']) && + $expected['check_access_cuid'] == 'SET_EDIT_ACCESS_CUID_IN_TEST' + ) { + $expected['check_access_cuid'] = self::$edit_access_cuid; + } + if ( + isset($mock_settings['edit_access_cuid']) && + $mock_settings['edit_access_cuid'] == 'SET_EDIT_ACCESS_CUID_IN_TEST' + ) { + $mock_settings['edit_access_cuid'] = self::$edit_access_cuid; + } // if ajax call // check if parameter, or globals (old type) // else normal call diff --git a/www/admin/class_test.login.php b/www/admin/class_test.login.php index bdf414b5..e5bbc6d4 100644 --- a/www/admin/class_test.login.php +++ b/www/admin/class_test.login.php @@ -99,8 +99,8 @@ echo "ACL: " . Support::printAr($login->loginGetAcl()) . "
"; echo "ACL (MIN): " . Support::printAr($login->loginGetAcl()['min'] ?? []) . "
"; echo "LOCALE: " . Support::printAr($login->loginGetLocale()) . "
"; -echo "ECUID: " . $login->loginGetEcuid() . "
"; -echo "ECUUID: " . $login->loginGetEcuuid() . "
"; +echo "ECUID: " . $login->loginGetEuCuid() . "
"; +echo "ECUUID: " . $login->loginGetEuCuuid() . "
"; echo "
"; // set + check edit access id diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index a39dbaa8..1421f04c 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -75,18 +75,18 @@ use CoreLibs\Convert\Json; class Login { /** @var ?int the user id var*/ - private ?int $euid; + private ?int $edit_user_id; /** @var ?string the user cuid (note will be super seeded with uuid v4 later) */ - private ?string $ecuid; + private ?string $edit_user_cuid; /** @var ?string UUIDv4, will superseed the ecuid and replace euid as login id */ - private ?string $ecuuid; + private ?string $edit_user_cuuid; /** @var string _GET/_POST loginUserId parameter for non password login */ private string $login_user_id = ''; /** @var string source, either _GET or _POST or empty */ private string $login_user_id_source = ''; /** @var bool set to true if illegal characters where found in the login user id string */ private bool $login_user_id_unclear = false; - // is set to one if login okay, or EUID is set and user is okay to access this page + // is set to one if login okay, or EUCUUID is set and user is okay to access this page /** @var bool */ private bool $permission_okay = false; /** @var string pressed login */ @@ -262,7 +262,7 @@ class Login ], // actually obsolete '100' => [ - 'msg' => '[EUID] came in as GET/POST!', + 'msg' => '[EUCUUID] came in as GET/POST!', 'flag' => 'e', ], // query errors @@ -769,7 +769,7 @@ class Login private function loginLoginUser(): void { // if pressed login at least and is not yet loggined in - if ($this->euid || (!$this->login && !$this->login_user_id)) { + if ($this->edit_user_cuuid || (!$this->login && !$this->login_user_id)) { return; } // if not username AND password where given @@ -939,13 +939,13 @@ class Login } // normal user processing // set class var and session var - $this->euid = (int)$res['edit_user_id']; - $this->ecuid = (string)$res['cuid']; - $this->ecuuid = (string)$res['cuuid']; + $this->edit_user_id = (int)$res['edit_user_id']; + $this->edit_user_cuid = (string)$res['cuid']; + $this->edit_user_cuuid = (string)$res['cuuid']; $this->session->setMany([ - 'EUID' => $this->euid, - 'ECUID' => $this->ecuid, - 'ECUUID' => $this->ecuuid, + 'EUID' => $this->edit_user_id, // DEPRECATED + 'EUCUID' => $this->edit_user_cuid, + 'EUCUUID' => $this->edit_user_cuuid, ]); // check if user is okay $this->loginCheckPermissions(); @@ -962,7 +962,7 @@ class Login SET login_user_id_last_revalidate = NOW() WHERE edit_user_id = $1 SQL; - $this->db->dbExecParams($q, [$this->euid]); + $this->db->dbExecParams($q, [$this->edit_user_id]); } $locale = $res['locale'] ?? 'en'; $encoding = $res['encoding'] ?? 'UTF-8'; @@ -1014,7 +1014,7 @@ class Login login_error_date_first = NULL WHERE edit_user_id = $1 SQL; - $this->db->dbExecParams($q, [$this->euid]); + $this->db->dbExecParams($q, [$this->edit_user_id]); } $edit_page_ids = []; $pages = []; @@ -1022,7 +1022,7 @@ class Login // set pages access $q = << $res['edit_page_id'], 'cuid' => $res['cuid'], + 'cuuid' => $res['cuuid'], // for reference of content data on a differen page 'content_alias_uid' => $res['content_alias_uid'], 'hostname' => $res['hostname'], @@ -1096,7 +1097,7 @@ class Login // get the page content and add them to the page $q = << $res['name'], 'uid' => $res['uid'], + 'cuid' => $res['cuid'], + 'cuuid' => $res['cuuid'], 'online' => $res['online'], 'order' => $res['order_number'], // access name and level @@ -1123,7 +1126,7 @@ class Login // load the edit_access user rights $q = <<db->dbReturnParams($q, [$this->euid]))) { + while (is_array($res = $this->db->dbReturnParams($q, [$this->edit_user_id]))) { // read edit access data fields and drop them into the unit access array $q_sub = << (int)$res['edit_access_id'], // DEPRECATED + 'cuuid' => $res['cuuid'], 'acl_level' => $res['level'], 'acl_type' => $res['type'], 'name' => $res['name'], @@ -1262,8 +1266,8 @@ class Login $this->acl['user_name'] = $_SESSION['USER_NAME']; $this->acl['group_name'] = $_SESSION['GROUP_NAME']; // edit user cuid - $this->acl['ecuid'] = $_SESSION['ECUID']; - $this->acl['ecuuid'] = $_SESSION['ECUUID']; + $this->acl['eucuid'] = $_SESSION['EUCUID']; + $this->acl['eucuuid'] = $_SESSION['EUCUUID']; // set additional acl $this->acl['additional_acl'] = [ 'user' => $_SESSION['USER_ADDITIONAL_ACL'], @@ -1701,15 +1705,15 @@ class Login $event = 'No Permission'; } // prepare for log - if ($this->euid) { + if ($this->edit_user_cuuid) { // get user from user table $q = <<db->dbReturnRowParams($q, [$this->euid]))) { + if (is_array($res = $this->db->dbReturnRowParams($q, [$this->edit_user_cuuid]))) { $username = $res['username']; } } // if euid is set, get username (or try) @@ -2023,10 +2027,10 @@ HTML; empty($username) ? $this->session->get('USER_NAME') ?? '' : $username, is_numeric($this->session->get('EUID')) ? $this->session->get('EUID') : null, - is_string($this->session->get('ECUID')) ? - $this->session->get('ECUID') : null, - !empty($this->session->get('ECUUID')) && Uids::validateUuuidv4($this->session->get('ECUUID')) ? - $this->session->get('ECUUID') : null, + is_string($this->session->get('EUCUID')) ? + $this->session->get('EUCUID') : null, + !empty($this->session->get('EUCUUID')) && Uids::validateUuuidv4($this->session->get('EUCUUID')) ? + $this->session->get('EUCUUID') : null, (string)$event, (string)$error, $data_write, @@ -2153,10 +2157,8 @@ HTML; } } // if there is none, there is none, saves me POST/GET check - $this->euid = (int)($this->session->get('EUID') ?? 0); - // TODO: allow load from cuid - // $this->ecuid = (string)($this->session->get('ECUID') ?? ''); - // $this->ecuuid = (string)($this->session->get('ECUUID') ?? ''); + // $this->euid = (int)($this->session->get('EUID') ?? 0); + $this->edit_user_cuuid = (string)($this->session->get('EUCUUID') ?? ''); // get login vars, are so, can't be changed // prepare // pass on vars to Object vars @@ -2337,7 +2339,7 @@ HTML; return $this->session->get('PAGES'); } - // MARK: logged in uid(pk)/cuid/eccuid + // MARK: logged in uid(pk)/cuid/ecuuid /** * Get the current set EUID (edit user id) @@ -2346,27 +2348,33 @@ HTML; */ public function loginGetEuid(): string { - return (string)$this->euid; + return (string)$this->edit_user_id; } /** - * Get the current set ECUID (edit user cuid) + * Get the current set EUCUID (edit user cuid) * - * @return string ECUID as string + * @return string EUCUID as string */ - public function loginGetEcuid(): string + public function loginGetEuCuid(): string { - return (string)$this->ecuid; + return (string)$this->edit_user_cuid; } /** - * Get the current set ECUUID (edit user cuuid) + * Get the current set EUCUUID (edit user cuuid) * - * @return string ECUUID as string + * @return string EUCUUID as string + * @deprecated Wrong name, use ->loginGetEuCuuid */ public function loginGetEcuuid(): string { - return (string)$this->ecuuid; + return (string)$this->edit_user_cuuid; + } + + public function loginGetEuCuuid(): string + { + return (string)$this->edit_user_cuuid; } // MARK: get error messages @@ -2515,9 +2523,9 @@ HTML; // unset session vars set/used in this login $this->session->sessionDestroy(); // unset euid - $this->euid = null; - $this->ecuid = null; - $this->ecuuid = null; + $this->edit_user_id = null; + $this->edit_user_cuid = null; + $this->edit_user_cuuid = null; // then prints the login screen again $this->permission_okay = false; } @@ -2534,7 +2542,7 @@ HTML; // start with not allowed $this->permission_okay = false; // bail for no euid (no login) - if (empty($this->euid)) { + if (empty($this->edit_user_cuuid)) { return $this->permission_okay; } // euid must match ecuid and ecuuid @@ -2544,7 +2552,7 @@ HTML; } $q = <<db->dbReturnRowParams($q, [$this->euid, $this->page_name]); + $res = $this->db->dbReturnRowParams($q, [$this->edit_user_cuuid, $this->page_name]); if (!is_array($res)) { $this->login_error = 109; return $this->permission_okay; @@ -2622,12 +2630,14 @@ HTML; } else { $this->login_error = 103; } - // set ECUID - $this->ecuid = (string)$res['cuid']; - $this->ecuuid = (string)$res['cuuid']; + // set all the internal vars + $this->edit_user_id = (int)$res['edit_user_id']; + $this->edit_user_cuid = (string)$res['cuid']; + $this->edit_user_cuuid = (string)$res['cuuid']; $this->session->setMany([ - 'ECUID' => $this->ecuid, - 'ECUUID' => $this->ecuuid, + 'EUID' => $this->edit_user_id, // DEPRECATED + 'EUCUID' => $this->edit_user_cuid, + 'EUCUUID' => $this->edit_user_cuuid, ]); // if called from public, so we can check if the permissions are ok return $this->permission_okay; From a56cbd8e97e05e61ece68bad8540056600b4e172 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 9 Dec 2024 16:20:21 +0900 Subject: [PATCH 007/105] ACL Login update layout to work with PC/Smartphone Updated login template and password change template block --- www/lib/CoreLibs/ACL/Login.php | 342 ++++++++++++++++++++++++--------- 1 file changed, 249 insertions(+), 93 deletions(-) diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 1421f04c..be730a9d 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -180,6 +180,7 @@ class Login private array $login_template = [ 'strings' => [], 'password_change' => '', + 'password_forgot' => '', 'template' => '' ]; @@ -232,13 +233,12 @@ class Login * @param \CoreLibs\Logging\Logging $log Logging class * @param \CoreLibs\Create\Session $session Session interface class * @param array $options Login ACL settings - * $auto_login [default true] DEPRECATED, moved into options */ public function __construct( \CoreLibs\DB\IO $db, \CoreLibs\Logging\Logging $log, \CoreLibs\Create\Session $session, - array $options = [] + array $options = [], ) { // attach db class $this->db = $db; @@ -1601,6 +1601,10 @@ class Login $this->login_template['strings']['LANGUAGE'] = $locales['lang'] ?? 'en'; // if password change is okay + // TODO: this should be a "forgot" password + // -> input email + // -> send to user with link + token + // -> validate token -> show change password if ($this->password_change) { $html_string_password_change = $this->login_template['password_change']; @@ -1617,14 +1621,14 @@ class Login // print error messagae if ($this->login_error) { $html_string_password_change = str_replace( - '{ERROR_MSG}', - $this->loginGetErrorMsg($this->login_error) . '
', + ['{ERROR_VISIBLE}', '{ERROR_MSG}'], + ['login-visible', $this->loginGetErrorMsg($this->login_error)], $html_string_password_change ); } else { $html_string_password_change = str_replace( - '{ERROR_MSG}', - '
', + ['{ERROR_VISIBLE}', '{ERROR_MSG}'], + ['login-hidden', ''], $html_string_password_change ); } @@ -1632,8 +1636,11 @@ class Login if ($this->change_password && !$this->password_change_ok) { $html_string_password_change = str_replace( '{PASSWORD_CHANGE_SHOW}', - '', + << + ShowHideDiv('login_pw_change_div'); + + HTML, $html_string_password_change ); } else { @@ -1660,18 +1667,22 @@ class Login // print error messagae if ($this->login_error) { $html_string = str_replace( - '{ERROR_MSG}', - $this->loginGetErrorMsg($this->login_error) . '
', + ['{ERROR_VISIBLE}', '{ERROR_MSG}'], + ['login-visible', $this->loginGetErrorMsg($this->login_error)], $html_string ); } elseif ($this->password_change_ok && $this->password_change) { $html_string = str_replace( - '{ERROR_MSG}', - $this->loginGetErrorMsg(300) . '
', + ['{ERROR_VISIBLE}', '{ERROR_MSG}'], + ['login-visible', $this->loginGetErrorMsg(300)], $html_string ); } else { - $html_string = str_replace('{ERROR_MSG}', '
', $html_string); + $html_string = str_replace( + ['{ERROR_VISIBLE}', '{ERROR_MSG}'], + ['login-hidden', ''], + $html_string + ); } // create the replace array context @@ -1756,39 +1767,93 @@ class Login 'NEW_PASSWORD' => $this->l->__('New Password'), 'NEW_PASSWORD_CONFIRM' => $this->l->__('New Password confirm'), 'CLOSE' => $this->l->__('Close'), - 'JS_SHOW_HIDE' => "function ShowHideDiv(id) { " - . "element = document.getElementById(id); " - . "if (element.className == 'visible' || !element.className) element.className = 'hidden'; " - . "else element.className = 'visible'; }", - 'PASSWORD_CHANGE_BUTTON' => '' + 'JS_SHOW_HIDE' => << str_replace( + '{PASSWORD_CHANGE_BUTTON_VALUE}', + $strings['PASSWORD_CHANGE_BUTTON_VALUE'], + // phpcs:disable Generic.Files.LineLength + << + HTML + // phpcs:enable Generic.Files.LineLength + ), ]); - // TODO: submit or JS to set target page as ajax call - // NOTE: for the HTML block I ignore line lengths - // phpcs:disable + // phpcs:disable Generic.Files.LineLength $this->login_template['password_change'] = << - - - - - - - - - - - - -

{TITLE_PASSWORD_CHANGE}

{ERROR_MSG}
{USERNAME}
{OLD_PASSWORD}
{NEW_PASSWORD}
{NEW_PASSWORD_CONFIRM}
-
+ {PASSWORD_CHANGE_SHOW} HTML; - // phpcs:enable + // phpcs:enable Generic.Files.LineLength } if ($this->password_forgot) { + // TODO: create a password forget request flow } if (!$this->password_change && !$this->password_forgot) { $strings = array_merge($strings, [ @@ -1807,71 +1872,162 @@ HTML; } // now check templates - // TODO: submit or JS to set target page as ajax call if (!$this->login_template['template']) { + // phpcs:disable Generic.Files.LineLength $this->login_template['template'] = << -{HTML_TITLE} - - + {LOGOUT_TARGET} -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - -
-

{TITLE}

-
 
- {ERROR_MSG} -
{USERNAME}
{PASSWORD}
- - {PASSWORD_CHANGE_BUTTON} -
-

-
 
-{PASSWORD_CHANGE_DIV} +
HTML; + // phpcs:enable Generic.Files.LineLength } } @@ -2167,11 +2323,11 @@ HTML; $this->password = $_POST['login_password'] ?? ''; $this->logout = $_POST['login_logout'] ?? ''; // password change vars - $this->change_password = $_POST['change_password'] ?? ''; - $this->pw_username = $_POST['pw_username'] ?? ''; - $this->pw_old_password = $_POST['pw_old_password'] ?? ''; - $this->pw_new_password = $_POST['pw_new_password'] ?? ''; - $this->pw_new_password_confirm = $_POST['pw_new_password_confirm'] ?? ''; + $this->change_password = $_POST['login_change_password'] ?? ''; + $this->pw_username = $_POST['login_pw_username'] ?? ''; + $this->pw_old_password = $_POST['login_pw_old_password'] ?? ''; + $this->pw_new_password = $_POST['login_pw_new_password'] ?? ''; + $this->pw_new_password_confirm = $_POST['login_pw_new_password_confirm'] ?? ''; // disallow user list for password change $this->pw_change_deny_users = ['admin']; // max login counts before error reporting From 65715ea9c37262e364f06f26ee67b47add5df0f6 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 9 Dec 2024 19:01:35 +0900 Subject: [PATCH 008/105] Add Array function to return only array entries based on matching key A simple key based array filter --- .../CoreLibsCombinedArrayHandlerTest.php | 85 +++++++++++++++++++ www/admin/class_test.array.php | 13 +++ www/lib/CoreLibs/Combined/ArrayHandler.php | 24 ++++++ 3 files changed, 122 insertions(+) diff --git a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php index 8dc5729a..d320b845 100644 --- a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php +++ b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php @@ -1201,6 +1201,91 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase 'Find next key in array' ); } + + public function providerReturnMatchingKeyOnley(): array + { + return [ + 'limited entries' => [ + [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar' + ], + [ + 'a', 'b' + ], + [ + 'a' => 'foo', + 'b' => 'bar', + ], + ], + 'limited entries, with one wrong key' => [ + [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar' + ], + [ + 'a', 'b', 'f' + ], + [ + 'a' => 'foo', + 'b' => 'bar', + ], + ], + 'wrong keys only' => [ + [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar' + ], + [ + 'f', 'f' + ], + [ + ], + ], + 'empty keys' => [ + [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar' + ], + [], + [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar' + ], + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::arrayReturnMatchingKeyOnly + * @dataProvider providerReturnMatchingKeyOnley + * @testdox arrayReturnMatchingKeyOnly get only selected key entries from array [$_dataName] + * + * @param array $input + * @param array $key_list + * @param array $expected + * @return void + */ + public function testArrayReturnMatchingKeyOnly( + array $input, + array $key_list, + array $expected + ): void { + $this->assertEquals( + $expected, + \CoreLibs\Combined\ArrayHandler::arrayReturnMatchingKeyOnly( + $input, + $key_list + ) + ); + } } // __END__ diff --git a/www/admin/class_test.array.php b/www/admin/class_test.array.php index 85dbfc0a..bb64ea46 100644 --- a/www/admin/class_test.array.php +++ b/www/admin/class_test.array.php @@ -250,6 +250,19 @@ foreach (array_keys($array) as $search) { } print "Key not exists: " . DgS::printAr(ArrayHandler::arrayGetNextKey($array, 'z')) . "
"; +print "
"; +$keys = ['b', 'c', 'f']; +print "Return only: " . DgS::printAr($keys) . ": " + . DgS::printAr(ArrayHandler::arrayReturnMatchingKeyOnly($array, $keys)) . "
"; + +$out = array_filter($array, fn($key) => in_array($key, $keys), ARRAY_FILTER_USE_KEY); +print "array filter: " . DgS::printAr($keys) . ": " . DgS::printAr($out) . "
"; +$out = array_intersect_key( + $array, + array_flip($keys) +); +print "array intersect key: " . DgS::printAr($keys) . ": " . DgS::printAr($out) . "
"; + print ""; // __END__ diff --git a/www/lib/CoreLibs/Combined/ArrayHandler.php b/www/lib/CoreLibs/Combined/ArrayHandler.php index b1002949..38727c1f 100644 --- a/www/lib/CoreLibs/Combined/ArrayHandler.php +++ b/www/lib/CoreLibs/Combined/ArrayHandler.php @@ -525,6 +525,30 @@ class ArrayHandler { return array_diff($array, $remove); } + + /** + * From the array with key -> anything values return only the matching entries from key list + * key list is a list[string] + * if key list is empty, return array as is + * + * @param array $array + * @param array $key_list + * @return array + */ + public static function arrayReturnMatchingKeyOnly( + array $array, + array $key_list + ): array { + // on empty return as is + if (empty($key_list)) { + return $array; + } + return array_filter( + $array, + fn($key) => in_array($key, $key_list), + ARRAY_FILTER_USE_KEY + ); + } } // __END__ From eeca13819215cee19fdd8994a50a0b151974ba26 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 9 Dec 2024 19:20:57 +0900 Subject: [PATCH 009/105] Remove the debug/db_debug flag from the edit user edit interface These settings are deprecated and do nothing. keep the DB default values for now, but update table create to remove them. They are set to 0 --- 4dev/database/table/edit_user.sql | 5 ---- www/lib/CoreLibs/Admin/EditBase.php | 2 -- .../Output/Form/TableArrays/EditUsers.php | 24 ------------------- 3 files changed, 31 deletions(-) diff --git a/4dev/database/table/edit_user.sql b/4dev/database/table/edit_user.sql index abae29c6..2b3c58d7 100644 --- a/4dev/database/table/edit_user.sql +++ b/4dev/database/table/edit_user.sql @@ -35,9 +35,6 @@ CREATE TABLE edit_user ( strict SMALLINT DEFAULT 0, locked SMALLINT DEFAULT 0, protected SMALLINT NOT NULL DEFAULT 0, - -- legacy, debug flags - debug SMALLINT NOT NULL DEFAULT 0, - db_debug SMALLINT NOT NULL DEFAULT 0, -- is admin user admin SMALLINT NOT NULL DEFAULT 0, -- last login log @@ -76,8 +73,6 @@ COMMENT ON COLUMN edit_user.deleted IS 'Login is deleted (master switch), overri COMMENT ON COLUMN edit_user.strict IS 'If too many failed logins user will be locked, default off'; COMMENT ON COLUMN edit_user.locked IS 'Locked from too many wrong password logins'; COMMENT ON COLUMN edit_user.protected IS 'User can only be chnaged by admin user'; -COMMENT ON COLUMN edit_user.debug IS 'Turn debug flag on (legacy)'; -COMMENT ON COLUMN edit_user.db_debug IS 'Turn DB debug flag on (legacy)'; COMMENT ON COLUMN edit_user.admin IS 'If set, this user is SUPER admin'; COMMENT ON COLUMN edit_user.last_login IS 'Last succesfull login tiemstamp'; COMMENT ON COLUMN edit_user.login_error_count IS 'Number of failed logins, reset on successful login'; diff --git a/www/lib/CoreLibs/Admin/EditBase.php b/www/lib/CoreLibs/Admin/EditBase.php index f740b53a..27d4a162 100644 --- a/www/lib/CoreLibs/Admin/EditBase.php +++ b/www/lib/CoreLibs/Admin/EditBase.php @@ -415,8 +415,6 @@ class EditBase $elements[] = $this->form->formCreateElement('lock_until'); $elements[] = $this->form->formCreateElement('lock_after'); $elements[] = $this->form->formCreateElement('admin'); - $elements[] = $this->form->formCreateElement('debug'); - $elements[] = $this->form->formCreateElement('db_debug'); $elements[] = $this->form->formCreateElement('edit_language_id'); $elements[] = $this->form->formCreateElement('edit_scheme_id'); $elements[] = $this->form->formCreateElementListTable('edit_access_user'); diff --git a/www/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php b/www/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php index 6d87753a..c51dbd77 100644 --- a/www/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php +++ b/www/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php @@ -135,30 +135,6 @@ class EditUsers implements Interface\TableArraysInterface 'min_edit_acl' => '100', 'min_show_acl' => '100', ], - 'debug' => [ - 'value' => $_POST['debug'] ?? '', - 'output_name' => 'Debug', - 'type' => 'binary', - 'int' => 1, - 'element_list' => [ - '1' => 'Yes', - '0' => 'No' - ], - 'min_edit_acl' => '100', - 'min_show_acl' => '100', - ], - 'db_debug' => [ - 'value' => $_POST['db_debug'] ?? '', - 'output_name' => 'DB Debug', - 'type' => 'binary', - 'int' => 1, - 'element_list' => [ - '1' => 'Yes', - '0' => 'No' - ], - 'min_edit_acl' => '100', - 'min_show_acl' => '100', - ], 'email' => [ 'value' => $_POST['email'] ?? '', 'output_name' => 'E-Mail', From 10c320f60c055fb1160147ce0da331f39c92c02c Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 9 Dec 2024 19:33:53 +0900 Subject: [PATCH 010/105] Rename all ACL Login session vars to LOGIN_, remove debug enties All ACL\Login loaded _SESSION vars are now prefixd with LOGIN_ only the language one stay as "DEFAULT_" Removed DEBUG_ALL/DB_DEBUG as they are now fully removed from everywhere - removed the edit user entries - removed from the edit user table The LANG direct loaded language entries is removed too. We only use locale and encoding. No more LOCALE_PATH and DEFAULT_DOMAIN _SESSION are set during the option set --- 4dev/tests/ACL/CoreLibsACLLoginTest.php | 55 +-- .../CoreLibsLanguageGetLocaleTest.php | 335 ------------------ www/admin/class_test.php | 4 +- www/lib/CoreLibs/ACL/Login.php | 191 +++++----- 4 files changed, 128 insertions(+), 457 deletions(-) diff --git a/4dev/tests/ACL/CoreLibsACLLoginTest.php b/4dev/tests/ACL/CoreLibsACLLoginTest.php index 17f78545..de57645f 100644 --- a/4dev/tests/ACL/CoreLibsACLLoginTest.php +++ b/4dev/tests/ACL/CoreLibsACLLoginTest.php @@ -265,21 +265,21 @@ final class CoreLibsACLLoginTest extends TestCase 'ajax_post_action' => 'login', ], ], - 'load, session ecuuid set only, php error' => [ + 'load, session eucuuid set only, php error' => [ [ 'page_name' => 'edit_users.php', ], [], [], [ - 'EUID' => 1, - 'EUCUID' => 'abc', - 'EUCUUID' => '1233456-1234-1234-1234-123456789012', + 'LOGIN_EUID' => 1, + 'LOGIN_EUCUID' => 'abc', + 'LOGIN_EUCUUID' => '1233456-1234-1234-1234-123456789012', ], 2, [], ], - 'load, session euid set, all set' => [ + 'load, session eucuuid set, all set' => [ [ 'page_name' => 'edit_users.php', 'edit_access_id' => 1, @@ -292,21 +292,21 @@ final class CoreLibsACLLoginTest extends TestCase [], [], [ - 'EUID' => 1, - 'EUCUID' => 'abc', - 'EUCUUID' => 'SET_EUCUUID_IN_TEST', - 'USER_NAME' => '', - 'GROUP_NAME' => '', - 'ADMIN' => 1, - 'GROUP_ACL_LEVEL' => -1, - 'PAGES_ACL_LEVEL' => [], - 'USER_ACL_LEVEL' => -1, - 'USER_ADDITIONAL_ACL' => [], - 'GROUP_ADDITIONAL_ACL' => [], - 'UNIT_UID' => [ + 'LOGIN_EUID' => 1, + 'LOGIN_EUCUID' => 'abc', + 'LOGIN_EUCUUID' => 'SET_EUCUUID_IN_TEST', + 'LOGIN_USER_NAME' => '', + 'LOGIN_GROUP_NAME' => '', + 'LOGIN_ADMIN' => 1, + 'LOGIN_GROUP_ACL_LEVEL' => -1, + 'LOGIN_PAGES_ACL_LEVEL' => [], + 'LOGIN_USER_ACL_LEVEL' => -1, + 'LOGIN_USER_ADDITIONAL_ACL' => [], + 'LOGIN_GROUP_ADDITIONAL_ACL' => [], + 'LOGIN_UNIT_UID' => [ 'AdminAccess' => '123456789012', ], - 'UNIT' => [ + 'LOGIN_UNIT' => [ '123456789012' => [ 'id' => 1, 'acl_level' => 80, @@ -320,8 +320,8 @@ final class CoreLibsACLLoginTest extends TestCase 'additional_acl' => [] ], ], - // 'UNIT_DEFAULT' => '', - // 'DEFAULT_ACL_LIST' => [], + // 'LOGIN_UNIT_DEFAULT' => '', + // 'LOGIN_DEFAULT_ACL_LIST' => [], ], 0, [ @@ -1177,8 +1177,8 @@ final class CoreLibsACLLoginTest extends TestCase } // set ingoing session cuuid if requested - if (isset($session['EUCUUID']) && $session['EUCUUID'] == 'SET_EUCUUID_IN_TEST') { - $session['EUCUUID'] = self::$edit_user_cuuid; + if (isset($session['LOGIN_EUCUUID']) && $session['LOGIN_EUCUUID'] == 'SET_EUCUUID_IN_TEST') { + $session['LOGIN_EUCUUID'] = self::$edit_user_cuuid; } // set _SESSION data @@ -1582,11 +1582,12 @@ final class CoreLibsACLLoginTest extends TestCase // - loginCheckPermissions // - loginGetPermissionOkay } catch (\Exception $e) { - // print "[E]: " . $e->getCode() . ", ERROR: " . $login_mock->loginGetLastErrorCode() . "/" - // . ($expected['login_error'] ?? 0) . "\n"; - // print "AJAX: " . $login_mock->loginGetAjaxFlag() . "\n"; - // print "AJAX GLOBAL: " . ($GLOBALS['AJAX_PAGE'] ?? '{f}') . "\n"; - // print "Login error expext: " . ($expected['login_error'] ?? '{0}') . "\n"; + /* print "[E]: " . $e->getCode() . ", ERROR: " . $login_mock->loginGetLastErrorCode() . "/" + . ($expected['login_error'] ?? 0) . "\n"; + print "AJAX: " . $login_mock->loginGetAjaxFlag() . "\n"; + print "AJAX GLOBAL: " . ($GLOBALS['AJAX_PAGE'] ?? '{f}') . "\n"; + print "Login error expext: " . ($expected['login_error'] ?? '{0}') . "\n"; + print "POST exit: " . ($_POST['login_exit'] ?? '{0}') . "\n"; */ // if this is 100, then we do further error checks if ( $e->getCode() == 100 || diff --git a/4dev/tests/Language/CoreLibsLanguageGetLocaleTest.php b/4dev/tests/Language/CoreLibsLanguageGetLocaleTest.php index fa053413..afd71a4d 100644 --- a/4dev/tests/Language/CoreLibsLanguageGetLocaleTest.php +++ b/4dev/tests/Language/CoreLibsLanguageGetLocaleTest.php @@ -21,341 +21,6 @@ final class CoreLibsLanguageGetLocaleTest extends TestCase . 'includes' . DIRECTORY_SEPARATOR . 'locale' . DIRECTORY_SEPARATOR; - /** - * set all constant variables that must be set before call - * - * @return void - */ - public static function setUpBeforeClass(): void - { - // default web page encoding setting - /* if (!defined('DEFAULT_ENCODING')) { - define('DEFAULT_ENCODING', 'UTF-8'); - } - if (!defined('DEFAULT_LOCALE')) { - // default lang + encoding - define('DEFAULT_LOCALE', 'en_US.UTF-8'); - } - // site - if (!defined('SITE_ENCODING')) { - define('SITE_ENCODING', DEFAULT_ENCODING); - } - if (!defined('SITE_LOCALE')) { - define('SITE_LOCALE', DEFAULT_LOCALE); - } */ - // just set - /* if (!defined('BASE')) { - define('BASE', str_replace('/configs', '', __DIR__) . DIRECTORY_SEPARATOR); - } - if (!defined('INCLUDES')) { - define('INCLUDES', 'includes' . DIRECTORY_SEPARATOR); - } - if (!defined('LANG')) { - define('LANG', 'lang' . DIRECTORY_SEPARATOR); - } - if (!defined('LOCALE')) { - define('LOCALE', 'locale' . DIRECTORY_SEPARATOR); - } - if (!defined('CONTENT_PATH')) { - define('CONTENT_PATH', 'frontend' . DIRECTORY_SEPARATOR); - } */ - // array session - $_SESSION = []; - global $_SESSION; - } - - /** - * all the test data - * - * @return array - */ - /* public function setLocaleProvider(): array - { - return [ - // 0: locale - // 1: domain - // 2: encoding - // 3: path - // 4: SESSION: DEFAULT_LOCALE - // 5: SESSION: DEFAULT_CHARSET - // 6: expected array - // 7: deprecation message - 'no params, all default constants' => [ - // lang, domain, encoding, path - null, null, null, null, - // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET - null, null, - // return array - [ - 'locale' => 'en_US.UTF-8', - 'lang' => 'en_US', - 'domain' => 'frontend', - 'encoding' => 'UTF-8', - 'path' => "/^\/(.*\/)?includes\/locale\/$/", - ], - 'setLocale: Unset $locale or unset SESSION locale is deprecated', - ], - 'no params, session charset and lang' => [ - // lang, domain, encoding, path - null, null, null, null, - // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET - 'ja_JP', 'UTF-8', - // return array - [ - 'locale' => 'ja_JP', - 'lang' => 'ja_JP', - 'domain' => 'frontend', - 'encoding' => 'UTF-8', - 'path' => "/^\/(.*\/)?includes\/locale\/$/", - ], - 'setLocale: Unset $domain is deprecated' - ], - 'no params, session charset and lang short' => [ - // lang, domain, encoding, path - null, null, null, null, - // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET - 'ja', 'UTF-8', - // return array - [ - 'locale' => 'ja', - 'lang' => 'ja', - 'domain' => 'frontend', - 'encoding' => 'UTF-8', - 'path' => "/^\/(.*\/)?includes\/locale\/$/", - ], - 'setLocale: Unset $domain is deprecated', - ], - // param lang (no sessions) - 'locale param only, no sessions' => [ - // lang, domain, encoding, path - 'ja.UTF-8', null, null, null, - // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET - null, null, - // return array - [ - 'locale' => 'ja.UTF-8', - 'lang' => 'ja', - 'domain' => 'frontend', - 'encoding' => 'UTF-8', - 'path' => "/^\/(.*\/)?includes\/locale\/$/", - ], - 'setLocale: Unset $domain is deprecated', - ], - // different locale setting - 'locale complex param only, no sessions' => [ - // lang, domain, encoding, path - 'ja_JP.SJIS', null, null, null, - // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET - null, null, - // return array - [ - 'locale' => 'ja_JP.SJIS', - 'lang' => 'ja_JP', - 'domain' => 'frontend', - 'encoding' => 'SJIS', - 'path' => "/^\/(.*\/)?includes\/locale\/$/", - ], - 'setLocale: Unset $domain is deprecated', - ], - // param lang and domain (no override) - 'locale, domain params, no sessions' => [ - // lang, domain, encoding, path - 'ja.UTF-8', 'admin', null, null, - // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET - null, null, - // return array - [ - 'locale' => 'ja.UTF-8', - 'lang' => 'ja', - 'domain' => 'admin', - 'encoding' => 'UTF-8', - 'path' => "/^\/(.*\/)?includes\/locale\/$/", - ], - 'setLocale: Unset $path is deprecated', - ], - // param lang and domain (no override) - 'locale, domain, encoding params, no sessions' => [ - // lang, domain, encoding, path - 'ja.UTF-8', 'admin', 'UTF-8', null, - // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET - null, null, - // return array - [ - 'locale' => 'ja.UTF-8', - 'lang' => 'ja', - 'domain' => 'admin', - 'encoding' => 'UTF-8', - 'path' => "/^\/(.*\/)?includes\/locale\/$/", - ], - 'setLocale: Unset $path is deprecated' - ], - // lang, domain, path (no override) - 'locale, domain and path, no sessions' => [ - // lang, domain, encoding, path - 'ja.UTF-8', 'admin', '', __DIR__ . '/locale_other/', - // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET - null, null, - // return array - [ - 'locale' => 'ja.UTF-8', - 'lang' => 'ja', - 'domain' => 'admin', - 'encoding' => 'UTF-8', - 'path' => "/^\/(.*\/)?locale_other\/$/", - ], - null - ], - // all params set (no override) - 'all parameter, no sessions' => [ - // lang, domain, encoding, path - 'ja', 'admin', 'UTF-8', __DIR__ . '/locale_other/', - // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET - null, null, - // return array - [ - 'locale' => 'ja', - 'lang' => 'ja', - 'domain' => 'admin', - 'encoding' => 'UTF-8', - 'path' => "/^\/(.*\/)?locale_other\/$/", - ], - null - ], - // param lang and domain (no override) - 'long locale, domain, encoding params, no sessions' => [ - // lang, domain, encoding, path - 'de_CH.UTF-8@euro', 'admin', 'UTF-8', null, - // SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET - null, null, - // return array - [ - 'locale' => 'de_CH.UTF-8@euro', - 'lang' => 'de_CH', - 'domain' => 'admin', - 'encoding' => 'UTF-8', - 'path' => "/^\/(.*\/)?includes\/locale\/$/", - ], - 'setLocale: Unset $path is deprecated', - ], - // TODO invalid params (bad path) (no override) - // TODO param calls, but with override set - ]; - } */ - - /** - * Undocumented function - * - * @covers ::setLocale - * @dataProvider setLocaleProvider - * @testdox lang settings lang $language, domain $domain, encoding $encoding, path $path; session lang: $SESSION_DEFAULT_LOCALE, session char: $SESSION_DEFAULT_CHARSET [$_dataName] - * - * @param string|null $language - * @param string|null $domain - * @param string|null $encoding - * @param string|null $path - * @param string|null $SESSION_DEFAULT_LOCALE - * @param string|null $SESSION_DEFAULT_CHARSET - * @param array $expected - * @param string|null $deprecation_message - * @return void - */ - /* public function testsetLocale( - ?string $language, - ?string $domain, - ?string $encoding, - ?string $path, - ?string $SESSION_DEFAULT_LOCALE, - ?string $SESSION_DEFAULT_CHARSET, - array $expected, - ?string $deprecation_message - ): void { - $return_lang_settings = []; - global $_SESSION; - // set override - if ($SESSION_DEFAULT_LOCALE !== null) { - $_SESSION['DEFAULT_LOCALE'] = $SESSION_DEFAULT_LOCALE; - } - if ($SESSION_DEFAULT_CHARSET !== null) { - $_SESSION['DEFAULT_CHARSET'] = $SESSION_DEFAULT_CHARSET; - } - if ($deprecation_message !== null) { - set_error_handler( - static function (int $errno, string $errstr): never { - throw new \Exception($errstr, $errno); - }, - E_USER_DEPRECATED - ); - // catch this with the message - $this->expectExceptionMessage($deprecation_message); - } - // function call - if ( - $language === null && $domain === null && - $encoding === null && $path === null - ) { - $return_lang_settings = \CoreLibs\Language\GetLocale::setLocale(); - } elseif ( - $language !== null && $domain === null && - $encoding === null && $path === null - ) { - $return_lang_settings = \CoreLibs\Language\GetLocale::setLocale( - $language - ); - } elseif ( - $language !== null && $domain !== null && - $encoding === null && $path === null - ) { - $return_lang_settings = \CoreLibs\Language\GetLocale::setLocale( - $language, - $domain - ); - } elseif ( - $language !== null && $domain !== null && - $encoding !== null && $path === null - ) { - $return_lang_settings = \CoreLibs\Language\GetLocale::setLocale( - $language, - $domain, - $encoding - ); - } else { - $return_lang_settings = \CoreLibs\Language\GetLocale::setLocale( - $language, - $domain, - $encoding, - $path - ); - } - restore_error_handler(); - // print "RETURN: " . print_r($return_lang_settings, true) . "\n"; - - foreach ( - [ - 'locale', 'lang', 'domain', 'encoding', 'path' - ] as $key - ) { - $value = $expected[$key]; - if (strpos($value, "/") === 0) { - // this is regex - $this->assertMatchesRegularExpression( - $value, - $return_lang_settings[$key], - 'assert regex failed for ' . $key - ); - } else { - // assert equal - $this->assertEquals( - $value, - $return_lang_settings[$key], - 'assert equal failed for ' . $key - ); - } - } - // unset all vars - $_SESSION = []; - unset($GLOBALS['OVERRIDE_LANG']); - } */ - /** * all the test data * diff --git a/www/admin/class_test.php b/www/admin/class_test.php index 7bff5bb9..138496ef 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -151,8 +151,8 @@ foreach ($test_files as $file => $name) { print "
"; -print "ECUID: " . $session->get('ECUID') . "
"; -print "ECUUID: " . $session->get('ECUUID') . "
"; +print "ECUID: " . $session->get('LOGIN_EUCUID') . "
"; +print "ECUUID: " . $session->get('LOGIN_EUCUUID') . "
"; print "
"; print "LOCALE: " . Support::dumpVar($locale) . "
"; diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index be730a9d..db6fb705 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -14,13 +14,14 @@ * will be a class one day * * descrption of session_vars -* DEBUG_ALL - set to one, prints out error_msg var at end of php execution -* DB_DEBUG - prints out database debugs (query, etc) -* GROUP_LEVEL - the level he can access (numeric) -* USER_NAME - login name from user -* LANG - lang to show edit interface (not yet used) +* TODO: Update session var info +* [DEPRECATED] DEBUG_ALL - set to one, prints out error_msg var at end of php execution +* [DEPRECATED] DB_DEBUG - prints out database debugs (query, etc) +* [REMOVED] LOGIN_GROUP_LEVEL - the level he can access (numeric) +* LOGIN_USER_NAME - login name from user +* [DEPRECATED] LANG - lang to show edit interface (not yet used) * DEFAULT_CHARSET - in connection with LANG (not yet used) -* PAGES - array of hashes +* LOGIN_PAGES - array of hashes * edit_page_id - ID from the edit_pages table * filename - name of the file * page_name - name in menu @@ -262,7 +263,7 @@ class Login ], // actually obsolete '100' => [ - 'msg' => '[EUCUUID] came in as GET/POST!', + 'msg' => '[EUCUUID] set from GET/POST!', 'flag' => 'e', ], // query errors @@ -393,8 +394,8 @@ class Login } // write that into the session $this->session->setMany([ - 'DEFAULT_ACL_LIST' => $this->default_acl_list, - 'DEFAULT_ACL_LIST_TYPE' => $this->default_acl_list_type, + 'LOGIN_DEFAULT_ACL_LIST' => $this->default_acl_list, + 'LOGIN_DEFAULT_ACL_LIST_TYPE' => $this->default_acl_list_type, ]); $this->loginSetEditLogWriteTypeAvailable(); @@ -587,7 +588,6 @@ class Login // set path $options['locale_path'] = BASE . INCLUDES . LOCALE; } - $this->session->set('LOCALE_PATH', $options['locale_path']); // LANG: LOCALE if (empty($options['site_locale'])) { trigger_error( @@ -622,7 +622,6 @@ class Login $options['set_domain'] = str_replace(DIRECTORY_SEPARATOR, '', CONTENT_PATH); } } - $this->session->set('DEFAULT_DOMAIN', $options['site_domain']); // LANG: ENCODING if (empty($options['site_encoding'])) { trigger_error( @@ -943,9 +942,9 @@ class Login $this->edit_user_cuid = (string)$res['cuid']; $this->edit_user_cuuid = (string)$res['cuuid']; $this->session->setMany([ - 'EUID' => $this->edit_user_id, // DEPRECATED - 'EUCUID' => $this->edit_user_cuid, - 'EUCUUID' => $this->edit_user_cuuid, + 'LOGIN_EUID' => $this->edit_user_id, // DEPRECATED + 'LOGIN_EUCUID' => $this->edit_user_cuid, + 'LOGIN_EUCUUID' => $this->edit_user_cuuid, ]); // check if user is okay $this->loginCheckPermissions(); @@ -968,35 +967,36 @@ class Login $encoding = $res['encoding'] ?? 'UTF-8'; $this->session->setMany([ // now set all session vars and read page permissions - 'DEBUG_ALL' => $this->db->dbBoolean($res['debug']), - 'DB_DEBUG' => $this->db->dbBoolean($res['db_debug']), + // DEBUG flag is deprecated + // 'DEBUG_ALL' => $this->db->dbBoolean($res['debug']), + // 'DB_DEBUG' => $this->db->dbBoolean($res['db_debug']), // general info for user logged in - 'USER_NAME' => $res['username'], - 'ADMIN' => $res['admin'], - 'GROUP_NAME' => $res['edit_group_name'], - 'USER_ACL_LEVEL' => $res['user_level'], - 'USER_ACL_TYPE' => $res['user_type'], - 'USER_ADDITIONAL_ACL' => Json::jsonConvertToArray($res['user_additional_acl']), - 'GROUP_ACL_LEVEL' => $res['group_level'], - 'GROUP_ACL_TYPE' => $res['group_type'], - 'GROUP_ADDITIONAL_ACL' => Json::jsonConvertToArray($res['group_additional_acl']), + 'LOGIN_USER_NAME' => $res['username'], + 'LOGIN_ADMIN' => $res['admin'], + 'LOGIN_GROUP_NAME' => $res['edit_group_name'], + 'LOGIN_USER_ACL_LEVEL' => $res['user_level'], + 'LOGIN_USER_ACL_TYPE' => $res['user_type'], + 'LOGIN_USER_ADDITIONAL_ACL' => Json::jsonConvertToArray($res['user_additional_acl']), + 'LOGIN_GROUP_ACL_LEVEL' => $res['group_level'], + 'LOGIN_GROUP_ACL_TYPE' => $res['group_type'], + 'LOGIN_GROUP_ADDITIONAL_ACL' => Json::jsonConvertToArray($res['group_additional_acl']), // deprecated TEMPLATE setting - 'TEMPLATE' => $res['template'] ? $res['template'] : '', - 'HEADER_COLOR' => !empty($res['second_header_color']) ? + // 'TEMPLATE' => $res['template'] ? $res['template'] : '', + 'LOGIN_HEADER_COLOR' => !empty($res['second_header_color']) ? $res['second_header_color'] : $res['first_header_color'], // LANGUAGE/LOCALE/ENCODING: - 'LANG' => $locale, + // 'LOGIN_LANG' => $locale, 'DEFAULT_CHARSET' => $encoding, 'DEFAULT_LOCALE' => $locale . '.' . strtoupper($encoding), 'DEFAULT_LANG' => $locale . '_' . strtolower(str_replace('-', '', $encoding)) ]); // missing # before, this is for legacy data, will be deprecated if ( - !empty($this->session->get('HEADER_COLOR')) && - preg_match("/^[\dA-Fa-f]{6,8}$/", $this->session->get('HEADER_COLOR')) + !empty($this->session->get('LOGIN_HEADER_COLOR')) && + preg_match("/^[\dA-Fa-f]{6,8}$/", $this->session->get('LOGIN_HEADER_COLOR')) ) { - $this->session->set('HEADER_COLOR', '#' . $this->session->get('HEADER_COLOR')); + $this->session->set('LOGIN_HEADER_COLOR', '#' . $this->session->get('LOGIN_HEADER_COLOR')); } // TODO: make sure that header color is valid: // # + 6 hex @@ -1120,8 +1120,8 @@ class Login } // write back the pages data to the output array $this->session->setMany([ - 'PAGES' => $pages, - 'PAGES_ACL_LEVEL' => $pages_acl, + 'LOGIN_PAGES' => $pages, + 'LOGIN_PAGES_ACL_LEVEL' => $pages_acl, ]); // load the edit_access user rights $q = <<session->set('UNIT_DEFAULT_EAID', (int)$res['edit_access_id']); // DEPRECATED - $this->session->set('UNIT_DEFAULT_EACUID', (int)$res['cuid']); + $this->session->set('LOGIN_UNIT_DEFAULT_EAID', (int)$res['edit_access_id']); // DEPRECATED + $this->session->set('LOGIN_UNIT_DEFAULT_EACUID', (int)$res['cuid']); } $unit_uid_lookup[$res['uid']] = $res['edit_access_id']; // DEPRECATED $unit_cuid_lookup[$res['uid']] = $res['cuid']; @@ -1183,13 +1183,13 @@ class Login $unit_acl[$res['cuid']] = $res['level']; } $this->session->setMany([ - 'UNIT_UID' => $unit_uid_lookup, // DEPRECATED - 'UNIT_CUID' => $unit_cuid_lookup, - 'UNIT' => $unit_access_cuid, - 'UNIT_LEGACY' => $unit_access_eaid, // DEPRECATED - 'UNIT_ACL_LEVEL' => $unit_acl, - 'EAID' => $eaid, // DEPRECATED - 'EACUID' => $eacuid, + 'LOGIN_UNIT_UID' => $unit_uid_lookup, // DEPRECATED + 'LOGIN_UNIT_CUID' => $unit_cuid_lookup, + 'LOGIN_UNIT' => $unit_access_cuid, + 'LOGIN_UNIT_LEGACY' => $unit_access_eaid, // DEPRECATED + 'LOGIN_UNIT_ACL_LEVEL' => $unit_acl, + 'LOGIN_EAID' => $eaid, // DEPRECATED + 'LOGIN_EACUID' => $eacuid, ]); } // user has permission to THIS page } // user was not enabled or other login error @@ -1263,21 +1263,21 @@ class Login return; } // username (login), group name - $this->acl['user_name'] = $_SESSION['USER_NAME']; - $this->acl['group_name'] = $_SESSION['GROUP_NAME']; + $this->acl['user_name'] = $_SESSION['LOGIN_USER_NAME']; + $this->acl['group_name'] = $_SESSION['LOGIN_GROUP_NAME']; // edit user cuid - $this->acl['eucuid'] = $_SESSION['EUCUID']; - $this->acl['eucuuid'] = $_SESSION['EUCUUID']; + $this->acl['eucuid'] = $_SESSION['LOGIN_EUCUID']; + $this->acl['eucuuid'] = $_SESSION['LOGIN_EUCUUID']; // set additional acl $this->acl['additional_acl'] = [ - 'user' => $_SESSION['USER_ADDITIONAL_ACL'], - 'group' => $_SESSION['GROUP_ADDITIONAL_ACL'], + 'user' => $_SESSION['LOGIN_USER_ADDITIONAL_ACL'], + 'group' => $_SESSION['LOGIN_GROUP_ADDITIONAL_ACL'], ]; // we start with the default acl $this->acl['base'] = $this->default_acl_level; // set admin flag and base to 100 - if (!empty($_SESSION['ADMIN'])) { + if (!empty($_SESSION['LOGIN_ADMIN'])) { $this->acl['admin'] = 1; $this->acl['base'] = 100; } else { @@ -1285,36 +1285,36 @@ class Login // now go throw the flow and set the correct ACL // user > page > group // group ACL 0 - if ($_SESSION['GROUP_ACL_LEVEL'] != -1) { - $this->acl['base'] = (int)$_SESSION['GROUP_ACL_LEVEL']; + if ($_SESSION['LOGIN_GROUP_ACL_LEVEL'] != -1) { + $this->acl['base'] = (int)$_SESSION['LOGIN_GROUP_ACL_LEVEL']; } // page ACL 1 if ( - isset($_SESSION['PAGES_ACL_LEVEL'][$this->page_name]) && - $_SESSION['PAGES_ACL_LEVEL'][$this->page_name] != -1 + isset($_SESSION['LOGIN_PAGES_ACL_LEVEL'][$this->page_name]) && + $_SESSION['LOGIN_PAGES_ACL_LEVEL'][$this->page_name] != -1 ) { - $this->acl['base'] = (int)$_SESSION['PAGES_ACL_LEVEL'][$this->page_name]; + $this->acl['base'] = (int)$_SESSION['LOGIN_PAGES_ACL_LEVEL'][$this->page_name]; } // user ACL 2 - if ($_SESSION['USER_ACL_LEVEL'] != -1) { - $this->acl['base'] = (int)$_SESSION['USER_ACL_LEVEL']; + if ($_SESSION['LOGIN_USER_ACL_LEVEL'] != -1) { + $this->acl['base'] = (int)$_SESSION['LOGIN_USER_ACL_LEVEL']; } } - $this->session->set('BASE_ACL_LEVEL', $this->acl['base']); + $this->session->set('LOGIN_BASE_ACL_LEVEL', $this->acl['base']); // set the current page acl // start with base acl // set group if not -1, overrides default // set page if not -1, overrides group set $this->acl['page'] = $this->acl['base']; - if ($_SESSION['GROUP_ACL_LEVEL'] != -1) { - $this->acl['page'] = $_SESSION['GROUP_ACL_LEVEL']; + if ($_SESSION['LOGIN_GROUP_ACL_LEVEL'] != -1) { + $this->acl['page'] = $_SESSION['LOGIN_GROUP_ACL_LEVEL']; } if ( - isset($_SESSION['PAGES_ACL_LEVEL'][$this->page_name]) && - $_SESSION['PAGES_ACL_LEVEL'][$this->page_name] != -1 + isset($_SESSION['LOGIN_PAGES_ACL_LEVEL'][$this->page_name]) && + $_SESSION['LOGIN_PAGES_ACL_LEVEL'][$this->page_name] != -1 ) { - $this->acl['page'] = $_SESSION['PAGES_ACL_LEVEL'][$this->page_name]; + $this->acl['page'] = $_SESSION['LOGIN_PAGES_ACL_LEVEL'][$this->page_name]; } $this->acl['unit_id'] = null; @@ -1325,7 +1325,7 @@ class Login $this->acl['unit_detail'] = []; // PER ACCOUNT (UNIT/edit access)-> - foreach ($_SESSION['UNIT'] as $ea_cuid => $unit) { + foreach ($_SESSION['LOGIN_UNIT'] as $ea_cuid => $unit) { // if admin flag is set, all units are set to 100 if (!empty($this->acl['admin'])) { $this->acl['unit'][$ea_cuid] = $this->acl['base']; @@ -1355,7 +1355,7 @@ class Login } } // flag if to show extra edit access drop downs (because user has multiple groups assigned) - if (count($_SESSION['UNIT']) > 1) { + if (count($_SESSION['LOGIN_UNIT']) > 1) { $this->acl['show_ea_extra'] = true; } else { $this->acl['show_ea_extra'] = false; @@ -1370,7 +1370,7 @@ class Login // $this->debug('ACL', $this->print_ar($this->acl)); } - // MARK: lgin set locale + // MARK: login set locale /** * set locale @@ -2180,13 +2180,14 @@ HTML; ), [ // row 1 - empty($username) ? $this->session->get('USER_NAME') ?? '' : $username, - is_numeric($this->session->get('EUID')) ? - $this->session->get('EUID') : null, - is_string($this->session->get('EUCUID')) ? - $this->session->get('EUCUID') : null, - !empty($this->session->get('EUCUUID')) && Uids::validateUuuidv4($this->session->get('EUCUUID')) ? - $this->session->get('EUCUUID') : null, + empty($username) ? $this->session->get('LOGIN_USER_NAME') ?? '' : $username, + is_numeric($this->session->get('LOGIN_EUID')) ? + $this->session->get('LOGIN_EUID') : null, + is_string($this->session->get('LOGIN_EUCUID')) ? + $this->session->get('LOGIN_EUCUID') : null, + !empty($this->session->get('LOGIN_EUCUUID')) && + Uids::validateUuuidv4($this->session->get('LOGIN_EUCUUID')) ? + $this->session->get('LOGIN_EUCUUID') : null, (string)$event, (string)$error, $data_write, @@ -2313,8 +2314,7 @@ HTML; } } // if there is none, there is none, saves me POST/GET check - // $this->euid = (int)($this->session->get('EUID') ?? 0); - $this->edit_user_cuuid = (string)($this->session->get('EUCUUID') ?? ''); + $this->edit_user_cuuid = (string)($this->session->get('LOGIN_EUCUUID') ?? ''); // get login vars, are so, can't be changed // prepare // pass on vars to Object vars @@ -2481,7 +2481,7 @@ HTML; */ public function loginGetHeaderColor(): ?string { - return $this->session->get('HEADER_COLOR'); + return $this->session->get('LOGIN_HEADER_COLOR'); } /** @@ -2492,7 +2492,7 @@ HTML; public function loginGetPages(): array { - return $this->session->get('PAGES'); + return $this->session->get('LOGIN_PAGES'); } // MARK: logged in uid(pk)/cuid/ecuuid @@ -2528,6 +2528,11 @@ HTML; return (string)$this->edit_user_cuuid; } + /** + * Get the current set EUCUUID (edit user cuuid) + * + * @return string EUCUUID as string + */ public function loginGetEuCuuid(): string { return (string)$this->edit_user_cuuid; @@ -2791,9 +2796,9 @@ HTML; $this->edit_user_cuid = (string)$res['cuid']; $this->edit_user_cuuid = (string)$res['cuuid']; $this->session->setMany([ - 'EUID' => $this->edit_user_id, // DEPRECATED - 'EUCUID' => $this->edit_user_cuid, - 'EUCUUID' => $this->edit_user_cuuid, + 'LOGIN_EUID' => $this->edit_user_id, // DEPRECATED + 'LOGIN_EUCUID' => $this->edit_user_cuid, + 'LOGIN_EUCUUID' => $this->edit_user_cuuid, ]); // if called from public, so we can check if the permissions are ok return $this->permission_okay; @@ -2962,12 +2967,12 @@ HTML; { if ( $cuid !== null && - is_array($this->session->get('UNIT')) && - !array_key_exists($cuid, $this->session->get('UNIT')) + is_array($this->session->get('LOGIN_UNIT')) && + !array_key_exists($cuid, $this->session->get('LOGIN_UNIT')) ) { $cuid = null; - if (!empty($this->session->get('UNIT_DEFAULT_EACUID'))) { - $cuid = $this->session->get('UNIT_DEFAULT_EACUID'); + if (!empty($this->session->get('LOGIN_UNIT_DEFAULT_EACUID'))) { + $cuid = $this->session->get('LOGIN_UNIT_DEFAULT_EACUID'); } } return $cuid; @@ -2987,12 +2992,12 @@ HTML; { if ( $edit_access_id !== null && - is_array($this->session->get('UNIT_LEGACY')) && - !array_key_exists($edit_access_id, $this->session->get('UNIT_LEGACY')) + is_array($this->session->get('LOGIN_UNIT_LEGACY')) && + !array_key_exists($edit_access_id, $this->session->get('LOGIN_UNIT_LEGACY')) ) { $edit_access_id = null; - if (!empty($this->session->get('UNIT_DEFAULT_EAID'))) { - $edit_access_id = (int)$this->session->get('UNIT_DEFAULT_EAID'); + if (!empty($this->session->get('LOGIN_UNIT_DEFAULT_EAID'))) { + $edit_access_id = (int)$this->session->get('LOGIN_UNIT_DEFAULT_EAID'); } } return $edit_access_id; @@ -3010,10 +3015,10 @@ HTML; string $cuid, string|int $data_key ): false|string { - if (!isset($_SESSION['UNIT'][$cuid]['data'][$data_key])) { + if (!isset($_SESSION['LOGIN_UNIT'][$cuid]['data'][$data_key])) { return false; } - return $_SESSION['UNIT'][$cuid]['data'][$data_key]; + return $_SESSION['LOGIN_UNIT'][$cuid]['data'][$data_key]; } /** @@ -3026,10 +3031,10 @@ HTML; */ public function loginGetEditAccessIdFromUid(string $uid): int|false { - if (!isset($_SESSION['UNIT_UID'][$uid])) { + if (!isset($_SESSION['LOGIN_UNIT_UID'][$uid])) { return false; } - return (int)$_SESSION['UNIT_UID'][$uid]; + return (int)$_SESSION['LOGIN_UNIT_UID'][$uid]; } /** @@ -3040,10 +3045,10 @@ HTML; */ public function loginGetEditAccessCuidFromUid(string $uid): int|false { - if (!isset($_SESSION['UNIT_CUID'][$uid])) { + if (!isset($_SESSION['LOGIN_UNIT_CUID'][$uid])) { return false; } - return (int)$_SESSION['UNIT_CUID'][$uid]; + return (int)$_SESSION['LOGIN_UNIT_CUID'][$uid]; } /** From e8299a123b330b59b04130c7c7822de9a894f5f3 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 10 Dec 2024 10:06:49 +0900 Subject: [PATCH 011/105] Update Edit Log with JSONB blocks all action data goes into a JSON block and the old action columns will be deprecated Same for ip, new ip address block with all possible ip addeses Additional HTTP_ data goes into the http_data block new request_schema column to get if the request was done to http or https --- 4dev/database/table/edit_log.sql | 39 +++++++----- .../CoreLibsACLLogin_database_create_data.sql | 7 +-- .../edit_tables_cuid_cuuid_update_add.sql | 4 ++ www/admin/class_test.login.php | 6 ++ www/admin/class_test.php | 3 + www/lib/CoreLibs/ACL/Login.php | 62 +++++++++++-------- 6 files changed, 74 insertions(+), 47 deletions(-) diff --git a/4dev/database/table/edit_log.sql b/4dev/database/table/edit_log.sql index e1de32d3..7d2039a9 100644 --- a/4dev/database/table/edit_log.sql +++ b/4dev/database/table/edit_log.sql @@ -11,34 +11,41 @@ CREATE TABLE edit_log ( euid INT, -- this is a foreign key, but I don't nedd to reference to it FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL, ecuid VARCHAR, - ecuuid UUID, + ecuuid UUID, -- this is the one we want to use, full UUIDv4 from the edit user table username VARCHAR, password VARCHAR, event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, - ip VARCHAR, + ip VARCHAR, -- just the REMOTE_IP, full set see ip_address + ip_address JSONB, -- REMOTE_IP and all other IPs (X_FORWARD, etc) as JSON block error TEXT, event TEXT, data_binary BYTEA, data TEXT, page VARCHAR, - action VARCHAR, - action_id VARCHAR, - action_sub_id VARCHAR, - action_yes VARCHAR, - action_flag VARCHAR, - action_menu VARCHAR, - action_loaded VARCHAR, - action_value VARCHAR, - action_type VARCHAR, - action_error VARCHAR, + -- various info data sets user_agent VARCHAR, referer VARCHAR, script_name VARCHAR, query_string VARCHAR, + request_scheme VARCHAR, -- http or https server_name VARCHAR, http_host VARCHAR, - http_accept VARCHAR, - http_accept_charset VARCHAR, - http_accept_encoding VARCHAR, - session_id VARCHAR + http_data JSONB, + http_accept VARCHAR, -- in http_data + http_accept_charset VARCHAR, -- in http_data + http_accept_encoding VARCHAR, -- in http_data + -- session ID if set + session_id VARCHAR. + -- any action var, -> same set in action_data as JSON + action_data JSONB, + action VARCHAR, -- in action_data + action_id VARCHAR, -- in action_data + action_sub_id VARCHAR, -- in action_data + action_yes VARCHAR, -- in action_data + action_flag VARCHAR, -- in action_data + action_menu VARCHAR, -- in action_data + action_loaded VARCHAR, -- in action_data + action_value VARCHAR, -- in action_data + action_type VARCHAR, -- in action_data + action_error VARCHAR -- in action_data ) INHERITS (edit_generic) WITHOUT OIDS; diff --git a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql index 3d4a54b4..9216f692 100644 --- a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql +++ b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql @@ -579,9 +579,6 @@ CREATE TABLE edit_user ( strict SMALLINT DEFAULT 0, locked SMALLINT DEFAULT 0, protected SMALLINT NOT NULL DEFAULT 0, - -- legacy, debug flags - debug SMALLINT NOT NULL DEFAULT 0, - db_debug SMALLINT NOT NULL DEFAULT 0, -- is admin user admin SMALLINT NOT NULL DEFAULT 0, -- last login log @@ -620,8 +617,6 @@ COMMENT ON COLUMN edit_user.deleted IS 'Login is deleted (master switch), overri COMMENT ON COLUMN edit_user.strict IS 'If too many failed logins user will be locked, default off'; COMMENT ON COLUMN edit_user.locked IS 'Locked from too many wrong password logins'; COMMENT ON COLUMN edit_user.protected IS 'User can only be chnaged by admin user'; -COMMENT ON COLUMN edit_user.debug IS 'Turn debug flag on (legacy)'; -COMMENT ON COLUMN edit_user.db_debug IS 'Turn DB debug flag on (legacy)'; COMMENT ON COLUMN edit_user.admin IS 'If set, this user is SUPER admin'; COMMENT ON COLUMN edit_user.last_login IS 'Last succesfull login tiemstamp'; COMMENT ON COLUMN edit_user.login_error_count IS 'Number of failed logins, reset on successful login'; @@ -1015,7 +1010,7 @@ INSERT INTO edit_page_access (enabled, edit_group_id, edit_page_id, edit_access_ -- edit user -- inserts admin user so basic users can be created DELETE FROM edit_user; -INSERT INTO edit_user (username, password, enabled, debug, db_debug, email, protected, admin, edit_language_id, edit_group_id, edit_scheme_id, edit_access_right_id) VALUES ('admin', 'admin', 1, 1, 1, '', 1, 1, +INSERT INTO edit_user (username, password, enabled, email, protected, admin, edit_language_id, edit_group_id, edit_scheme_id, edit_access_right_id) VALUES ('admin', 'admin', 1, 'test@tequila.jp', 1, 1, (SELECT edit_language_id FROM edit_language WHERE short_name = 'en_US'), (SELECT edit_group_id FROM edit_group WHERE name = 'Admin'), (SELECT edit_scheme_id FROM edit_scheme WHERE name = 'Admin'), diff --git a/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql b/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql index f4e36ec3..df2bdbef 100644 --- a/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql +++ b/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql @@ -3,6 +3,10 @@ ALTER TABLE edit_generic ADD cuuid UUID DEFAULT gen_random_uuid(); ALTER TABLE edit_log ADD ecuid VARCHAR; ALTER TABLE edit_log ADD ecuuid VARCHAR; ALTER TABLE edit_log ADD action_sub_id VARCHAR; +ALTER TABLE edit_log ADD http_data JSONB; +ALTER TABLE edit_log ADD ip_address JSONB; +ALTER TABLE edit_log ADD action_data JSONB; +ALTER TABLE edit_log ADD request_scheme VARCHAR; -- update set_edit_gneric -- adds the created or updated date tags diff --git a/www/admin/class_test.login.php b/www/admin/class_test.login.php index e5bbc6d4..d153078d 100644 --- a/www/admin/class_test.login.php +++ b/www/admin/class_test.login.php @@ -118,6 +118,12 @@ if (isset($login->loginGetAcl()['unit'])) { print "Something went wrong with the login
"; } +echo "
"; + +// IP check: 'REMOTE_ADDR', 'HTTP_X_FORWARDED_FOR', 'CLIENT_IP' in _SERVER +// Agent check: 'HTTP_USER_AGENT' + + echo "
"; print "SESSION: " . Support::printAr($_SESSION) . "
"; diff --git a/www/admin/class_test.php b/www/admin/class_test.php index 138496ef..616f684a 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -194,6 +194,9 @@ print "HOST: " . HOST_NAME . " => DB HOST: " . DB_CONFIG_NAME . " => " . Support print "DS is: " . DIRECTORY_SEPARATOR . "
"; print "SERVER HOST: " . $_SERVER['HTTP_HOST'] . "
"; +print "
READ _SERVER ARRAY:
"; +print Support::dumpVar(array_map('htmlentities', $_SERVER)); + print ""; # __END__ diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index db6fb705..69160ce8 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -1904,7 +1904,7 @@ body { margin: 2% 5%; } .login-data { - margin: 0 5% 5% 5%; + margin: 2% 5% 5% 5%; } .login-data-row { display: flex; @@ -1951,7 +1951,7 @@ button.login-button { margin: 5% 0; } .login-data { - margin: 0 5% 5% 5%; + margin: 5%; } .login-error { margin: 10% 5%; @@ -2160,16 +2160,18 @@ HTML; $q = <<db->dbExecParams( @@ -2186,7 +2188,7 @@ HTML; is_string($this->session->get('LOGIN_EUCUID')) ? $this->session->get('LOGIN_EUCUID') : null, !empty($this->session->get('LOGIN_EUCUUID')) && - Uids::validateUuuidv4($this->session->get('LOGIN_EUCUUID')) ? + Uids::validateUuuidv4($this->session->get('LOGIN_EUCUUID')) ? $this->session->get('LOGIN_EUCUUID') : null, (string)$event, (string)$error, @@ -2195,29 +2197,39 @@ HTML; (string)$this->page_name, // row 2 $_SERVER["REMOTE_ADDR"] ?? null, + [ + 'REMOTE_ADDR' => $_SERVER["REMOTE_ADDR"], + ], $_SERVER['HTTP_USER_AGENT'] ?? null, $_SERVER['HTTP_REFERER'] ?? null, $_SERVER['SCRIPT_FILENAME'] ?? null, $_SERVER['QUERY_STRING'] ?? null, + $_SERVER['REQUEST_SCHEME'] ?? null, $_SERVER['SERVER_NAME'] ?? null, - $_SERVER['HTTP_HOST'] ?? null, // row 3 - $_SERVER['HTTP_ACCEPT'] ?? null, - $_SERVER['HTTP_ACCEPT_CHARSET'] ?? null, - $_SERVER['HTTP_ACCEPT_ENCODING'] ?? null, + $_SERVER['HTTP_HOST'] ?? null, + [ + 'HTTP_ACCEPT' => $_SERVER['HTTP_ACCEPT'] ?? null, + 'HTTP_ACCEPT_CHARSET' => $_SERVER['HTTP_ACCEPT_CHARSET'] ?? null, + 'HTTP_ACCEPT_LANGUAGE' => $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? null, + 'HTTP_ACCEPT_ENCODING' => $_SERVER['HTTP_ACCEPT_ENCODING'] ?? null, + ], $this->session->getSessionId() !== '' ? $this->session->getSessionId() : null, // row 4 - $action_set['action'] ?? null, - $action_set['action_id'] ?? null, - $action_set['action_sub_id'] ?? null, - $action_set['action_yes'] ?? null, - $action_set['action_flag'] ?? null, - $action_set['action_menu'] ?? null, - $action_set['action_loaded'] ?? null, - $action_set['action_value'] ?? null, - $action_set['action_type'] ?? null, - $action_set['action_error'] ?? null, + // action data as JSONB + [ + 'action' => $action_set['action'] ?? null, + 'action_id' => $action_set['action_id'] ?? null, + 'action_sub_id' => $action_set['action_sub_id'] ?? null, + 'action_yes' => $action_set['action_yes'] ?? null, + 'action_flag' => $action_set['action_flag'] ?? null, + 'action_menu' => $action_set['action_menu'] ?? null, + 'action_loaded' => $action_set['action_loaded'] ?? null, + 'action_value' => $action_set['action_value'] ?? null, + 'action_type' => $action_set['action_type'] ?? null, + 'action_error' => $action_set['action_error'] ?? null, + ] ], 'NULL' ); From 78591d6ba46d0f3cb3c5cfe6c4f1b6e5dcd71b42 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 10 Dec 2024 12:01:06 +0900 Subject: [PATCH 012/105] Fix Param regex lookup Query was not counting params after "--" comment strings --- 4dev/tests/DB/CoreLibsDBIOTest.php | 33 +++++++++++++++++++ www/lib/CoreLibs/DB/IO.php | 1 + .../DB/Support/ConvertPlaceholder.php | 15 +++++++-- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/4dev/tests/DB/CoreLibsDBIOTest.php b/4dev/tests/DB/CoreLibsDBIOTest.php index 19420a47..fab83940 100644 --- a/4dev/tests/DB/CoreLibsDBIOTest.php +++ b/4dev/tests/DB/CoreLibsDBIOTest.php @@ -5135,6 +5135,39 @@ final class CoreLibsDBIOTest extends TestCase SQL, 'count' => 6, 'convert' => false, + ], + 'comments in insert' => [ + 'query' => << 4, + 'convert' => false + ], + // Note some are not set + 'a complete set of possible' => [ + 'query' => << $3 + AND row_varchar > $4 AND row_varchar < $5 + AND row_varchar >= $6 AND row_varchar <=$7 + AND row_jsonb->'a' = $8 AND row_jsonb->>$9 = 'a' + AND row_jsonb<@$10 AND row_jsonb@>$11 + AND row_varchar ^@ $12 + SQL, + 'count' => 12, + 'convert' => false, ] ]; } diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index 4daad5b8..d579fd16 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -1332,6 +1332,7 @@ class IO */ private function __dbCheckQueryParams(string $query, array $params): bool { + // $this->log->debug('DB QUERY PARAMS REGEX', ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS); $placeholder_count = $this->__dbCountQueryParams($query); $params_count = count($params); if ($params_count != $placeholder_count) { diff --git a/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php b/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php index 0b9542b2..3541276e 100644 --- a/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php +++ b/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php @@ -14,8 +14,19 @@ namespace CoreLibs\DB\Support; class ConvertPlaceholder { - /** @var string split regex */ - private const PATTERN_QUERY_SPLIT = '[(<>=,?-]|->|->>|#>|#>>|@>|<@|\?\|\?\&|\|\||#-'; + // NOTE for missing: range */+ are not iplemented in the regex below, but - is for now + // NOTE some combinations are allowed, but the query will fail before this + /** @var string split regex, entries before $ group */ + private const PATTERN_QUERY_SPLIT = + ',|' // for ',' mostly in INSERT + . '[(<>=]|' // general set for (, <, >, = in any query with any combination + . '(?:[\(,]\s*\-\-\s*\w*)\r?\n|' // a comment that starts after a ( or , + . '\^@|' // text search for start from text with ^@ + . '\|\||' // concats two elements + . '&&|' // array overlap + . '\-\|\-|' // range overlap + . '[^-]-{1}|' // single -, used in JSON too + . '->|->>|#>|#>>|@>|<@|@@|@\?|\?{1}|\?\||\?&|#-'; //JSON searches, Array searchs, etc /** @var string the main regex including the pattern query split */ private const PATTERN_ELEMENT = '(?:\'.*?\')?\s*(?:\?\?|' . self::PATTERN_QUERY_SPLIT . ')\s*'; /** @var string parts to ignore in the SQL */ From a7742bd5c82192381c45b6dc76829bc73846705b Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 10 Dec 2024 13:36:57 +0900 Subject: [PATCH 013/105] DB IO count params fix for comments --- 4dev/tests/DB/CoreLibsDBIOTest.php | 6 +++--- www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/4dev/tests/DB/CoreLibsDBIOTest.php b/4dev/tests/DB/CoreLibsDBIOTest.php index fab83940..faadf6e1 100644 --- a/4dev/tests/DB/CoreLibsDBIOTest.php +++ b/4dev/tests/DB/CoreLibsDBIOTest.php @@ -5141,11 +5141,11 @@ final class CoreLibsDBIOTest extends TestCase INSERT INTO table_with_primary_key ( row_int, row_numeric, row_varchar, row_varchar_literal ) VALUES ( - -- comment + -- comment 1 $1, $2, - -- comment + -- comment 2 $3 - -- comment + -- comment 3 , $4 ) SQL, diff --git a/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php b/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php index 3541276e..fd8eea60 100644 --- a/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php +++ b/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php @@ -20,7 +20,7 @@ class ConvertPlaceholder private const PATTERN_QUERY_SPLIT = ',|' // for ',' mostly in INSERT . '[(<>=]|' // general set for (, <, >, = in any query with any combination - . '(?:[\(,]\s*\-\-\s*\w*)\r?\n|' // a comment that starts after a ( or , + . '(?:[\(,]\s*\-\-[\s\w]*)\r?\n|' // a comment that starts after a ( or , . '\^@|' // text search for start from text with ^@ . '\|\||' // concats two elements . '&&|' // array overlap From 41cb6358f9a14147c988d774a6c427a348ebd82a Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 10 Dec 2024 14:40:07 +0900 Subject: [PATCH 014/105] phpunit checks update, update edit_log logging sets --- .../CoreLibsACLLogin_database_create_data.sql | 39 +++++++++++-------- www/lib/CoreLibs/ACL/Login.php | 18 ++++++--- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql index 9216f692..c5f4bb3c 100644 --- a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql +++ b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql @@ -651,36 +651,43 @@ CREATE TABLE edit_log ( euid INT, -- this is a foreign key, but I don't nedd to reference to it FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL, ecuid VARCHAR, - ecuuid UUID, + ecuuid UUID, -- this is the one we want to use, full UUIDv4 from the edit user table username VARCHAR, password VARCHAR, event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, - ip VARCHAR, + ip VARCHAR, -- just the REMOTE_IP, full set see ip_address + ip_address JSONB, -- REMOTE_IP and all other IPs (X_FORWARD, etc) as JSON block error TEXT, event TEXT, data_binary BYTEA, data TEXT, page VARCHAR, - action VARCHAR, - action_id VARCHAR, - action_sub_id VARCHAR, - action_yes VARCHAR, - action_flag VARCHAR, - action_menu VARCHAR, - action_loaded VARCHAR, - action_value VARCHAR, - action_type VARCHAR, - action_error VARCHAR, + -- various info data sets user_agent VARCHAR, referer VARCHAR, script_name VARCHAR, query_string VARCHAR, + request_scheme VARCHAR, -- http or https server_name VARCHAR, http_host VARCHAR, - http_accept VARCHAR, - http_accept_charset VARCHAR, - http_accept_encoding VARCHAR, - session_id VARCHAR + http_data JSONB, + http_accept VARCHAR, -- in http_data + http_accept_charset VARCHAR, -- in http_data + http_accept_encoding VARCHAR, -- in http_data + -- session ID if set + session_id VARCHAR. + -- any action var, -> same set in action_data as JSON + action_data JSONB, + action VARCHAR, -- in action_data + action_id VARCHAR, -- in action_data + action_sub_id VARCHAR, -- in action_data + action_yes VARCHAR, -- in action_data + action_flag VARCHAR, -- in action_data + action_menu VARCHAR, -- in action_data + action_loaded VARCHAR, -- in action_data + action_value VARCHAR, -- in action_data + action_type VARCHAR, -- in action_data + action_error VARCHAR -- in action_data ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_log.sql -- START: table/edit_log_overflow.sql diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 69160ce8..5b8609de 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -2197,9 +2197,15 @@ HTML; (string)$this->page_name, // row 2 $_SERVER["REMOTE_ADDR"] ?? null, - [ + Json::jsonConvertArrayTo([ 'REMOTE_ADDR' => $_SERVER["REMOTE_ADDR"], - ], + 'HTTP_X_FORWARDED_FOR' => !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? + explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']) + : [], + 'CLIENT_IP' => !empty($_SERVER['CLIENT_IP']) ? + explode(',', $_SERVER['CLIENT_IP']) + : [], + ]), $_SERVER['HTTP_USER_AGENT'] ?? null, $_SERVER['HTTP_REFERER'] ?? null, $_SERVER['SCRIPT_FILENAME'] ?? null, @@ -2208,17 +2214,17 @@ HTML; $_SERVER['SERVER_NAME'] ?? null, // row 3 $_SERVER['HTTP_HOST'] ?? null, - [ + Json::jsonConvertArrayTo([ 'HTTP_ACCEPT' => $_SERVER['HTTP_ACCEPT'] ?? null, 'HTTP_ACCEPT_CHARSET' => $_SERVER['HTTP_ACCEPT_CHARSET'] ?? null, 'HTTP_ACCEPT_LANGUAGE' => $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? null, 'HTTP_ACCEPT_ENCODING' => $_SERVER['HTTP_ACCEPT_ENCODING'] ?? null, - ], + ]), $this->session->getSessionId() !== '' ? $this->session->getSessionId() : null, // row 4 // action data as JSONB - [ + Json::jsonConvertArrayTo([ 'action' => $action_set['action'] ?? null, 'action_id' => $action_set['action_id'] ?? null, 'action_sub_id' => $action_set['action_sub_id'] ?? null, @@ -2229,7 +2235,7 @@ HTML; 'action_value' => $action_set['action_value'] ?? null, 'action_type' => $action_set['action_type'] ?? null, 'action_error' => $action_set['action_error'] ?? null, - ] + ]) ], 'NULL' ); From 46e44c19bfea238243530607d317bdd7bb112daf Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 10 Dec 2024 14:44:00 +0900 Subject: [PATCH 015/105] edit log table column order update --- 4dev/database/table/edit_log.sql | 19 +++++++++++++----- .../CoreLibsACLLogin_database_create_data.sql | 20 +++++++++++++------ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/4dev/database/table/edit_log.sql b/4dev/database/table/edit_log.sql index 7d2039a9..5c492849 100644 --- a/4dev/database/table/edit_log.sql +++ b/4dev/database/table/edit_log.sql @@ -12,15 +12,24 @@ CREATE TABLE edit_log ( FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL, ecuid VARCHAR, ecuuid UUID, -- this is the one we want to use, full UUIDv4 from the edit user table - username VARCHAR, - password VARCHAR, + -- date_created equal, but can be overridden event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, - ip VARCHAR, -- just the REMOTE_IP, full set see ip_address + -- session ID if set + session_id VARCHAR, + -- username + username VARCHAR, + -- DEPRECATED [password] + password VARCHAR, ip_address JSONB, -- REMOTE_IP and all other IPs (X_FORWARD, etc) as JSON block + -- DEPRECATED [ip] + ip VARCHAR, -- just the REMOTE_IP, full set see ip_address + -- string blocks, general error TEXT, event TEXT, + -- bytea or string type storage of any data data_binary BYTEA, data TEXT, + -- set page name only page VARCHAR, -- various info data sets user_agent VARCHAR, @@ -31,13 +40,13 @@ CREATE TABLE edit_log ( server_name VARCHAR, http_host VARCHAR, http_data JSONB, + -- DEPRECATED [http*] http_accept VARCHAR, -- in http_data http_accept_charset VARCHAR, -- in http_data http_accept_encoding VARCHAR, -- in http_data - -- session ID if set - session_id VARCHAR. -- any action var, -> same set in action_data as JSON action_data JSONB, + -- DEPRECATED [action*] action VARCHAR, -- in action_data action_id VARCHAR, -- in action_data action_sub_id VARCHAR, -- in action_data diff --git a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql index c5f4bb3c..686d5a03 100644 --- a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql +++ b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql @@ -652,15 +652,24 @@ CREATE TABLE edit_log ( FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL, ecuid VARCHAR, ecuuid UUID, -- this is the one we want to use, full UUIDv4 from the edit user table - username VARCHAR, - password VARCHAR, + -- date_created equal, but can be overridden event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, - ip VARCHAR, -- just the REMOTE_IP, full set see ip_address + -- session ID if set + session_id VARCHAR, + -- username + username VARCHAR, + -- DEPRECATED [password] + password VARCHAR, ip_address JSONB, -- REMOTE_IP and all other IPs (X_FORWARD, etc) as JSON block + -- DEPRECATED [ip] + ip VARCHAR, -- just the REMOTE_IP, full set see ip_address + -- string blocks, general error TEXT, event TEXT, + -- bytea or string type storage of any data data_binary BYTEA, data TEXT, + -- set page name only page VARCHAR, -- various info data sets user_agent VARCHAR, @@ -671,13 +680,13 @@ CREATE TABLE edit_log ( server_name VARCHAR, http_host VARCHAR, http_data JSONB, + -- DEPRECATED [http*] http_accept VARCHAR, -- in http_data http_accept_charset VARCHAR, -- in http_data http_accept_encoding VARCHAR, -- in http_data - -- session ID if set - session_id VARCHAR. -- any action var, -> same set in action_data as JSON action_data JSONB, + -- DEPRECATED [action*] action VARCHAR, -- in action_data action_id VARCHAR, -- in action_data action_sub_id VARCHAR, -- in action_data @@ -688,7 +697,6 @@ CREATE TABLE edit_log ( action_value VARCHAR, -- in action_data action_type VARCHAR, -- in action_data action_error VARCHAR -- in action_data -) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_log.sql -- START: table/edit_log_overflow.sql -- AUTHOR: Clemens Schwaighofer From fdefaca301e2d244ff9111e367a38c2c3ecc05a1 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 10 Dec 2024 15:22:59 +0900 Subject: [PATCH 016/105] Missing php unit test path for locale check --- 4dev/tests/Language/locale_other/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 4dev/tests/Language/locale_other/.gitignore diff --git a/4dev/tests/Language/locale_other/.gitignore b/4dev/tests/Language/locale_other/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/4dev/tests/Language/locale_other/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From 022c39e791051ec902ed37c549253e6f24b18e86 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 10 Dec 2024 15:24:45 +0900 Subject: [PATCH 017/105] Add missing phpunit test folder for deprecated session var load test --- 4dev/tests/Language/locale_other/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 4dev/tests/Language/locale_other/.gitignore diff --git a/4dev/tests/Language/locale_other/.gitignore b/4dev/tests/Language/locale_other/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/4dev/tests/Language/locale_other/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From a46601fe0337cac0cabb5733bbabb69b187fcb87 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 10 Dec 2024 15:25:17 +0900 Subject: [PATCH 018/105] Sync folder is master and not trunk --- 4dev/composer/sync-to-composer-all-folder.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/4dev/composer/sync-to-composer-all-folder.sh b/4dev/composer/sync-to-composer-all-folder.sh index d717bfcb..07e57098 100755 --- a/4dev/composer/sync-to-composer-all-folder.sh +++ b/4dev/composer/sync-to-composer-all-folder.sh @@ -13,7 +13,7 @@ if [ "${GO}" != "go" ]; then fi; BASE="/storage/var/www/html/developers/clemens/core_data/"; -SOURCE="${BASE}php_libraries/trunk/" +SOURCE="${BASE}php_libraries/master/" TARGET="${BASE}composer-packages/CoreLibs-Composer-All/" rsync ${DRY_RUN}-Plzvrupt --stats --delete ${SOURCE}4dev/tests/ ${TARGET}test/phpunit/ From 534303476831e490ac6abb815eb64c3776528436 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 11 Dec 2024 10:30:41 +0900 Subject: [PATCH 019/105] Fix DB IO placeholder detect and count regex comment regex: (?:\-\-[^\r\n]*?\r?\n)* Which is AFTER the element search as the comment can appear anywhere after the tag trigger --- 4dev/tests/DB/CoreLibsDBIOTest.php | 32 ++++- .../class_test.db.convert-placeholder.php | 1 - www/admin/class_test.db.query-placeholder.php | 129 ++++++++++++++---- www/admin/class_test.php | 2 +- www/lib/CoreLibs/DB/IO.php | 2 +- .../DB/Support/ConvertPlaceholder.php | 17 ++- 6 files changed, 146 insertions(+), 37 deletions(-) diff --git a/4dev/tests/DB/CoreLibsDBIOTest.php b/4dev/tests/DB/CoreLibsDBIOTest.php index faadf6e1..14767292 100644 --- a/4dev/tests/DB/CoreLibsDBIOTest.php +++ b/4dev/tests/DB/CoreLibsDBIOTest.php @@ -5141,9 +5141,9 @@ final class CoreLibsDBIOTest extends TestCase INSERT INTO table_with_primary_key ( row_int, row_numeric, row_varchar, row_varchar_literal ) VALUES ( - -- comment 1 + -- comment 1 かな $1, $2, - -- comment 2 + -- comment 2 - $3 -- comment 3 , $4 @@ -5152,6 +5152,23 @@ final class CoreLibsDBIOTest extends TestCase 'count' => 4, 'convert' => false ], + 'comment in update' => [ + 'query' => << 4, + 'convert' => false, + ], // Note some are not set 'a complete set of possible' => [ 'query' => << 12, 'convert' => false, + ], + // all the same + 'all the same numbered' => [ + 'query' => << 1, + 'convert' => false, ] ]; } diff --git a/www/admin/class_test.db.convert-placeholder.php b/www/admin/class_test.db.convert-placeholder.php index 8cca56a9..e95586ad 100644 --- a/www/admin/class_test.db.convert-placeholder.php +++ b/www/admin/class_test.db.convert-placeholder.php @@ -28,7 +28,6 @@ $log = new CoreLibs\Logging\Logging([ 'log_per_date' => true, ]); - $PAGE_NAME = 'TEST CLASS: DB CONVERT PLACEHOLDER'; print ""; print "" . $PAGE_NAME . ""; diff --git a/www/admin/class_test.db.query-placeholder.php b/www/admin/class_test.db.query-placeholder.php index b2d729c4..c934962a 100644 --- a/www/admin/class_test.db.query-placeholder.php +++ b/www/admin/class_test.db.query-placeholder.php @@ -53,6 +53,9 @@ if (($dbh = $db->dbGetDbh()) instanceof \PgSql\Connection) { } else { print "NO DB HANDLER
"; } +// REGEX for placeholder count +print "Placeholder regex:
" . CoreLibs\DB\Support\ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS . "
"; + // turn on debug replace for placeholders $db->dbSetDebugReplacePlaceholder(true); @@ -62,53 +65,94 @@ $db->dbExec("TRUNCATE test_foo"); $uniqid = \CoreLibs\Create\Uids::uniqIdShort(); $binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: ''); $query_params = [ - $uniqid, - true, - 'STRING A', - 2, - 2.5, - 1, - date('H:m:s'), - date('Y-m-d H:i:s'), - json_encode(['a' => 'string', 'b' => 1, 'c' => 1.5, 'f' => true, 'g' => ['a', 1, 1.5]]), - null, - '{"a", "b"}', - '{1,2}', - '{"(array Text A, 5, 8.8)","(array Text B, 10, 15.2)"}', - '("Text", 4, 6.3)', - $binary_data + $uniqid, // test + true, // some_bool + 'STRING A', // string_a + 2, // number_a + 2.5, // numeric_a + 1, // smallint + date('H:m:s'), // some_internval + date('Y-m-d H:i:s'), // some_timestamp + json_encode(['a' => 'string', 'b' => 1, 'c' => 1.5, 'f' => true, 'g' => ['a', 1, 1.5]]), // json_string + null, // null_var + '{"a", "b"}', // array_char_1 + '{1,2}', // array_int_1 + '{"(array Text A, 5, 8.8)","(array Text B, 10, 15.2)"}', // array_composite + '("Text", 4, 6.3)', // composite_item + $binary_data, // some_binary + date('Y-m-d'), // some_date + date('H:i:s'), // some_time + '{"c", "d", "e"}', // array_char_2 + '{3,4,5}', // array_int_2 + 12345667778818, // bigint + 1.56, // numbrer_real + 3.75, // number_double + 124.5, // numeric_3 + \CoreLibs\Create\Uids::uuidv4() // uuid_var ]; $query_insert = <<dbExecParams($query_insert, $query_params); echo "*
"; echo "INSERT ALL COLUMN TYPES: " . Support::printToString($query_params) . " |
" - . "QUERY: " . $db->dbGetQuery() . " |
" + . "QUERY:
" . $db->dbGetQuery() . "
|
" . "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " |
" . "RETURNING EXT:
" . print_r($db->dbGetReturningExt(), true) . "
|
" . "RETURNING RETURN:
" . print_r($db->dbGetReturningArray(), true) . "
 |
" @@ -146,6 +190,16 @@ SQL, 'params' => [], 'direction' => 'pg', ], + 'numbers' => [ + 'query' => << [\CoreLibs\Create\Uids::uniqIdShort(), 'string A-1', 1234], + 'direction' => 'pdo', + ], 'a?' => [ 'query' => << 'pg', ], + 'select, compare $' => [ + 'query' => <<= $1 OR number_a <= $2 OR + number_a > $3 OR number_a < $4 + OR number_a = $5 OR number_a <> $6 + SQL, + 'params' => [1, 2, 3, 4, 5, 6], + 'direction' => 'pg' + ] ]; $db->dbSetConvertPlaceholder(true); @@ -184,11 +250,12 @@ foreach ($test_queries as $info => $data) { // . "
"; if ($db->dbCheckQueryForSelect($query)) { $row = $db->dbReturnRowParams($query, $params); - print "[$info] SELECT: " . Support::prAr($row) . "
"; + print "[$info] SELECT: " . Support::prAr($row) . "
"; } else { $db->dbExecParams($query, $params); } - print "[$info] " . Support::printAr($db->dbGetPlaceholderConverted()) . "
"; + print "ERROR: " . $db->dbGetLastError(true) . "
"; + print "[$info] " . Support::printAr($db->dbGetPlaceholderConverted()) . "
"; echo "
"; } @@ -203,22 +270,29 @@ SQL, ['string A-1'] )) ) { - print "RES: " . Support::prAr($res) . "
"; + print "RES: " . Support::prAr($res) . "
"; } +print "ERROR: " . $db->dbGetLastError(true) . "
"; +echo "
"; print "CursorExt: " . Support::prAr($db->dbGetCursorExt(<<"; +// ERROR BELOW: missing params $res = $db->dbReturnRowParams(<<dbGetPlaceholderConverted()) . "
"; +print "ERROR: " . $db->dbGetLastError(true) . "
"; +echo "
"; +// ERROR BELOW: LIKE cannot have placeholder echo "dbReturn read LIKE:
"; while ( is_array($res = $db->dbReturnParams( @@ -232,6 +306,7 @@ SQL, ) { print "RES: " . Support::prAr($res) . "
"; } +print "ERROR: " . $db->dbGetLastError(true) . "
"; print ""; $db->log->debug('DEBUGEND', '==================================== [END]'); diff --git a/www/admin/class_test.php b/www/admin/class_test.php index 4f576fb2..40d2eba1 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -91,7 +91,7 @@ HTML; $test_files = [ 'class_test.db.php' => 'Class Test: DB', 'class_test.db.types.php' => 'Class Test: DB column type convert', - 'class_test.db.query-placeholder.php' => 'Class Test: DB query placeholder convert', + 'class_test.db.query-placeholder.php' => 'Class Test: DB placeholder queries', 'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn', 'class_test.db.single.php' => 'Class Test: DB single query tests', 'class_test.db.convert-placeholder.php' => 'Class Test: DB convert placeholder', diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index d579fd16..7af3407f 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -1332,7 +1332,7 @@ class IO */ private function __dbCheckQueryParams(string $query, array $params): bool { - // $this->log->debug('DB QUERY PARAMS REGEX', ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS); + $this->log->debug('DB QUERY PARAMS REGEX', ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS); $placeholder_count = $this->__dbCountQueryParams($query); $params_count = count($params); if ($params_count != $placeholder_count) { diff --git a/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php b/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php index fd8eea60..dff03788 100644 --- a/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php +++ b/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php @@ -18,17 +18,20 @@ class ConvertPlaceholder // NOTE some combinations are allowed, but the query will fail before this /** @var string split regex, entries before $ group */ private const PATTERN_QUERY_SPLIT = - ',|' // for ',' mostly in INSERT - . '[(<>=]|' // general set for (, <, >, = in any query with any combination - . '(?:[\(,]\s*\-\-[\s\w]*)\r?\n|' // a comment that starts after a ( or , + '\?\?|' // UNKNOWN: double ??, is this to avoid something? + . '[\(,]|' // for ',' and '(' mostly in INSERT or ANY() + . '[<>=]|' // general set for <, >, = in any query with any combination . '\^@|' // text search for start from text with ^@ . '\|\||' // concats two elements . '&&|' // array overlap - . '\-\|\-|' // range overlap + . '\-\|\-|' // range overlap for array . '[^-]-{1}|' // single -, used in JSON too . '->|->>|#>|#>>|@>|<@|@@|@\?|\?{1}|\?\||\?&|#-'; //JSON searches, Array searchs, etc /** @var string the main regex including the pattern query split */ - private const PATTERN_ELEMENT = '(?:\'.*?\')?\s*(?:\?\?|' . self::PATTERN_QUERY_SPLIT . ')\s*'; + private const PATTERN_ELEMENT = '(?:\'.*?\')?\s*(?:' . self::PATTERN_QUERY_SPLIT . ')\s*'; + /** @var string comment regex + * anything that starts with -- and ends with a line break but any character that is not line break inbetween */ + private const PATTERN_COMMENT = '(?:\-\-[^\r\n]*?\r?\n)*\s*'; /** @var string parts to ignore in the SQL */ private const PATTERN_IGNORE = // digit -> ignore @@ -45,6 +48,7 @@ class ConvertPlaceholder /** @var string replace regex for named (:...) entries */ public const REGEX_REPLACE_NAMED = '/' . '(' . self::PATTERN_ELEMENT . ')' + . self::PATTERN_COMMENT . '(' . self::PATTERN_IGNORE . self::PATTERN_NAMED @@ -53,6 +57,7 @@ class ConvertPlaceholder /** @var string replace regex for question mark (?) entries */ public const REGEX_REPLACE_QUESTION_MARK = '/' . '(' . self::PATTERN_ELEMENT . ')' + . self::PATTERN_COMMENT . '(' . self::PATTERN_IGNORE . self::PATTERN_QUESTION_MARK @@ -61,6 +66,7 @@ class ConvertPlaceholder /** @var string replace regex for numbered ($n) entries */ public const REGEX_REPLACE_NUMBERED = '/' . '(' . self::PATTERN_ELEMENT . ')' + . self::PATTERN_COMMENT . '(' . self::PATTERN_IGNORE . self::PATTERN_NUMBERED @@ -71,6 +77,7 @@ class ConvertPlaceholder // prefix string part, must match towards // seperator for ( = , ? - [and json/jsonb in pg doc section 9.15] . self::PATTERN_ELEMENT + . self::PATTERN_COMMENT // match for replace part . '(?:' // ignore parts From ba11a936db3605f22644a5749b1a5f4fc326c1a1 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 11 Dec 2024 10:36:31 +0900 Subject: [PATCH 020/105] DB IO remove debug placeholder output --- www/lib/CoreLibs/DB/IO.php | 1 - 1 file changed, 1 deletion(-) diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index 7af3407f..4daad5b8 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -1332,7 +1332,6 @@ class IO */ private function __dbCheckQueryParams(string $query, array $params): bool { - $this->log->debug('DB QUERY PARAMS REGEX', ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS); $placeholder_count = $this->__dbCountQueryParams($query); $params_count = count($params); if ($params_count != $placeholder_count) { From 8d3882a6feb7fe8a18a3ef38b8a0e70451096d4f Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 11 Dec 2024 21:02:21 +0900 Subject: [PATCH 021/105] Session and ACL Login Class update Session: regenerate session id after some time or random. Default is 'never', can be 'interval' form 0 to 1h and random from always to 1 in 100 Session also checks that strict session settings are enabled Login class: Automatic re-read of acl settings after some time (default 5min, can be chnaged via option). Default set strict headers, can be turned off via option Moved various parts into their own methods and cleaned up double call logic. Login is now recorded in the last login entry no more debug flags are read from the database anymore All options are set via array and not with a single option (was auto login) --- 4dev/database/table/edit_user.sql | 3 + 4dev/tests/ACL/CoreLibsACLLoginTest.php | 7 +- .../CoreLibsACLLogin_database_create_data.sql | 3 + .../Create/CoreLibsCreateSessionTest.php | 21 +- .../edit_tables_cuid_cuuid_update_add.sql | 4 + www/admin/class_test.login.php | 10 +- www/admin/class_test.session.php | 2 +- www/lib/CoreLibs/ACL/Login.php | 1252 ++++++++++------- www/lib/CoreLibs/Create/Session.php | 169 ++- 9 files changed, 944 insertions(+), 527 deletions(-) diff --git a/4dev/database/table/edit_user.sql b/4dev/database/table/edit_user.sql index 2b3c58d7..47c4914c 100644 --- a/4dev/database/table/edit_user.sql +++ b/4dev/database/table/edit_user.sql @@ -37,6 +37,8 @@ CREATE TABLE edit_user ( protected SMALLINT NOT NULL DEFAULT 0, -- is admin user admin SMALLINT NOT NULL DEFAULT 0, + -- force lgout counter + force_logout INT DEFAULT 0, -- last login log last_login TIMESTAMP WITHOUT TIME ZONE, -- login error @@ -74,6 +76,7 @@ COMMENT ON COLUMN edit_user.strict IS 'If too many failed logins user will be lo COMMENT ON COLUMN edit_user.locked IS 'Locked from too many wrong password logins'; COMMENT ON COLUMN edit_user.protected IS 'User can only be chnaged by admin user'; COMMENT ON COLUMN edit_user.admin IS 'If set, this user is SUPER admin'; +COMMENT ON COLUMN edit_user.force_logout IS 'Counter for forced log out, if this one is higher than the session set one the session gets terminated'; COMMENT ON COLUMN edit_user.last_login IS 'Last succesfull login tiemstamp'; COMMENT ON COLUMN edit_user.login_error_count IS 'Number of failed logins, reset on successful login'; COMMENT ON COLUMN edit_user.login_error_date_last IS 'Last login error date'; diff --git a/4dev/tests/ACL/CoreLibsACLLoginTest.php b/4dev/tests/ACL/CoreLibsACLLoginTest.php index de57645f..1582a1ee 100644 --- a/4dev/tests/ACL/CoreLibsACLLoginTest.php +++ b/4dev/tests/ACL/CoreLibsACLLoginTest.php @@ -1185,7 +1185,6 @@ final class CoreLibsACLLoginTest extends TestCase foreach ($session as $session_var => $session_value) { $_SESSION[$session_var] = $session_value; } - /** @var \CoreLibs\ACL\Login&MockObject */ $login_mock = $this->getMockBuilder(\CoreLibs\ACL\Login::class) ->setConstructorArgs([ @@ -1204,7 +1203,7 @@ final class CoreLibsACLLoginTest extends TestCase . 'locale' . DIRECTORY_SEPARATOR, ] ]) - ->onlyMethods(['loginTerminate', 'loginReadPageName', 'loginPrintLogin']) + ->onlyMethods(['loginTerminate', 'loginReadPageName', 'loginPrintLogin', 'loginEnhanceHttpSecurity']) ->getMock(); $login_mock->expects($this->any()) ->method('loginTerminate') @@ -1222,6 +1221,10 @@ final class CoreLibsACLLoginTest extends TestCase ->method('loginPrintLogin') ->willReturnCallback(function () { }); + $login_mock->expects($this->any()) + ->method('loginEnhanceHttpSecurity') + ->willReturnCallback(function () { + }); // if mock_settings: enabled OFF // run DB update and set off diff --git a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql index 686d5a03..e1d1a0cb 100644 --- a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql +++ b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql @@ -581,6 +581,8 @@ CREATE TABLE edit_user ( protected SMALLINT NOT NULL DEFAULT 0, -- is admin user admin SMALLINT NOT NULL DEFAULT 0, + -- forced logout counter + force_logout INT DEFAULT 0, -- last login log last_login TIMESTAMP WITHOUT TIME ZONE, -- login error @@ -697,6 +699,7 @@ CREATE TABLE edit_log ( action_value VARCHAR, -- in action_data action_type VARCHAR, -- in action_data action_error VARCHAR -- in action_data +) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_log.sql -- START: table/edit_log_overflow.sql -- AUTHOR: Clemens Schwaighofer diff --git a/4dev/tests/Create/CoreLibsCreateSessionTest.php b/4dev/tests/Create/CoreLibsCreateSessionTest.php index 2ac833fd..411b610b 100644 --- a/4dev/tests/Create/CoreLibsCreateSessionTest.php +++ b/4dev/tests/Create/CoreLibsCreateSessionTest.php @@ -54,7 +54,9 @@ final class CoreLibsCreateSessionTest extends TestCase 'getSessionId' => '1234abcd4567' ], 'sessionNameGlobals', - false, + [ + 'auto_write_close' => false, + ], ], 'auto write close' => [ 'sessionNameAutoWriteClose', @@ -66,7 +68,9 @@ final class CoreLibsCreateSessionTest extends TestCase 'getSessionId' => '1234abcd4567' ], 'sessionNameAutoWriteClose', - true, + [ + 'auto_write_close' => true, + ], ], ]; } @@ -81,13 +85,14 @@ final class CoreLibsCreateSessionTest extends TestCase * @param string $input * @param array $mock_data * @param string $expected + * @param array $options * @return void */ public function testStartSession( string $input, array $mock_data, string $expected, - ?bool $auto_write_close, + ?array $options, ): void { /** @var \CoreLibs\Create\Session&MockObject $session_mock */ $session_mock = $this->createPartialMock( @@ -174,9 +179,14 @@ final class CoreLibsCreateSessionTest extends TestCase 4, '/^\[SESSION\] Failed to activate session/' ], + 'expired session' => [ + \RuntimeException::class, + 5, + '/^\[SESSION\] Expired session found/' + ], 'not a valid session id returned' => [ \UnexpectedValueException::class, - 5, + 6, '/^\[SESSION\] getSessionId did not return a session id/' ], */ ]; @@ -206,7 +216,8 @@ final class CoreLibsCreateSessionTest extends TestCase $this->expectException($exception); $this->expectExceptionCode($exception_code); $this->expectExceptionMessageMatches($expected_error); - new \CoreLibs\Create\Session($session_name); + // cannot set ini after header sent, plus we are on command line there are no headers + new \CoreLibs\Create\Session($session_name, ['session_strict' => false]); } /** diff --git a/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql b/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql index df2bdbef..ec72ed72 100644 --- a/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql +++ b/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql @@ -7,6 +7,10 @@ ALTER TABLE edit_log ADD http_data JSONB; ALTER TABLE edit_log ADD ip_address JSONB; ALTER TABLE edit_log ADD action_data JSONB; ALTER TABLE edit_log ADD request_scheme VARCHAR; +ALTER TABLE edit_user ADD force_logout INT DEFAULT 0; +COMMENT ON COLUMN edit_user.force_logout IS 'Counter for forced log out, if this one is higher than the session set one the session gets terminated'; +ALTER TABLE edit_user ADD last_login TIMESTAMP WITHOUT TIME ZONE; +COMMENT ON COLUMN edit_user.last_login IS 'Last succesfull login tiemstamp'; -- update set_edit_gneric -- adds the created or updated date tags diff --git a/www/admin/class_test.login.php b/www/admin/class_test.login.php index d153078d..1be459a9 100644 --- a/www/admin/class_test.login.php +++ b/www/admin/class_test.login.php @@ -21,7 +21,10 @@ $SET_SESSION_NAME = EDIT_SESSION_NAME; use CoreLibs\Debug\Support; // init login & backend class -$session = new CoreLibs\Create\Session($SET_SESSION_NAME); +$session = new CoreLibs\Create\Session($SET_SESSION_NAME, [ + 'regenerate' => 'interval', + 'regenerate_interval' => 10, // every 10 seconds +]); $log = new CoreLibs\Logging\Logging([ 'log_folder' => BASE . LOG, 'log_file_id' => $LOG_FILE_ID, @@ -90,6 +93,8 @@ print << HTML; +echo "SESSION ID: " . $session->getSessionIdCall() . "
"; + echo "CHECK PERMISSION: " . ($login->loginCheckPermissions() ? 'OK' : 'BAD') . "
"; echo "IS ADMIN: " . ($login->loginIsAdmin() ? 'OK' : 'BAD') . "
"; echo "MIN ACCESS BASE: " . ($login->loginCheckAccessBase('admin') ? 'OK' : 'BAD') . "
"; @@ -118,8 +123,7 @@ if (isset($login->loginGetAcl()['unit'])) { print "Something went wrong with the login
"; } -echo "
"; - +// echo "
"; // IP check: 'REMOTE_ADDR', 'HTTP_X_FORWARDED_FOR', 'CLIENT_IP' in _SERVER // Agent check: 'HTTP_USER_AGENT' diff --git a/www/admin/class_test.session.php b/www/admin/class_test.session.php index ed9439ef..a8227c78 100644 --- a/www/admin/class_test.session.php +++ b/www/admin/class_test.session.php @@ -146,7 +146,7 @@ $_SESSION['this_will_be_written'] = 'not empty'; // open again with same name $session_name = 'class-test-session'; try { - $session_alt = new Session($session_name, auto_write_close:true); + $session_alt = new Session($session_name, ['auto_write_close' => true]); print "[4 SET] Current session id: " . $session_alt->getSessionId() . "
"; print "[4 SET] Current session auto write close: " . ($session_alt->checkAutoWriteClose() ? 'Yes' : 'No') . "
"; print "[START AGAIN] Current session id: " . $session_alt->getSessionId() . "
"; diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 5b8609de..db937bd8 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -217,6 +217,36 @@ class Login 'path' => '', ]; + // lock status bitmap (smallint, 256) + /** @var int enabled flag */ + public const ENABLED = 1; + /** @var int deleted flag */ + public const DELETED = 2; + /** @var int locked flag */ + public const LOCKED = 4; + /** @var int banned/suspened flag [not implemented] */ + public const BANNED = 8; + /** @var int password reset in progress [not implemented] */ + public const RESET = 16; + /** @var int confirm/paending, eg waiting for confirm of email [not implemented] */ + public const CONFIRM = 32; + /** @var int strict, on error lock */ + public const STRICT = 64; + /** @var int proected, cannot delete */ + public const PROTECTED = 128; + /** @var int master admin flag */ + public const ADMIN = 256; + + /** @var int resync interval time in minutes */ + private const DEFAULT_AUTH_RESYNC_INTERVAL = 5 * 60; + /** @var int the session max garbage collection life time */ + // private const DEFAULT_SESSION_GC_MAXLIFETIME = ; + private int $default_session_gc_maxlifetime; + /** @var int in how many minutes an auth resync is done */ + private int $auth_resync_interval; + /** @var bool set the enhanced header security */ + private bool $header_enhance_security = false; + /** @var \CoreLibs\Logging\Logging logger */ public \CoreLibs\Logging\Logging $log; /** @var \CoreLibs\DB\IO database */ @@ -248,156 +278,19 @@ class Login // attach session class $this->session = $session; + $this->default_session_gc_maxlifetime = (int)ini_get("session.gc_maxlifetime"); + // set and check options if (false === $this->loginSetOptions($options)) { // on failure, exit echo "Could not set options"; $this->loginTerminate('Could not set options', 3000); } - - // string key, msg: string, flag: e (error), o (ok) - $this->login_error_msg = [ - '0' => [ - 'msg' => 'No error', - 'flag' => 'o' - ], - // actually obsolete - '100' => [ - 'msg' => '[EUCUUID] set from GET/POST!', - 'flag' => 'e', - ], - // query errors - '1009' => [ - 'msg' => 'Login query reading failed', - 'flag' => 'e', - ], - // user not found - '1010' => [ - 'msg' => 'Login Failed - Wrong Username or Password', - 'flag' => 'e' - ], - // blowfish password wrong - /* '1011' => [ - 'msg' => 'Login Failed - Wrong Username or Password', - 'flag' => 'e' - ], */ - // fallback md5 password wrong - '1012' => [ - 'msg' => 'Login Failed - Wrong Username or Password', - 'flag' => 'e' - ], - // new password_hash wrong - '1013' => [ - 'msg' => 'Login Failed - Wrong Username or Password', - 'flag' => 'e' - ], - '1101' => [ - 'msg' => 'Login Failed - Login User ID must be validated', - 'flag' => 'e' - ], - '1102' => [ - 'msg' => 'Login Failed - Login User ID is outside valid date range', - 'flag' => 'e' - ], - '102' => [ - 'msg' => 'Login Failed - Please enter username and password', - 'flag' => 'e' - ], - '103' => [ - 'msg' => 'You do not have the rights to access this Page', - 'flag' => 'e' - ], - '104' => [ - 'msg' => 'Login Failed - User not enabled', - 'flag' => 'e' - ], - '105' => [ - 'msg' => 'Login Failed - User is locked', - 'flag' => 'e' - ], - '106' => [ - 'msg' => 'Login Failed - User is deleted', - 'flag' => 'e' - ], - '107' => [ - 'msg' => 'Login Failed - User in locked via date period', - 'flag' => 'e' - ], - '108' => [ - 'msg' => 'Login Failed - User is locked via Login User ID', - 'flag' => 'e' - ], - '109' => [ - 'msg' => 'Check permission query reading failed', - 'flag' => 'e' - ], - // actually this is an illegal user, but I mask it - '220' => [ - 'msg' => 'Password change - The user could not be found', - 'flag' => 'e' - ], - '200' => [ - 'msg' => 'Password change - Please enter username and old password', - 'flag' => 'e' - ], - '201' => [ - 'msg' => 'Password change - The user could not be found', - 'flag' => 'e' - ], - '202' => [ - 'msg' => 'Password change - The old password is not correct', - 'flag' => 'e' - ], - '203' => [ - 'msg' => 'Password change - Please fill out both new password fields', - 'flag' => 'e' - ], - '204' => [ - 'msg' => 'Password change - The new passwords do not match', - 'flag' => 'e' - ], - // we should also not here WHAT is valid - '205' => [ - 'msg' => 'Password change - The new password is not in a valid format', - 'flag' => 'e' - ], - // for OK password change - '300' => [ - 'msg' => 'Password change successful', - 'flag' => 'o' - ], - // this is bad bad error - '9999' => [ - 'msg' => 'Necessary crypt engine could not be found. Login is impossible', - 'flag' => 'e' - ], - ]; - - // read the current edit_access_right list into an array - $q = <<= 0 - ORDER BY - level - SQL; - while (is_array($res = $this->db->dbReturn($q))) { - // level to description format (numeric) - $this->default_acl_list[$res['level']] = [ - 'type' => $res['type'], - 'name' => $res['name'] - ]; - $this->default_acl_list_type[(string)$res['type']] = (int)$res['level']; - } - // write that into the session - $this->session->setMany([ - 'LOGIN_DEFAULT_ACL_LIST' => $this->default_acl_list, - 'LOGIN_DEFAULT_ACL_LIST_TYPE' => $this->default_acl_list_type, - ]); - + // init error array + $this->loginInitErrorMessages(); + // acess right list + $this->loginLoadAccessRightList(); + // log allowed write flags $this->loginSetEditLogWriteTypeAvailable(); // this will be deprecated @@ -425,6 +318,7 @@ class Login } else { $this->log->critical($message, ['code' => $code]); } + // TODO throw error and not exit exit($code); } @@ -577,6 +471,20 @@ class Login } $this->password_forgot = $options['forgot_flow']; + // sync _SESSION acl settings + if ( + !isset($options['auth_resync_interval']) || + !is_numeric($options['auth_resync_interval']) || + $options['auth_resync_interval'] < 0 || + $options['auth_resync_interval'] > $this->default_session_gc_maxlifetime + ) { + // default 5 minutues + $options['auth_resync_interval'] = self::DEFAULT_AUTH_RESYNC_INTERVAL; + } else { + $options['auth_resync_interval'] = (int)$options['auth_resync_interval']; + } + $this->auth_resync_interval = $options['auth_resync_interval']; + // *** LANGUAGE // LANG: LOCALE PATH if (empty($options['locale_path'])) { @@ -631,12 +539,210 @@ class Login $options['site_encoding'] = defined('SITE_ENCODING') && !empty(SITE_ENCODING) ? SITE_ENCODING : 'UTF-8'; } + // set enhancded security flag + if ( + empty($options['enhanced_security']) || + !is_bool($options['enhanced_security']) + ) { + $options['enhanced_security'] = true; + } + $this->header_enhance_security = $options['enhanced_security']; // write array to options $this->options = $options; return true; } + /** + * sets the login error message array + * + * @return void + */ + private function loginInitErrorMessages() + { + // string key, msg: string, flag: e (error), o (ok) + $this->login_error_msg = [ + '0' => [ + 'msg' => 'No error', + 'flag' => 'o' + ], + // actually obsolete + '100' => [ + 'msg' => '[EUCUUID] set from GET/POST!', + 'flag' => 'e', + ], + // query errors + '1009' => [ + 'msg' => 'Login query reading failed', + 'flag' => 'e', + ], + // user not found + '1010' => [ + 'msg' => 'Login Failed - Wrong Username or Password', + 'flag' => 'e' + ], + // general login error + '1011' => [ + 'msg' => 'Login Failed - General authentication error', + 'flag' => 'e' + ], + // fallback md5 password wrong + '1012' => [ + 'msg' => 'Login Failed - Wrong Username or Password', + 'flag' => 'e' + ], + // new password_hash wrong + '1013' => [ + 'msg' => 'Login Failed - Wrong Username or Password', + 'flag' => 'e' + ], + '1101' => [ + 'msg' => 'Login Failed - Login User ID must be validated', + 'flag' => 'e' + ], + '1102' => [ + 'msg' => 'Login Failed - Login User ID is outside valid date range', + 'flag' => 'e' + ], + '102' => [ + 'msg' => 'Login Failed - Please enter username and password', + 'flag' => 'e' + ], + '103' => [ + 'msg' => 'You do not have the rights to access this Page', + 'flag' => 'e' + ], + '104' => [ + 'msg' => 'Login Failed - User not enabled', + 'flag' => 'e' + ], + '105' => [ + 'msg' => 'Login Failed - User is locked', + 'flag' => 'e' + ], + '106' => [ + 'msg' => 'Login Failed - User is deleted', + 'flag' => 'e' + ], + '107' => [ + 'msg' => 'Login Failed - User in locked via date period', + 'flag' => 'e' + ], + '108' => [ + 'msg' => 'Login Failed - User is locked via Login User ID', + 'flag' => 'e' + ], + '109' => [ + 'msg' => 'Check permission query reading failed', + 'flag' => 'e' + ], + '110' => [ + 'msg' => 'Forced logout', + 'flag' => '', + ], + // actually this is an illegal user, but I mask it + '220' => [ + 'msg' => 'Password change - The user could not be found', + 'flag' => 'e' + ], + '200' => [ + 'msg' => 'Password change - Please enter username and old password', + 'flag' => 'e' + ], + '201' => [ + 'msg' => 'Password change - The user could not be found', + 'flag' => 'e' + ], + '202' => [ + 'msg' => 'Password change - The old password is not correct', + 'flag' => 'e' + ], + '203' => [ + 'msg' => 'Password change - Please fill out both new password fields', + 'flag' => 'e' + ], + '204' => [ + 'msg' => 'Password change - The new passwords do not match', + 'flag' => 'e' + ], + // we should also not here WHAT is valid + '205' => [ + 'msg' => 'Password change - The new password is not in a valid format', + 'flag' => 'e' + ], + // for OK password change + '300' => [ + 'msg' => 'Password change successful', + 'flag' => 'o' + ], + // this is bad bad error + '9999' => [ + 'msg' => 'Necessary crypt engine could not be found. Login is impossible', + 'flag' => 'e' + ], + ]; + } + + /** + * loads the access right list from the database + * + * @return void + */ + private function loginLoadAccessRightList(): void + { + // read the current edit_access_right list into an array + $q = <<= 0 + ORDER BY + level + SQL; + while (is_array($res = $this->db->dbReturn($q))) { + // level to description format (numeric) + $this->default_acl_list[$res['level']] = [ + 'type' => $res['type'], + 'name' => $res['name'] + ]; + $this->default_acl_list_type[(string)$res['type']] = (int)$res['level']; + } + // write that into the session + $this->session->setMany([ + 'LOGIN_DEFAULT_ACL_LIST' => $this->default_acl_list, + 'LOGIN_DEFAULT_ACL_LIST_TYPE' => $this->default_acl_list_type, + ]); + } + + /** + * Improves the application's security over HTTP(S) by setting specific headers + * + * @return void + */ + protected function loginEnhanceHttpSecurity(): void + { + // skip if not wanted + if (!$this->header_enhance_security) { + return; + } + // remove exposure of PHP version (at least where possible) + header_remove('X-Powered-By'); + // if the user is signed in + if ($this->permission_okay) { + // prevent clickjacking + header('X-Frame-Options: sameorigin'); + // prevent content sniffing (MIME sniffing) + header('X-Content-Type-Options: nosniff'); + + // disable caching of potentially sensitive data + header('Cache-Control: no-store, no-cache, must-revalidate', true); + header('Expires: Thu, 19 Nov 1981 00:00:00 GMT', true); + header('Pragma: no-cache', true); + } + } + // MARK: validation checks /** @@ -649,6 +755,7 @@ class Login * @param int $locked Locked because of too many invalid passwords * @param int $locked_period Locked because of time period set * @param int $login_user_id_locked Locked from using Login User Id + * @param int $force_logout Force logout counter, if higher than session, permission is false * @return bool */ private function loginValidationCheck( @@ -656,7 +763,8 @@ class Login int $enabled, int $locked, int $locked_period, - int $login_user_id_locked + int $login_user_id_locked, + int $force_logout ): bool { $validation = false; if ($deleted) { @@ -674,6 +782,8 @@ class Login } elseif ($login_user_id_locked) { // user is locked, either set or auto set $this->login_error = 108; + } elseif ($force_logout > $this->session->get('LOGIN_FORCE_LOGOUT')) { + $this->login_error = 110; } else { $validation = true; } @@ -757,7 +867,112 @@ class Login return $login_id_ok; } - // MARK: login user action + /** + * write error data for login errors + * + * @param array $res + * @return void + */ + private function loginWriteLoginError(array $res) + { + if (!$this->login_error) { + return; + } + $login_error_date_first = ''; + if ($res['login_error_count'] == 0) { + $login_error_date_first = ", login_error_date_first = NOW()"; + } + // update login error count for this user + $q = <<db->dbExecParams( + str_replace('{LOGIN_ERROR_SQL}', $login_error_date_first, $q), + [$res['edit_user_id']] + ); + // totally lock the user if error max is reached + if ( + $this->max_login_error_count != -1 && + $res['login_error_count'] + 1 > $this->max_login_error_count + ) { + // do some alert reporting in case this error is too big + // if strict is set, lock this user + // this needs manual unlocking by an admin user + if ($res['strict'] && !in_array($this->username, $this->lock_deny_users)) { + $q = << $res + * @return void + */ + private function loginSetEditUserUidData(array $res) + { + // normal user processing + // set class var and session var + $this->edit_user_id = (int)$res['edit_user_id']; + $this->edit_user_cuid = (string)$res['cuid']; + $this->edit_user_cuuid = (string)$res['cuuid']; + $this->session->setMany([ + 'LOGIN_EUID' => $this->edit_user_id, + 'LOGIN_EUCUID' => $this->edit_user_cuid, + 'LOGIN_EUCUUID' => $this->edit_user_cuuid, + ]); + } + + /** + * check for re-loading of ACL data after a period of time + * or if any of the core session vars is not set + * + * @return void + */ + private function loginAuthResync() + { + if (!$this->session->get('LOGIN_LAST_AUTH_RESYNC')) { + $this->session->set('LOGIN_LAST_AUTH_RESYNC', 0); + } + // reauth on missing session vars and timed out re-sync interval + $mandatory_session_vars = [ + 'LOGIN_USER_NAME', 'LOGIN_GROUP_NAME', 'LOGIN_EUCUID', 'LOGIN_EUCUUID', + 'LOGIN_USER_ADDITIONAL_ACL', 'LOGIN_GROUP_ADDITIONAL_ACL', + 'LOGIN_ADMIN', 'LOGIN_GROUP_ACL_LEVEL', 'LOGIN_PAGES_ACL_LEVEL', 'LOGIN_USER_ACL_LEVEL', + 'LOGIN_UNIT', 'LOGIN_UNIT_DEFAULT_EACUID' + ]; + $force_reauth = false; + foreach ($mandatory_session_vars as $_session_var) { + if (!isset($_SESSION[$_session_var])) { + $force_reauth = true; + break; + } + } + if ( + $this->session->get('LOGIN_LAST_AUTH_RESYNC') + $this->auth_resync_interval <= time() && + $force_reauth == false + ) { + return; + } + if (($res = $this->loginLoadUserData($this->edit_user_cuuid)) === false) { + return; + } + // set the session vars + $this->loginSetSession($res); + } + + // MARK: MAIN LOGIN ACTION /** * if user pressed login button this script is called, @@ -769,6 +984,10 @@ class Login { // if pressed login at least and is not yet loggined in if ($this->edit_user_cuuid || (!$this->login && !$this->login_user_id)) { + // run reload user data based on re-auth timeout, but only if we got a set cuuid + if ($this->edit_user_cuuid) { + $this->loginAuthResync(); + } return; } // if not username AND password where given @@ -778,16 +997,105 @@ class Login $this->permission_okay = false; return; } - // have to get the global stuff here for setting it later - // we have to get the themes in here too + // load user data, abort on error + if (($res = $this->loginLoadUserData()) === false) { + return; + } + // if login errors is half of max errors and the last login error + // was less than 10s ago, forbid any new login try + + // check flow + // - user is enabled + // - user is not locked + // - password is readable + // - encrypted password matches + // - plain password matches + if ( + !$this->loginValidationCheck( + (int)$res['deleted'], + (int)$res['enabled'], + (int)$res['locked'], + (int)$res['locked_period'], + (int)$res['login_user_id_locked'], + (int)$res['force_logout'] + ) + ) { + // error set in method (104, 105, 106, 107, 108) + } elseif ( + empty($this->username) && + !empty($this->login_user_id) && + !$this->loginLoginUserIdCheck( + (int)$res['login_user_id_valid_date'], + (int)$res['login_user_id_revalidate'] + ) + ) { + // check done in loginLoginIdCheck method + // aborts on must revalidate and not valid (date range) + } elseif ( + !empty($this->username) && + !$this->loginPasswordCheck($res['password']) + ) { + // none to be set, set in login password check + // this is not valid password input error here + // all error codes are set in loginPasswordCheck method + // also valid if login_user_id is ok + } else { + // check if the current password is an invalid hash and do a rehash and set password + // $this->debug('LOGIN', 'Hash: '.$res['password'].' -> VERIFY: ' + // .($Password::passwordVerify($this->password, $res['password']) ? 'OK' : 'FAIL') + // .' => HASH: '.(Password::passwordRehashCheck($res['password']) ? 'NEW NEEDED' : 'OK')); + if (Password::passwordRehashCheck($res['password'])) { + // update password hash to new one now + $q = <<db->dbExecParams($q, [ + Password::passwordSet($this->password), + $res['edit_user_id'] + ]); + } + // normal user processing + // set class var and session var + $this->loginSetEditUserUidData($res); + // set the last login time stamp for normal login only (not for reauthenticate) + $this->db->dbExecParams(<<edit_user_id]); + // set the session vars + $this->loginSetSession($res); + } // user was not enabled or other login error + // check for login error and write to the user + $this->loginWriteLoginError($res); + // if there was an login error, show login screen + if ($this->login_error) { + // reset the perm var, to confirm logout + $this->permission_okay = false; + } + } + + /** + * load user data and all connect4ed settings + * + * @param ?string $edit_user_cuuid for re-auth + * @return array|false + */ + private function loginLoadUserData(?string $edit_user_cuuid = null): array|false + { $q = <<login_user_id && empty($this->username))) { + // if login is OK and we have edit_user_cuuid as parameter, then this is internal re-auth + // else login_user_id OR password must be given + if (!empty($edit_user_cuuid)) { + $replace_string = 'eu.cuuid = $1'; + $params = [$this->edit_user_cuuid]; + } elseif (!empty($this->login_user_id) && empty($this->username)) { // check with login id if set and NO username $replace_string = 'eu.login_user_id = $1'; $params = [$this->login_user_id]; @@ -874,366 +1184,288 @@ class Login if (!empty($this->db->dbGetLastError())) { $this->login_error = 1009; $this->permission_okay = false; - return; + return false; } elseif (!is_array($res)) { // username is wrong, but we throw for wrong username // and wrong password the same error - $this->login_error = 1010; + // unless with have edit user cuuid set then we run an general ACL error + if (empty($edit_user_cuuid)) { + $this->login_error = 1010; + } else { + $this->login_error = 1011; + } $this->permission_okay = false; + return false; + } + return $res; + } + + // MARK: login set all session variables + + /** + * set all the _SESSION variables + * + * @param array $res user data loaded query result + * @return void + */ + private function loginSetSession(array $res): void + { + // user has permission to THIS page + if ($this->login_error != 0) { return; } - // if login errors is half of max errors and the last login error - // was less than 10s ago, forbid any new login try - - // check flow - // - user is enabled - // - user is not locked - // - password is readable - // - encrypted password matches - // - plain password matches + // set the dit group id + $edit_group_id = $res["edit_group_id"]; + $edit_user_id = (int)$res['edit_user_id']; + // update last revalidate flag if ( - !$this->loginValidationCheck( - (int)$res['deleted'], - (int)$res['enabled'], - (int)$res['locked'], - (int)$res['locked_period'], - (int)$res['login_user_id_locked'] - ) + !empty($res['login_user_id']) && + !empty($this->username) && !empty($this->password) ) { - // error set in method (104, 105, 106, 107, 108) - } elseif ( - empty($this->username) && - !empty($this->login_user_id) && - !$this->loginLoginUserIdCheck( - (int)$res['login_user_id_valid_date'], - (int)$res['login_user_id_revalidate'] - ) + $q = <<db->dbExecParams($q, [$edit_user_id]); + } + $locale = $res['locale'] ?? 'en'; + $encoding = $res['encoding'] ?? 'UTF-8'; + $this->session->setMany([ + // now set all session vars and read page permissions + // DEBUG flag is deprecated + // 'DEBUG_ALL' => $this->db->dbBoolean($res['debug']), + // 'DB_DEBUG' => $this->db->dbBoolean($res['db_debug']), + // login timestamp + 'LOGIN_LAST_AUTH_RESYNC' => time(), + // current forced logout counter + 'LOGIN_FORCE_LOGOUT' => $res['force_logout'], + // general info for user logged in + 'LOGIN_USER_NAME' => $res['username'], + 'LOGIN_EMAIL' => $res['email'], + 'LOGIN_ADMIN' => $res['admin'], + 'LOGIN_GROUP_NAME' => $res['edit_group_name'], + 'LOGIN_USER_ACL_LEVEL' => $res['user_level'], + 'LOGIN_USER_ACL_TYPE' => $res['user_type'], + 'LOGIN_USER_ADDITIONAL_ACL' => Json::jsonConvertToArray($res['user_additional_acl']), + 'LOGIN_GROUP_ACL_LEVEL' => $res['group_level'], + 'LOGIN_GROUP_ACL_TYPE' => $res['group_type'], + 'LOGIN_GROUP_ADDITIONAL_ACL' => Json::jsonConvertToArray($res['group_additional_acl']), + // deprecated TEMPLATE setting + // 'TEMPLATE' => $res['template'] ? $res['template'] : '', + 'LOGIN_HEADER_COLOR' => !empty($res['second_header_color']) ? + $res['second_header_color'] : + $res['first_header_color'], + // LANGUAGE/LOCALE/ENCODING: + // 'LOGIN_LANG' => $locale, + 'DEFAULT_CHARSET' => $encoding, + 'DEFAULT_LOCALE' => $locale . '.' . strtoupper($encoding), + 'DEFAULT_LANG' => $locale . '_' . strtolower(str_replace('-', '', $encoding)) + ]); + // missing # before, this is for legacy data, will be deprecated + if ( + !empty($this->session->get('LOGIN_HEADER_COLOR')) && + preg_match("/^[\dA-Fa-f]{6,8}$/", $this->session->get('LOGIN_HEADER_COLOR')) ) { - // check done in loginLoginIdCheck method - // aborts on must revalidate and not valid (date range) - } elseif ( - !empty($this->username) && - !$this->loginPasswordCheck($res['password']) - ) { - // none to be set, set in login password check - // this is not valid password input error here - // all error codes are set in loginPasswordCheck method - // also valid if login_user_id is ok - } else { - // check if the current password is an invalid hash and do a rehash and set password - // $this->debug('LOGIN', 'Hash: '.$res['password'].' -> VERIFY: ' - // .($Password::passwordVerify($this->password, $res['password']) ? 'OK' : 'FAIL') - // .' => HASH: '.(Password::passwordRehashCheck($res['password']) ? 'NEW NEEDED' : 'OK')); - if (Password::passwordRehashCheck($res['password'])) { - // update password hash to new one now - $q = <<db->dbExecParams($q, [ - Password::passwordSet($this->password), - $res['edit_user_id'] - ]); - } - // normal user processing - // set class var and session var - $this->edit_user_id = (int)$res['edit_user_id']; - $this->edit_user_cuid = (string)$res['cuid']; - $this->edit_user_cuuid = (string)$res['cuuid']; - $this->session->setMany([ - 'LOGIN_EUID' => $this->edit_user_id, // DEPRECATED - 'LOGIN_EUCUID' => $this->edit_user_cuid, - 'LOGIN_EUCUUID' => $this->edit_user_cuuid, - ]); - // check if user is okay - $this->loginCheckPermissions(); - if ($this->login_error == 0) { - // set the dit group id - $edit_group_id = $res["edit_group_id"]; - // update last revalidate flag - if ( - !empty($res['login_user_id']) && - !empty($this->username) && !empty($this->password) - ) { - $q = <<db->dbExecParams($q, [$this->edit_user_id]); - } - $locale = $res['locale'] ?? 'en'; - $encoding = $res['encoding'] ?? 'UTF-8'; - $this->session->setMany([ - // now set all session vars and read page permissions - // DEBUG flag is deprecated - // 'DEBUG_ALL' => $this->db->dbBoolean($res['debug']), - // 'DB_DEBUG' => $this->db->dbBoolean($res['db_debug']), - // general info for user logged in - 'LOGIN_USER_NAME' => $res['username'], - 'LOGIN_ADMIN' => $res['admin'], - 'LOGIN_GROUP_NAME' => $res['edit_group_name'], - 'LOGIN_USER_ACL_LEVEL' => $res['user_level'], - 'LOGIN_USER_ACL_TYPE' => $res['user_type'], - 'LOGIN_USER_ADDITIONAL_ACL' => Json::jsonConvertToArray($res['user_additional_acl']), - 'LOGIN_GROUP_ACL_LEVEL' => $res['group_level'], - 'LOGIN_GROUP_ACL_TYPE' => $res['group_type'], - 'LOGIN_GROUP_ADDITIONAL_ACL' => Json::jsonConvertToArray($res['group_additional_acl']), - // deprecated TEMPLATE setting - // 'TEMPLATE' => $res['template'] ? $res['template'] : '', - 'LOGIN_HEADER_COLOR' => !empty($res['second_header_color']) ? - $res['second_header_color'] : - $res['first_header_color'], - // LANGUAGE/LOCALE/ENCODING: - // 'LOGIN_LANG' => $locale, - 'DEFAULT_CHARSET' => $encoding, - 'DEFAULT_LOCALE' => $locale . '.' . strtoupper($encoding), - 'DEFAULT_LANG' => $locale . '_' . strtolower(str_replace('-', '', $encoding)) - ]); - // missing # before, this is for legacy data, will be deprecated - if ( - !empty($this->session->get('LOGIN_HEADER_COLOR')) && - preg_match("/^[\dA-Fa-f]{6,8}$/", $this->session->get('LOGIN_HEADER_COLOR')) - ) { - $this->session->set('LOGIN_HEADER_COLOR', '#' . $this->session->get('LOGIN_HEADER_COLOR')); - } - // TODO: make sure that header color is valid: - // # + 6 hex - // # + 8 hex (alpha) - // rgb(), rgba(), hsl(), hsla() - // rgb: nnn.n for each - // hsl: nnn.n for first, nnn.n% for 2nd, 3rd - // Check\Colors::validateColor() - // reset any login error count for this user - if ($res['login_error_count'] > 0) { - $q = <<db->dbExecParams($q, [$this->edit_user_id]); - } - $edit_page_ids = []; - $pages = []; - $pages_acl = []; - // set pages access - $q = <<db->dbReturnParams($q, [$edit_group_id]))) { - // page id array for sub data readout - $edit_page_ids[$res['edit_page_id']] = $res['cuid']; - // create the array for pages - $pages[$res['cuid']] = [ - 'edit_page_id' => $res['edit_page_id'], - 'cuid' => $res['cuid'], - 'cuuid' => $res['cuuid'], - // for reference of content data on a differen page - 'content_alias_uid' => $res['content_alias_uid'], - 'hostname' => $res['hostname'], - 'filename' => $res['filename'], - 'page_name' => $res['edit_page_name'], - 'order' => $res['edit_page_order'], - 'menu' => $res['menu'], - 'popup' => $res['popup'], - 'popup_x' => $res['popup_x'], - 'popup_y' => $res['popup_y'], - 'online' => $res['online'], - 'acl_level' => $res['level'], - 'acl_type' => $res['type'], - 'query' => [], - 'visible' => [] - ]; - // make reference filename -> level - $pages_acl[$res['filename']] = $res['level']; - } // for each page - // edit page id params - $params = ['{' . join(',', array_keys($edit_page_ids)) . '}']; - // get the visible groups for all pages and write them to the pages - $q = <<db->dbReturnParams($q, $params))) { - $pages[$edit_page_ids[$res['edit_page_id']]]['visible'][$res['name']] = $res['flag']; - } - // get the same for the query strings - $q = <<db->dbReturnParams($q, $params))) { - $pages[$edit_page_ids[$res['edit_page_id']]]['query'][] = [ - 'name' => $res['name'], - 'value' => $res['value'], - 'dynamic' => $res['dynamic'] - ]; - } - // get the page content and add them to the page - $q = <<db->dbReturnParams($q, $params))) { - $pages[$edit_page_ids[$res['edit_page_id']]]['content'][$res['uid']] = [ - 'name' => $res['name'], - 'uid' => $res['uid'], - 'cuid' => $res['cuid'], - 'cuuid' => $res['cuuid'], - 'online' => $res['online'], - 'order' => $res['order_number'], - // access name and level - 'acl_type' => $res['type'], - 'acl_level' => $res['level'] - ]; - } - // write back the pages data to the output array - $this->session->setMany([ - 'LOGIN_PAGES' => $pages, - 'LOGIN_PAGES_ACL_LEVEL' => $pages_acl, - ]); - // load the edit_access user rights - $q = <<db->dbReturnParams($q, [$this->edit_user_id]))) { - // read edit access data fields and drop them into the unit access array - $q_sub = <<db->dbReturnParams($q_sub, [$res['edit_access_id']]))) { - $ea_data[$res_sub['name']] = $res_sub['value']; - } - // build master unit array - $unit_access_cuid[$res['cuid']] = [ - 'id' => (int)$res['edit_access_id'], // DEPRECATED - 'cuuid' => $res['cuuid'], - 'acl_level' => $res['level'], - 'acl_type' => $res['type'], - 'name' => $res['name'], - 'uid' => $res['uid'], - 'color' => $res['color'], - 'default' => $res['edit_default'], - 'additional_acl' => Json::jsonConvertToArray($res['additional_acl']), - 'data' => $ea_data - ]; - $unit_access_eaid[$res['edit_access_id']] = [ - 'cuid' => $res['cuid'], - ]; - // set the default unit - if ($res['edit_default']) { - $this->session->set('LOGIN_UNIT_DEFAULT_EAID', (int)$res['edit_access_id']); // DEPRECATED - $this->session->set('LOGIN_UNIT_DEFAULT_EACUID', (int)$res['cuid']); - } - $unit_uid_lookup[$res['uid']] = $res['edit_access_id']; // DEPRECATED - $unit_cuid_lookup[$res['uid']] = $res['cuid']; - // sub arrays for simple access - array_push($eaid, $res['edit_access_id']); - array_push($eacuid, $res['cuid']); - $unit_acl[$res['cuid']] = $res['level']; - } - $this->session->setMany([ - 'LOGIN_UNIT_UID' => $unit_uid_lookup, // DEPRECATED - 'LOGIN_UNIT_CUID' => $unit_cuid_lookup, - 'LOGIN_UNIT' => $unit_access_cuid, - 'LOGIN_UNIT_LEGACY' => $unit_access_eaid, // DEPRECATED - 'LOGIN_UNIT_ACL_LEVEL' => $unit_acl, - 'LOGIN_EAID' => $eaid, // DEPRECATED - 'LOGIN_EACUID' => $eacuid, - ]); - } // user has permission to THIS page - } // user was not enabled or other login error - if ($this->login_error && is_array($res)) { - $login_error_date_first = ''; - if ($res['login_error_count'] == 0) { - $login_error_date_first = ", login_error_date_first = NOW()"; - } - // update login error count for this user + $this->session->set('LOGIN_HEADER_COLOR', '#' . $this->session->get('LOGIN_HEADER_COLOR')); + } + // TODO: make sure that header color is valid: + // # + 6 hex + // # + 8 hex (alpha) + // rgb(), rgba(), hsl(), hsla() + // rgb: nnn.n for each + // hsl: nnn.n for first, nnn.n% for 2nd, 3rd + // Check\Colors::validateColor() + // reset any login error count for this user + if ($res['login_error_count'] > 0) { $q = <<db->dbExecParams( - str_replace('{LOGIN_ERROR_SQL}', $login_error_date_first, $q), - [$res['edit_user_id']] - ); - // totally lock the user if error max is reached - if ( - $this->max_login_error_count != -1 && - $res['login_error_count'] + 1 > $this->max_login_error_count - ) { - // do some alert reporting in case this error is too big - // if strict is set, lock this user - // this needs manual unlocking by an admin user - if ($res['strict'] && !in_array($this->username, $this->lock_deny_users)) { - $q = <<db->dbExecParams($q, [$edit_user_id]); + } + $edit_page_ids = []; + $pages = []; + $pages_acl = []; + // set pages access + $q = <<db->dbReturnParams($q, [$edit_group_id]))) { + // page id array for sub data readout + $edit_page_ids[$res['edit_page_id']] = $res['cuid']; + // create the array for pages + $pages[$res['cuid']] = [ + 'edit_page_id' => $res['edit_page_id'], + 'cuid' => $res['cuid'], + 'cuuid' => $res['cuuid'], + // for reference of content data on a differen page + 'content_alias_uid' => $res['content_alias_uid'], + 'hostname' => $res['hostname'], + 'filename' => $res['filename'], + 'page_name' => $res['edit_page_name'], + 'order' => $res['edit_page_order'], + 'menu' => $res['menu'], + 'popup' => $res['popup'], + 'popup_x' => $res['popup_x'], + 'popup_y' => $res['popup_y'], + 'online' => $res['online'], + 'acl_level' => $res['level'], + 'acl_type' => $res['type'], + 'query' => [], + 'visible' => [] + ]; + // make reference filename -> level + $pages_acl[$res['filename']] = $res['level']; + } // for each page + // edit page id params + $params = ['{' . join(',', array_keys($edit_page_ids)) . '}']; + // get the visible groups for all pages and write them to the pages + $q = <<db->dbReturnParams($q, $params))) { + $pages[$edit_page_ids[$res['edit_page_id']]]['visible'][$res['name']] = $res['flag']; + } + // get the same for the query strings + $q = <<db->dbReturnParams($q, $params))) { + $pages[$edit_page_ids[$res['edit_page_id']]]['query'][] = [ + 'name' => $res['name'], + 'value' => $res['value'], + 'dynamic' => $res['dynamic'] + ]; + } + // get the page content and add them to the page + $q = <<db->dbReturnParams($q, $params))) { + $pages[$edit_page_ids[$res['edit_page_id']]]['content'][$res['uid']] = [ + 'name' => $res['name'], + 'uid' => $res['uid'], + 'cuid' => $res['cuid'], + 'cuuid' => $res['cuuid'], + 'online' => $res['online'], + 'order' => $res['order_number'], + // access name and level + 'acl_type' => $res['type'], + 'acl_level' => $res['level'] + ]; + } + // write back the pages data to the output array + $this->session->setMany([ + 'LOGIN_PAGES' => $pages, + 'LOGIN_PAGES_ACL_LEVEL' => $pages_acl, + ]); + // load the edit_access user rights + $q = <<db->dbReturnParams($q, [$edit_user_id]))) { + // read edit access data fields and drop them into the unit access array + $q_sub = <<db->dbReturnParams($q_sub, [$res['edit_access_id']]))) { + $ea_data[$res_sub['name']] = $res_sub['value']; } + // build master unit array + $unit_access_cuid[$res['cuid']] = [ + 'id' => (int)$res['edit_access_id'], // DEPRECATED + 'cuuid' => $res['cuuid'], + 'acl_level' => $res['level'], + 'acl_type' => $res['type'], + 'name' => $res['name'], + 'uid' => $res['uid'], + 'color' => $res['color'], + 'default' => $res['edit_default'], + 'additional_acl' => Json::jsonConvertToArray($res['additional_acl']), + 'data' => $ea_data + ]; + $unit_access_eaid[$res['edit_access_id']] = [ + 'cuid' => $res['cuid'], + ]; + // set the default unit + $this->session->setMany([ + 'LOGIN_UNIT_DEFAULT_EAID' => null, + 'LOGIN_UNIT_DEFAULT_EACUID' => null, + ]); + if ($res['edit_default']) { + $this->session->set('LOGIN_UNIT_DEFAULT_EAID', (int)$res['edit_access_id']); // DEPRECATED + $this->session->set('LOGIN_UNIT_DEFAULT_EACUID', (int)$res['cuid']); + } + $unit_uid_lookup[$res['uid']] = $res['edit_access_id']; // DEPRECATED + $unit_cuid_lookup[$res['uid']] = $res['cuid']; + // sub arrays for simple access + array_push($eaid, $res['edit_access_id']); + array_push($eacuid, $res['cuid']); + $unit_acl[$res['cuid']] = $res['level']; } - // if there was an login error, show login screen - if ($this->login_error) { - // reset the perm var, to confirm logout - $this->permission_okay = false; - } + $this->session->setMany([ + 'LOGIN_UNIT_UID' => $unit_uid_lookup, // DEPRECATED + 'LOGIN_UNIT_CUID' => $unit_cuid_lookup, + 'LOGIN_UNIT' => $unit_access_cuid, + 'LOGIN_UNIT_LEGACY' => $unit_access_eaid, // DEPRECATED + 'LOGIN_UNIT_ACL_LEVEL' => $unit_acl, + 'LOGIN_EAID' => $eaid, // DEPRECATED + 'LOGIN_EACUID' => $eacuid, + ]); } // MARK: login set ACL @@ -1361,7 +1593,7 @@ class Login $this->acl['show_ea_extra'] = false; } // set the default edit access - $this->acl['default_edit_access'] = $_SESSION['UNIT_DEFAULT'] ?? null; + $this->acl['default_edit_access'] = $_SESSION['LOGIN_UNIT_DEFAULT_EACUID']; // integrate the type acl list, but only for the keyword -> level $this->acl['min'] = $this->default_acl_list_type; // set the full acl list too (lookup level number and get level data) @@ -2198,7 +2430,7 @@ HTML; // row 2 $_SERVER["REMOTE_ADDR"] ?? null, Json::jsonConvertArrayTo([ - 'REMOTE_ADDR' => $_SERVER["REMOTE_ADDR"], + 'REMOTE_ADDR' => $_SERVER["REMOTE_ADDR"] ?? null, 'HTTP_X_FORWARDED_FOR' => !empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']) : [], @@ -2262,7 +2494,7 @@ HTML; // **** PUBLIC INTERNAL // ************************************************************************* - // MARK: PUBLIC LOGIN CALL + // MARK: MASTER PUBLIC LOGIN CALL /** * Main call that needs to be run to actaully check for login @@ -2355,10 +2587,12 @@ HTML; // if username & password & !$euid start login $this->loginLoginUser(); - // checks if $euid given check if user is okay for that side + // checks if $euid given check if user is okay for that site $this->loginCheckPermissions(); - // logsout user + // logout user $this->loginLogoutUser(); + // set headers for enhanced security + $this->loginEnhanceHttpSecurity(); // ** LANGUAGE SET AFTER LOGIN ** $this->loginSetLocale(); // load translator @@ -2731,7 +2965,7 @@ HTML; } $q = <<login_error = 103; } // set all the internal vars - $this->edit_user_id = (int)$res['edit_user_id']; - $this->edit_user_cuid = (string)$res['cuid']; - $this->edit_user_cuuid = (string)$res['cuuid']; - $this->session->setMany([ - 'LOGIN_EUID' => $this->edit_user_id, // DEPRECATED - 'LOGIN_EUCUID' => $this->edit_user_cuid, - 'LOGIN_EUCUUID' => $this->edit_user_cuuid, - ]); + $this->loginSetEditUserUidData($res); // if called from public, so we can check if the permissions are ok return $this->permission_okay; } diff --git a/www/lib/CoreLibs/Create/Session.php b/www/lib/CoreLibs/Create/Session.php index 6873ee27..0f2e4a38 100644 --- a/www/lib/CoreLibs/Create/Session.php +++ b/www/lib/CoreLibs/Create/Session.php @@ -21,21 +21,107 @@ class Session private string $session_id = ''; /** @var bool flag auto write close */ private bool $auto_write_close = false; + /** @var string regenerate option, default never */ + private string $regenerate = 'never'; + /** @var int regenerate interval either 1 to 100 for random or 0 to 3600 for interval */ + private int $regenerate_interval = 0; + + /** @var array allowed session id regenerate (rotate) options */ + private const ALLOWED_REGENERATE_OPTIONS = ['none', 'random', 'interval']; + /** @var int default random interval */ + public const DEFAULT_REGENERATE_RANDOM = 100; + /** @var int default rotate internval in minutes */ + public const DEFAULT_REGENERATE_INTERVAL = 5 * 60; + /** @var int maximum time for regenerate interval is one hour */ + public const MAX_REGENERATE_INTERAL = 60 * 60; /** * init a session, if array is empty or array does not have session_name set * then no auto init is run * * @param string $session_name if set and not empty, will start session + * @param array $options */ - public function __construct(string $session_name, bool $auto_write_close = false) - { + public function __construct( + string $session_name, + array $options = [] + ) { + $this->setOptions($options); $this->initSession($session_name); - $this->auto_write_close = $auto_write_close; } // MARK: private methods + /** + * set session class options + * + * @param array $options + * @return void + */ + private function setOptions(array $options): void + { + if ( + !isset($options['auto_write_close']) || + !is_bool($options['auto_write_close']) + ) { + $options['auto_write_close'] = false; + } + $this->auto_write_close = $options['auto_write_close']; + if ( + !isset($options['session_strict']) || + !is_bool($options['session_strict']) + ) { + $options['session_strict'] = true; + } + // set strict options, on not started sessiononly + if ( + $options['session_strict'] && + $this->getSessionStatus() === PHP_SESSION_NONE + ) { + // use cookies to store session IDs + ini_set('session.use_cookies', 1); + // use cookies only (do not send session IDs in URLs) + ini_set('session.use_only_cookies', 1); + // do not send session IDs in URLs + ini_set('session.use_trans_sid', 0); + } + // session regenerate id options + if ( + empty($options['regenerate']) || + !in_array($options['regenerate'], self::ALLOWED_REGENERATE_OPTIONS) + ) { + $options['regenerate'] = 'never'; + } + $this->regenerate = (string)$options['regenerate']; + // for regenerate: 'random' (default 100) + // regenerate_interval must be between (1 = always) and 100 (1 in 100) + // for regenerate: 'interval' (default 5min) + // regenerate_interval must be 0 = always, to 3600 (every hour) + if ( + $options['regenerate'] == 'random' && + ( + !isset($options['regenerate_interval']) || + !is_numeric($options['regenerate_interval']) || + $options['regenerate_interval'] < 0 || + $options['regenerate_interval'] > 100 + ) + ) { + $options['regenerate_interval'] = self::DEFAULT_REGENERATE_RANDOM; + } + if ( + $options['regenerate'] == 'interval' && + ( + !isset($options['regenerate_interval']) || + !is_numeric($options['regenerate_interval']) || + $options['regenerate_interval'] < 1 || + $options['regenerate_interval'] > self::MAX_REGENERATE_INTERAL + ) + ) { + $options['regenerate_interval'] = self::DEFAULT_REGENERATE_INTERVAL; + } + $this->regenerate_interval = (int)($options['regenerate_interval'] ?? 0); + } + /** * Start session * startSession should be called for complete check @@ -72,6 +158,72 @@ class Session return false; } + // MARK: regenerate session + + /** + * auto rotate session id + * + * @return void + * @throws \RuntimeException failure to regenerate session id + * @throws \UnexpectedValueException failed to get new session id + * @throws \RuntimeException failed to set new sesson id + * @throws \UnexpectedValueException new session id generated does not match the new set one + */ + private function sessionRegenerateSessionId() + { + // never + if ($this->regenerate == 'never') { + return; + } + // regenerate + if ( + !( + // is not session obsolete + empty($_SESSION['SESSION_REGENERATE_OBSOLETE']) && + ( + ( + // random + $this->regenerate == 'random' && + mt_rand(1, $this->regenerate_interval) == 1 + ) || ( + // interval type + $this->regenerate == 'interval' && + ($_SESSION['SESSION_REGENERATE_TIMESTAMP'] ?? 0) + $this->regenerate_interval < time() + ) + ) + ) + ) { + return; + } + // Set current session to expire in 1 minute + $_SESSION['SESSION_REGENERATE_OBSOLETE'] = true; + $_SESSION['SESSION_REGENERATE_EXPIRES'] = time() + 60; + $_SESSION['SESSION_REGENERATE_TIMESTAMP'] = time(); + // Create new session without destroying the old one + if (session_regenerate_id(false) === false) { + throw new \RuntimeException('[SESSION] Session id regeneration failed', 1); + } + // Grab current session ID and close both sessions to allow other scripts to use them + if (false === ($new_session_id = $this->getSessionIdCall())) { + throw new \UnexpectedValueException('[SESSION] getSessionIdCall did not return a session id', 2); + } + $this->writeClose(); + // Set session ID to the new one, and start it back up again + if (($get_new_session_id = session_id($new_session_id)) === false) { + throw new \RuntimeException('[SESSION] set session_id failed', 3); + } + if ($get_new_session_id != $new_session_id) { + throw new \UnexpectedValueException('[SESSION] new session id does not match the new set one', 4); + } + $this->session_id = $new_session_id; + $this->startSessionCall(); + // Don't want this one to expire + unset($_SESSION['SESSION_REGENERATE_OBSOLETE']); + unset($_SESSION['SESSION_REGENERATE_EXPIRES']); + } + + // MARK: session validation + /** * check if session name is valid * @@ -151,6 +303,13 @@ class Session if (!$this->checkActiveSession()) { throw new \RuntimeException('[SESSION] Failed to activate session', 5); } + if ( + !empty($_SESSION['SESSION_REGENERATE_OBSOLETE']) && + !empty($_SESSION['SESSION_REGENERATE_EXPIRES']) && $_SESSION['SESSION_REGENERATE_EXPIRES'] < time() + ) { + $this->sessionDestroy(); + throw new \RuntimeException('[SESSION] Expired session found', 6); + } } elseif ($session_name != $this->getSessionName()) { throw new \UnexpectedValueException( '[SESSION] Another session exists with a different name: ' . $this->getSessionName(), @@ -159,10 +318,12 @@ class Session } // check session id if (false === ($session_id = $this->getSessionIdCall())) { - throw new \UnexpectedValueException('[SESSION] getSessionId did not return a session id', 6); + throw new \UnexpectedValueException('[SESSION] getSessionIdCall did not return a session id', 7); } // set session id $this->session_id = $session_id; + // run session id re-create from time to time + $this->sessionRegenerateSessionId(); // if flagged auto close, write close session if ($this->auto_write_close) { $this->writeClose(); From d1c461143178d57c72a573a8f5b49cccdf226892 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 11 Dec 2024 21:06:59 +0900 Subject: [PATCH 022/105] Indent fix for ACL Login --- www/lib/CoreLibs/ACL/Login.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index db937bd8..6fb11dd4 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -260,10 +260,10 @@ class Login * constructor, does ALL, opens db, works through connection checks, * finishes itself * - * @param \CoreLibs\DB\IO $db Database connection class - * @param \CoreLibs\Logging\Logging $log Logging class - * @param \CoreLibs\Create\Session $session Session interface class - * @param array $options Login ACL settings + * @param \CoreLibs\DB\IO $db Database connection class + * @param \CoreLibs\Logging\Logging $log Logging class + * @param \CoreLibs\Create\Session $session Session interface class + * @param array $options Login ACL settings */ public function __construct( \CoreLibs\DB\IO $db, From 7d4c9724fe61297f745cdf7fda606574e751baad Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 11 Dec 2024 21:10:00 +0900 Subject: [PATCH 023/105] Fix session options argument declaration for phpstan --- www/lib/CoreLibs/Create/Session.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/lib/CoreLibs/Create/Session.php b/www/lib/CoreLibs/Create/Session.php index 0f2e4a38..b3f9f772 100644 --- a/www/lib/CoreLibs/Create/Session.php +++ b/www/lib/CoreLibs/Create/Session.php @@ -40,7 +40,7 @@ class Session * then no auto init is run * * @param string $session_name if set and not empty, will start session - * @param array $options + * @param array{auto_write_close?:bool,session_strict?:bool,regenerate?:string,regenerate_interval?:int} $options */ public function __construct( string $session_name, @@ -55,7 +55,7 @@ class Session /** * set session class options * - * @param array $options + * @param array{auto_write_close?:bool,session_strict?:bool,regenerate?:string,regenerate_interval?:int} $options * @return void */ private function setOptions(array $options): void From e793c3975b651d775d9df92ed96ea09f075814bb Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 12 Dec 2024 12:02:49 +0900 Subject: [PATCH 024/105] Change all db now to clock_timestamp for triggers --- 4dev/database/function/set_date.sql | 4 +- 4dev/database/function/set_edit_generic.sql | 4 +- 4dev/database/function/set_generic_uid.sql | 4 +- 4dev/database/function/update_function.sql | 19 ------ .../CoreLibsACLLogin_database_create_data.sql | 4 +- www/lib/CoreLibs/ACL/Login.php | 20 ------ www/lib/CoreLibs/ACL/LoginUserStatus.php | 68 +++++++++++++++++++ 7 files changed, 76 insertions(+), 47 deletions(-) delete mode 100644 4dev/database/function/update_function.sql create mode 100644 www/lib/CoreLibs/ACL/LoginUserStatus.php diff --git a/4dev/database/function/set_date.sql b/4dev/database/function/set_date.sql index 408688a1..13a51743 100644 --- a/4dev/database/function/set_date.sql +++ b/4dev/database/function/set_date.sql @@ -5,9 +5,9 @@ RETURNS TRIGGER AS $$ BEGIN IF TG_OP = 'INSERT' THEN - NEW.date_created := 'now'; + NEW.date_created := clock_timestamp(); ELSIF TG_OP = 'UPDATE' THEN - NEW.date_updated := 'now'; + NEW.date_updated := clock_timestamp(); END IF; RETURN NEW; END; diff --git a/4dev/database/function/set_edit_generic.sql b/4dev/database/function/set_edit_generic.sql index 4ec68683..be7519b9 100644 --- a/4dev/database/function/set_edit_generic.sql +++ b/4dev/database/function/set_edit_generic.sql @@ -7,11 +7,11 @@ DECLARE random_length INT = 25; -- that should be long enough BEGIN IF TG_OP = 'INSERT' THEN - NEW.date_created := 'now'; + NEW.date_created := clock_timestamp(); NEW.cuid := random_string(random_length); NEW.cuuid := gen_random_uuid(); ELSIF TG_OP = 'UPDATE' THEN - NEW.date_updated := 'now'; + NEW.date_updated := clock_timestamp(); END IF; RETURN NEW; END; diff --git a/4dev/database/function/set_generic_uid.sql b/4dev/database/function/set_generic_uid.sql index 71c27275..8ad24467 100644 --- a/4dev/database/function/set_generic_uid.sql +++ b/4dev/database/function/set_generic_uid.sql @@ -8,12 +8,12 @@ DECLARE random_length INT = 32; -- long for massive data BEGIN IF TG_OP = 'INSERT' THEN - NEW.date_created := 'now'; + NEW.date_created := clock_timestamp(); IF NEW.uid IS NULL THEN NEW.uid := random_string(random_length); END IF; ELSIF TG_OP = 'UPDATE' THEN - NEW.date_updated := 'now'; + NEW.date_updated := clock_timestamp(); END IF; RETURN NEW; END; diff --git a/4dev/database/function/update_function.sql b/4dev/database/function/update_function.sql deleted file mode 100644 index 80a3dccf..00000000 --- a/4dev/database/function/update_function.sql +++ /dev/null @@ -1,19 +0,0 @@ --- adds the created or updated date tags - --- OLD, DEPRECATED, use set_generic.sql - --- CREATE OR REPLACE FUNCTION set_generic() --- RETURNS TRIGGER AS --- $$ --- BEGIN --- IF TG_OP = 'INSERT' THEN --- NEW.date_created := clock_timestamp(); --- NEW.user_created := current_user; --- ELSIF TG_OP = 'UPDATE' THEN --- NEW.date_updated := clock_timestamp(); --- NEW.user_updated := current_user; --- END IF; --- RETURN NEW; --- END; --- $$ --- LANGUAGE 'plpgsql'; diff --git a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql index e1d1a0cb..7caaf1c3 100644 --- a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql +++ b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql @@ -30,11 +30,11 @@ DECLARE random_length INT = 12; -- that should be long enough BEGIN IF TG_OP = 'INSERT' THEN - NEW.date_created := 'now'; + NEW.date_created := clock_timestamp(); NEW.cuid := random_string(random_length); NEW.cuuid := gen_random_uuid(); ELSIF TG_OP = 'UPDATE' THEN - NEW.date_updated := 'now'; + NEW.date_updated := clock_timestamp(); END IF; RETURN NEW; END; diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 6fb11dd4..365f5e69 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -217,26 +217,6 @@ class Login 'path' => '', ]; - // lock status bitmap (smallint, 256) - /** @var int enabled flag */ - public const ENABLED = 1; - /** @var int deleted flag */ - public const DELETED = 2; - /** @var int locked flag */ - public const LOCKED = 4; - /** @var int banned/suspened flag [not implemented] */ - public const BANNED = 8; - /** @var int password reset in progress [not implemented] */ - public const RESET = 16; - /** @var int confirm/paending, eg waiting for confirm of email [not implemented] */ - public const CONFIRM = 32; - /** @var int strict, on error lock */ - public const STRICT = 64; - /** @var int proected, cannot delete */ - public const PROTECTED = 128; - /** @var int master admin flag */ - public const ADMIN = 256; - /** @var int resync interval time in minutes */ private const DEFAULT_AUTH_RESYNC_INTERVAL = 5 * 60; /** @var int the session max garbage collection life time */ diff --git a/www/lib/CoreLibs/ACL/LoginUserStatus.php b/www/lib/CoreLibs/ACL/LoginUserStatus.php new file mode 100644 index 00000000..fb791abb --- /dev/null +++ b/www/lib/CoreLibs/ACL/LoginUserStatus.php @@ -0,0 +1,68 @@ + + */ + public static function getMap() + { + return array_flip((new \ReflectionClass(static::class))->getConstants()); + } + + /** + * Returns the descriptive role names + * + * @return string[] + */ + public static function getNames() + { + + return array_keys((new \ReflectionClass(static::class))->getConstants()); + } + + /** + * Returns the numerical role values + * + * @return int[] + */ + public static function getValues() + { + return array_values((new \ReflectionClass(static::class))->getConstants()); + } +} + +// __END__ From 540269e61ffd180e4cabb47baf6e155a0e1f2798 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 12 Dec 2024 19:04:21 +0900 Subject: [PATCH 025/105] Fix update script for now to clock_timestamp --- .../edit_tables_cuid_cuuid_update_add.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql b/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql index ec72ed72..255f0b56 100644 --- a/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql +++ b/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql @@ -22,11 +22,11 @@ DECLARE random_length INT = 25; -- that should be long enough BEGIN IF TG_OP = 'INSERT' THEN - NEW.date_created := 'now'; + NEW.date_created := clock_timestamp(); NEW.cuid := random_string(random_length); NEW.cuuid := gen_random_uuid(); ELSIF TG_OP = 'UPDATE' THEN - NEW.date_updated := 'now'; + NEW.date_updated := clock_timestamp(); END IF; RETURN NEW; END; From 1e90bb677e502482cf7b57aaf90effcdf20939d5 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 12 Dec 2024 21:07:17 +0900 Subject: [PATCH 026/105] Fix Symmetric encryption with wrong key handling - static call encrypt: do not check pre set key - indirect call: set new if key is different --- ...oreLibsSecuritySymmetricEncryptionTest.php | 167 +++++++++++++++++- .../CoreLibs/Security/SymmetricEncryption.php | 8 +- 2 files changed, 172 insertions(+), 3 deletions(-) diff --git a/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php index d3f502af..d7486501 100644 --- a/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php +++ b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php @@ -56,7 +56,24 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $decrypted, 'Class call', ); + } + /** + * test encrypt/decrypt produce correct output + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptDecryptSuccess + * @testdox encrypt/decrypt indirect $input must be $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testEncryptDecryptSuccessIndirect(string $input, string $expected): void + { + $key = CreateKey::generateRandomKey(); // test indirect $encrypted = SymmetricEncryption::getInstance($key)->encrypt($input); $decrypted = SymmetricEncryption::getInstance($key)->decrypt($encrypted); @@ -65,7 +82,24 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $decrypted, 'Class Instance call', ); + } + /** + * test encrypt/decrypt produce correct output + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptDecryptSuccess + * @testdox encrypt/decrypt static $input must be $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testEncryptDecryptSuccessStatic(string $input, string $expected): void + { + $key = CreateKey::generateRandomKey(); // test static $encrypted = SymmetricEncryption::encryptKey($input, $key); $decrypted = SymmetricEncryption::decryptKey($encrypted, $key); @@ -114,13 +148,51 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $crypt = new SymmetricEncryption($key); $encrypted = $crypt->encrypt($input); $this->expectExceptionMessage($exception_message); - $crypt->setKey($key); + $crypt->setKey($wrong_key); $crypt->decrypt($encrypted); + } + + /** + * Test decryption with wrong key + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptFailed + * @testdox decrypt indirect with wrong key $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testEncryptFailedIndirect(string $input, string $exception_message): void + { + $key = CreateKey::generateRandomKey(); + $wrong_key = CreateKey::generateRandomKey(); // class instance $encrypted = SymmetricEncryption::getInstance($key)->encrypt($input); $this->expectExceptionMessage($exception_message); SymmetricEncryption::getInstance($wrong_key)->decrypt($encrypted); + } + + /** + * Test decryption with wrong key + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptFailed + * @testdox decrypt static with wrong key $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testEncryptFailedStatic(string $input, string $exception_message): void + { + $key = CreateKey::generateRandomKey(); + $wrong_key = CreateKey::generateRandomKey(); // class static $encrypted = SymmetricEncryption::encryptKey($input, $key); @@ -190,6 +262,56 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase SymmetricEncryption::decryptKey($encrypted, $key); } + /** + * test invalid key provided to decrypt or encrypt + * + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerWrongKey + * @testdox wrong key indirect $key throws $exception_message [$_dataName] + * + * @param string $key + * @param string $exception_message + * @return void + */ + public function testWrongKeyIndirect(string $key, string $exception_message): void + { + $enc_key = CreateKey::generateRandomKey(); + + // class instance + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::getInstance($key)->encrypt('test'); + // we must encrypt valid thing first so we can fail with the wrong key + $encrypted = SymmetricEncryption::getInstance($enc_key)->encrypt('test'); + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::getInstance($key)->decrypt($encrypted); + } + + /** + * test invalid key provided to decrypt or encrypt + * + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerWrongKey + * @testdox wrong key static $key throws $exception_message [$_dataName] + * + * @param string $key + * @param string $exception_message + * @return void + */ + public function testWrongKeyStatic(string $key, string $exception_message): void + { + $enc_key = CreateKey::generateRandomKey(); + + // class static + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::encryptKey('test', $key); + // we must encrypt valid thing first so we can fail with the wrong key + $encrypted = SymmetricEncryption::encryptKey('test', $enc_key); + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::decryptKey($encrypted, $key); + } + /** * Undocumented function * @@ -232,6 +354,49 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $this->expectExceptionMessage($exception_message); SymmetricEncryption::decryptKey($input, $key); } + + /** + * Undocumented function + * + * @covers ::decrypt + * @dataProvider providerWrongCiphertext + * @testdox too short ciphertext indirect $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testWrongCiphertextIndirect(string $input, string $exception_message): void + { + $key = CreateKey::generateRandomKey(); + + // class instance + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::getInstance($key)->decrypt($input); + + // class static + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::decryptKey($input, $key); + } + + /** + * Undocumented function + * + * @covers ::decrypt + * @dataProvider providerWrongCiphertext + * @testdox too short ciphertext static $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testWrongCiphertextStatic(string $input, string $exception_message): void + { + $key = CreateKey::generateRandomKey(); + // class static + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::decryptKey($input, $key); + } } // __END__ diff --git a/www/lib/CoreLibs/Security/SymmetricEncryption.php b/www/lib/CoreLibs/Security/SymmetricEncryption.php index c12f4b3f..2f8fb75e 100644 --- a/www/lib/CoreLibs/Security/SymmetricEncryption.php +++ b/www/lib/CoreLibs/Security/SymmetricEncryption.php @@ -49,7 +49,11 @@ class SymmetricEncryption */ public static function getInstance(string|null $key = null): self { - if (empty(self::$instance)) { + // new if no instsance or key is different + if ( + empty(self::$instance) || + self::$instance->key != $key + ) { self::$instance = new self($key); } return self::$instance; @@ -130,7 +134,7 @@ class SymmetricEncryption */ private function encryptData(string $message, ?string $key): string { - if (empty($this->key) || $key === null) { + if ($key === null) { throw new \UnexpectedValueException('Key not set'); } $key = $this->createKey($key); From c13934de99cdf63c5f260e702adf441552d77dcb Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 12 Dec 2024 21:09:41 +0900 Subject: [PATCH 027/105] Fix for wrong key handling in Symmetric encryption --- ...oreLibsSecuritySymmetricEncryptionTest.php | 167 +++++++++++++++++- .../CoreLibs/Security/SymmetricEncryption.php | 8 +- 2 files changed, 172 insertions(+), 3 deletions(-) diff --git a/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php index d3f502af..d7486501 100644 --- a/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php +++ b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php @@ -56,7 +56,24 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $decrypted, 'Class call', ); + } + /** + * test encrypt/decrypt produce correct output + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptDecryptSuccess + * @testdox encrypt/decrypt indirect $input must be $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testEncryptDecryptSuccessIndirect(string $input, string $expected): void + { + $key = CreateKey::generateRandomKey(); // test indirect $encrypted = SymmetricEncryption::getInstance($key)->encrypt($input); $decrypted = SymmetricEncryption::getInstance($key)->decrypt($encrypted); @@ -65,7 +82,24 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $decrypted, 'Class Instance call', ); + } + /** + * test encrypt/decrypt produce correct output + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptDecryptSuccess + * @testdox encrypt/decrypt static $input must be $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testEncryptDecryptSuccessStatic(string $input, string $expected): void + { + $key = CreateKey::generateRandomKey(); // test static $encrypted = SymmetricEncryption::encryptKey($input, $key); $decrypted = SymmetricEncryption::decryptKey($encrypted, $key); @@ -114,13 +148,51 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $crypt = new SymmetricEncryption($key); $encrypted = $crypt->encrypt($input); $this->expectExceptionMessage($exception_message); - $crypt->setKey($key); + $crypt->setKey($wrong_key); $crypt->decrypt($encrypted); + } + + /** + * Test decryption with wrong key + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptFailed + * @testdox decrypt indirect with wrong key $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testEncryptFailedIndirect(string $input, string $exception_message): void + { + $key = CreateKey::generateRandomKey(); + $wrong_key = CreateKey::generateRandomKey(); // class instance $encrypted = SymmetricEncryption::getInstance($key)->encrypt($input); $this->expectExceptionMessage($exception_message); SymmetricEncryption::getInstance($wrong_key)->decrypt($encrypted); + } + + /** + * Test decryption with wrong key + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptFailed + * @testdox decrypt static with wrong key $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testEncryptFailedStatic(string $input, string $exception_message): void + { + $key = CreateKey::generateRandomKey(); + $wrong_key = CreateKey::generateRandomKey(); // class static $encrypted = SymmetricEncryption::encryptKey($input, $key); @@ -190,6 +262,56 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase SymmetricEncryption::decryptKey($encrypted, $key); } + /** + * test invalid key provided to decrypt or encrypt + * + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerWrongKey + * @testdox wrong key indirect $key throws $exception_message [$_dataName] + * + * @param string $key + * @param string $exception_message + * @return void + */ + public function testWrongKeyIndirect(string $key, string $exception_message): void + { + $enc_key = CreateKey::generateRandomKey(); + + // class instance + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::getInstance($key)->encrypt('test'); + // we must encrypt valid thing first so we can fail with the wrong key + $encrypted = SymmetricEncryption::getInstance($enc_key)->encrypt('test'); + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::getInstance($key)->decrypt($encrypted); + } + + /** + * test invalid key provided to decrypt or encrypt + * + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerWrongKey + * @testdox wrong key static $key throws $exception_message [$_dataName] + * + * @param string $key + * @param string $exception_message + * @return void + */ + public function testWrongKeyStatic(string $key, string $exception_message): void + { + $enc_key = CreateKey::generateRandomKey(); + + // class static + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::encryptKey('test', $key); + // we must encrypt valid thing first so we can fail with the wrong key + $encrypted = SymmetricEncryption::encryptKey('test', $enc_key); + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::decryptKey($encrypted, $key); + } + /** * Undocumented function * @@ -232,6 +354,49 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $this->expectExceptionMessage($exception_message); SymmetricEncryption::decryptKey($input, $key); } + + /** + * Undocumented function + * + * @covers ::decrypt + * @dataProvider providerWrongCiphertext + * @testdox too short ciphertext indirect $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testWrongCiphertextIndirect(string $input, string $exception_message): void + { + $key = CreateKey::generateRandomKey(); + + // class instance + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::getInstance($key)->decrypt($input); + + // class static + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::decryptKey($input, $key); + } + + /** + * Undocumented function + * + * @covers ::decrypt + * @dataProvider providerWrongCiphertext + * @testdox too short ciphertext static $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testWrongCiphertextStatic(string $input, string $exception_message): void + { + $key = CreateKey::generateRandomKey(); + // class static + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::decryptKey($input, $key); + } } // __END__ diff --git a/www/lib/CoreLibs/Security/SymmetricEncryption.php b/www/lib/CoreLibs/Security/SymmetricEncryption.php index c12f4b3f..2f8fb75e 100644 --- a/www/lib/CoreLibs/Security/SymmetricEncryption.php +++ b/www/lib/CoreLibs/Security/SymmetricEncryption.php @@ -49,7 +49,11 @@ class SymmetricEncryption */ public static function getInstance(string|null $key = null): self { - if (empty(self::$instance)) { + // new if no instsance or key is different + if ( + empty(self::$instance) || + self::$instance->key != $key + ) { self::$instance = new self($key); } return self::$instance; @@ -130,7 +134,7 @@ class SymmetricEncryption */ private function encryptData(string $message, ?string $key): string { - if (empty($this->key) || $key === null) { + if ($key === null) { throw new \UnexpectedValueException('Key not set'); } $key = $this->createKey($key); From f966209e0a4276c0b8132a05f1dac78b0eca2cc5 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 12 Dec 2024 21:20:09 +0900 Subject: [PATCH 028/105] phpstan param declration fix for ACL Login user status --- www/lib/CoreLibs/ACL/LoginUserStatus.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/lib/CoreLibs/ACL/LoginUserStatus.php b/www/lib/CoreLibs/ACL/LoginUserStatus.php index fb791abb..ddc5a84f 100644 --- a/www/lib/CoreLibs/ACL/LoginUserStatus.php +++ b/www/lib/CoreLibs/ACL/LoginUserStatus.php @@ -36,7 +36,7 @@ final class LoginUserStatus /** * Returns an array mapping the numerical role values to their descriptive names * - * @return array + * @return array */ public static function getMap() { From e9cfdb4bf07e6f36cad85c874876255de1cc1572 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 13 Dec 2024 09:35:54 +0900 Subject: [PATCH 029/105] Remove all deprecated tests --- www/admin/class_test.convert.colors.php | 5 +++-- www/admin/class_test.html_build.element.php | 4 ++-- www/admin/class_test.lang.php | 4 ++-- www/admin/class_test.readenvfile.php | 6 ++++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/www/admin/class_test.convert.colors.php b/www/admin/class_test.convert.colors.php index 6754aa51..c1ef6c2f 100644 --- a/www/admin/class_test.convert.colors.php +++ b/www/admin/class_test.convert.colors.php @@ -131,7 +131,8 @@ try { } catch (\LengthException $e) { print "*Exception: " . $e->getMessage() . "
" . print_r($e, true) . "

"; } -print "
"; + +/* print "
"; print "

LEGACY

"; // B(valid) $rgb = [50, 20, 30]; @@ -173,7 +174,7 @@ $hsb = [0, 0, 5]; print "S::COLOR hsb->rgb: $hsb[0], $hsb[1], $hsb[2]: " . DgS::printAr(SetVarType::setArray( Colors::hsb2rgb($hsb[0], $hsb[1], $hsb[2]) - )) . "
"; + )) . "
"; */ print "
"; diff --git a/www/admin/class_test.html_build.element.php b/www/admin/class_test.html_build.element.php index 6296fbd6..303388b3 100644 --- a/www/admin/class_test.html_build.element.php +++ b/www/admin/class_test.html_build.element.php @@ -74,8 +74,8 @@ print "EL_O:
" . print_r($el_o, true) . "
"; echo "
"; print "buildHtml():
" . htmlentities($el_o->buildHtml()) . "
"; -echo "
"; -print "phfo(\$el_o):
" . htmlentities($el_o::printHtmlFromObject($el_o, true)) . "
"; +/* echo "
"; +print "phfo(\$el_o):
" . htmlentities($el_o::printHtmlFromObject($el_o, true)) . "
"; */ echo "
"; print "phfa(\$el_list):
" . htmlentities($el_o::buildHtmlFromList($el_o_list, true)) . "
"; diff --git a/www/admin/class_test.lang.php b/www/admin/class_test.lang.php index 43a217e3..7a73199b 100644 --- a/www/admin/class_test.lang.php +++ b/www/admin/class_test.lang.php @@ -48,8 +48,7 @@ $locale = 'en.UTF-8'; $locale_info = L10n::parseLocale($locale); print "[" . $locale . "] INFO: " . Support::printAr($locale_info) . "
"; -echo "
AUTO DETECT
"; - +/* echo "
AUTO DETECT
"; // DEPRECATED // $get_locale = Language\GetLocale::setLocale(); // print "[AUTO, DEPRECATED]: " . Support::printAr($get_locale) . "
"; @@ -103,6 +102,7 @@ $get_locale = Language\GetLocale::setLocaleFromSession( BASE . INCLUDES . LOCALE ); print "[SESSION SET INVALID]: " . Support::printAr($get_locale) . "
"; + */ // try to load non existing echo "
NEW TYPE
"; diff --git a/www/admin/class_test.readenvfile.php b/www/admin/class_test.readenvfile.php index 9449bee8..2988dbcc 100644 --- a/www/admin/class_test.readenvfile.php +++ b/www/admin/class_test.readenvfile.php @@ -34,10 +34,12 @@ print '

' . $PAGE_NAME . '

'; print "ALREADY from config.php: " . \CoreLibs\Debug\Support::printAr($_ENV) . "
"; +// This is now in \gullevek\dotenv\DotEnv::readEnvFile(...) + // test .env in local -$status = \CoreLibs\Get\DotEnv::readEnvFile('.', 'test.env'); +/* $status = \CoreLibs\Get\DotEnv::readEnvFile('.', 'test.env'); print "test.env: STATUS: " . $status . "
"; -print "AFTER reading test.env file: " . \CoreLibs\Debug\Support::printAr($_ENV) . "
"; +print "AFTER reading test.env file: " . \CoreLibs\Debug\Support::printAr($_ENV) . "
"; */ print ""; // ;; From e349613d60dbbb44a75cb9cb54b664ec72c2d49e Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 13 Dec 2024 10:17:28 +0900 Subject: [PATCH 030/105] phpunit updates Add testsuits for default run Fix wording in testdox add a fallback in the Debugging Support test suit --- 4dev/tests/Debug/CoreLibsDebugSupportTest.php | 3 +++ 4dev/tests/Logging/CoreLibsLoggingErrorMessagesTest.php | 2 +- phpunit.xml | 5 +++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/4dev/tests/Debug/CoreLibsDebugSupportTest.php b/4dev/tests/Debug/CoreLibsDebugSupportTest.php index 74ed67ed..7b6ed197 100644 --- a/4dev/tests/Debug/CoreLibsDebugSupportTest.php +++ b/4dev/tests/Debug/CoreLibsDebugSupportTest.php @@ -568,6 +568,9 @@ final class CoreLibsDebugSupportTest extends TestCase 'assert expected 12' ); break; + default: + $this->assertTrue(true, 'Default fallback as true'); + break; } } diff --git a/4dev/tests/Logging/CoreLibsLoggingErrorMessagesTest.php b/4dev/tests/Logging/CoreLibsLoggingErrorMessagesTest.php index ad2d606c..5e9c3ac6 100644 --- a/4dev/tests/Logging/CoreLibsLoggingErrorMessagesTest.php +++ b/4dev/tests/Logging/CoreLibsLoggingErrorMessagesTest.php @@ -10,7 +10,7 @@ use CoreLibs\Logging\Logger\Level; /** * Test class for Logging * @coversDefaultClass \CoreLibs\Logging\ErrorMessages - * @testdox \CoreLibs\Logging\ErrorMEssages method tests + * @testdox \CoreLibs\Logging\ErrorMessages method tests */ final class CoreLibsLoggingErrorMessagesTest extends TestCase { diff --git a/phpunit.xml b/phpunit.xml index dcaf3022..90ff0695 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -5,4 +5,9 @@ convertDeprecationsToExceptions="true" bootstrap="4dev/tests/bootstrap.php" > + + + 4dev/tests + + From ab52bf59b5fd3b51c71485316c2a208eb7459ffb Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 13 Dec 2024 10:38:24 +0900 Subject: [PATCH 031/105] phan/phpstan fixes --- www/admin/class_test.convert.colors.php | 3 +-- www/admin/class_test.lang.php | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/www/admin/class_test.convert.colors.php b/www/admin/class_test.convert.colors.php index c1ef6c2f..4b5f68db 100644 --- a/www/admin/class_test.convert.colors.php +++ b/www/admin/class_test.convert.colors.php @@ -18,7 +18,7 @@ require 'config.php'; $LOG_FILE_ID = 'classTest-convert-colors'; ob_end_flush(); -use CoreLibs\Convert\Colors; +// use CoreLibs\Convert\Colors; use CoreLibs\Convert\Color\Color; use CoreLibs\Convert\Color\Coordinates; use CoreLibs\Debug\Support as DgS; @@ -29,7 +29,6 @@ $log = new CoreLibs\Logging\Logging([ 'log_file_id' => $LOG_FILE_ID, 'log_per_date' => true, ]); -$color_class = 'CoreLibs\Convert\Colors'; /** * print out a color block with info diff --git a/www/admin/class_test.lang.php b/www/admin/class_test.lang.php index 7a73199b..8deb2a2d 100644 --- a/www/admin/class_test.lang.php +++ b/www/admin/class_test.lang.php @@ -34,18 +34,18 @@ use CoreLibs\Debug\Support; echo "
LIST LOCALES
"; $locale = 'en_US.UTF-8'; -$locales = L10n::listLocales($locale); +$locales = Language\L10n::listLocales($locale); print "[" . $locale . "] LOCALES: " . Support::printAr($locales) . "
"; $locale = 'en.UTF-8'; -$locales = L10n::listLocales($locale); +$locales = Language\L10n::listLocales($locale); print "[" . $locale . "] LOCALES: " . Support::printAr($locales) . "
"; echo "
PARSE LOCAL
"; $locale = 'en_US.UTF-8'; -$locale_info = L10n::parseLocale($locale); +$locale_info = Language\L10n::parseLocale($locale); print "[" . $locale . "] INFO: " . Support::printAr($locale_info) . "
"; $locale = 'en.UTF-8'; -$locale_info = L10n::parseLocale($locale); +$locale_info = Language\L10n::parseLocale($locale); print "[" . $locale . "] INFO: " . Support::printAr($locale_info) . "
"; /* echo "
AUTO DETECT
"; From d16b920966fb1b80c95b6faa6f4049da6ef44ca5 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 13 Dec 2024 11:29:37 +0900 Subject: [PATCH 032/105] Update arrayReturnMatchinKeyOnly description --- www/lib/CoreLibs/Combined/ArrayHandler.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/lib/CoreLibs/Combined/ArrayHandler.php b/www/lib/CoreLibs/Combined/ArrayHandler.php index 38727c1f..bd9a3b58 100644 --- a/www/lib/CoreLibs/Combined/ArrayHandler.php +++ b/www/lib/CoreLibs/Combined/ArrayHandler.php @@ -527,7 +527,9 @@ class ArrayHandler } /** - * From the array with key -> anything values return only the matching entries from key list + * From the array with key -> mixed values, + * return only the entries where the key matches the key given in the key list parameter + * * key list is a list[string] * if key list is empty, return array as is * From 1cf4fdf31a6df41227e290015cb1dbdd399c8d4b Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 13 Dec 2024 11:37:11 +0900 Subject: [PATCH 033/105] Fix column named for edit_log to eu prefixed as eucuid and eucuuid --- .../edit_tables_cuid_cuuid_update_add.sql | 8 ++++++-- www/lib/CoreLibs/ACL/Login.php | 8 ++++---- www/lib/CoreLibs/Admin/Backend.php | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql b/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql index 255f0b56..9917d9a7 100644 --- a/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql +++ b/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql @@ -1,7 +1,7 @@ -- 20241203: update edit tables ALTER TABLE edit_generic ADD cuuid UUID DEFAULT gen_random_uuid(); -ALTER TABLE edit_log ADD ecuid VARCHAR; -ALTER TABLE edit_log ADD ecuuid VARCHAR; +ALTER TABLE edit_log ADD eucuid VARCHAR; +ALTER TABLE edit_log ADD eucuuid VARCHAR; ALTER TABLE edit_log ADD action_sub_id VARCHAR; ALTER TABLE edit_log ADD http_data JSONB; ALTER TABLE edit_log ADD ip_address JSONB; @@ -32,3 +32,7 @@ BEGIN END; $$ LANGUAGE 'plpgsql'; + +-- +ALTER TABLE edit_log RENAME ecuid TO eucuid; +ALTER TABLE edit_log RENAME ecuuid TO eucuuid; diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 365f5e69..6302b122 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -79,7 +79,7 @@ class Login private ?int $edit_user_id; /** @var ?string the user cuid (note will be super seeded with uuid v4 later) */ private ?string $edit_user_cuid; - /** @var ?string UUIDv4, will superseed the ecuid and replace euid as login id */ + /** @var ?string UUIDv4, will superseed the eucuid and replace euid as login id */ private ?string $edit_user_cuuid; /** @var string _GET/_POST loginUserId parameter for non password login */ private string $login_user_id = ''; @@ -2371,7 +2371,7 @@ HTML; } $q = <<session->get('LOGIN_PAGES'); } - // MARK: logged in uid(pk)/cuid/ecuuid + // MARK: logged in uid(pk)/eucuid/eucuuid /** * Get the current set EUID (edit user id) @@ -2938,7 +2938,7 @@ HTML; if (empty($this->edit_user_cuuid)) { return $this->permission_okay; } - // euid must match ecuid and ecuuid + // euid must match eucuid and eucuuid // bail for previous wrong page match, eg if method is called twice if ($this->login_error == 103) { return $this->permission_okay; diff --git a/www/lib/CoreLibs/Admin/Backend.php b/www/lib/CoreLibs/Admin/Backend.php index 6f02cd25..04e084ff 100644 --- a/www/lib/CoreLibs/Admin/Backend.php +++ b/www/lib/CoreLibs/Admin/Backend.php @@ -358,7 +358,7 @@ class Backend } $q = << Date: Fri, 13 Dec 2024 11:41:14 +0900 Subject: [PATCH 034/105] ecuid name fix in test file --- 4dev/database/table/edit_log.sql | 4 ++-- .../ACL/database/CoreLibsACLLogin_database_create_data.sql | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/4dev/database/table/edit_log.sql b/4dev/database/table/edit_log.sql index 5c492849..7edd8da5 100644 --- a/4dev/database/table/edit_log.sql +++ b/4dev/database/table/edit_log.sql @@ -10,8 +10,8 @@ CREATE TABLE edit_log ( edit_log_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, euid INT, -- this is a foreign key, but I don't nedd to reference to it FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL, - ecuid VARCHAR, - ecuuid UUID, -- this is the one we want to use, full UUIDv4 from the edit user table + eucuid VARCHAR, + eucuuid UUID, -- this is the one we want to use, full UUIDv4 from the edit user table -- date_created equal, but can be overridden event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, -- session ID if set diff --git a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql index 7caaf1c3..1b6855b5 100644 --- a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql +++ b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql @@ -652,8 +652,8 @@ CREATE TABLE edit_log ( edit_log_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, euid INT, -- this is a foreign key, but I don't nedd to reference to it FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL, - ecuid VARCHAR, - ecuuid UUID, -- this is the one we want to use, full UUIDv4 from the edit user table + eucuid VARCHAR, + eucuuid UUID, -- this is the one we want to use, full UUIDv4 from the edit user table -- date_created equal, but can be overridden event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, -- session ID if set From 3bd21c75d88ec90ff474175ae7325361e4c13f98 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 13 Dec 2024 13:58:56 +0900 Subject: [PATCH 035/105] Make the font-size for ACL Login template a bit smaller 1.5em was too large, 1.3em is better --- .../edit_tables_cuid_cuuid_update_add.sql | 4 +--- www/lib/CoreLibs/ACL/Login.php | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql b/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql index 9917d9a7..5bbb9c49 100644 --- a/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql +++ b/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql @@ -33,6 +33,4 @@ END; $$ LANGUAGE 'plpgsql'; --- -ALTER TABLE edit_log RENAME ecuid TO eucuid; -ALTER TABLE edit_log RENAME ecuuid TO eucuuid; +-- END -- diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 6302b122..0b6c55ad 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -2138,10 +2138,10 @@ body { text-align: right; } input.login-input-text { - font-size: 1.5em; + font-size: 1.3em; } button.login-button { - font-size: 1.5em; + font-size: 1.3em; } .login-visible { visibility: visible; From 711b3bfe97556418b3b9ef29b3776b3986f21c28 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 13 Dec 2024 18:45:27 +0900 Subject: [PATCH 036/105] Remove E_STRICT from error reporting, it is deprecated --- www/admin/class_test.admin.backend.php | 2 +- www/admin/class_test.array.php | 2 +- www/admin/class_test.autoloader.php | 2 +- www/admin/class_test.byte.php | 2 +- www/admin/class_test.check.colors.php | 2 +- www/admin/class_test.class-calls.php | 2 +- www/admin/class_test.config.direct.php | 2 +- www/admin/class_test.config.link.php | 2 +- www/admin/class_test.convert.colors.php | 2 +- www/admin/class_test.create_email.php | 2 +- www/admin/class_test.datetime.php | 2 +- www/admin/class_test.db.convert-placeholder.php | 2 +- www/admin/class_test.db.dbReturn.php | 2 +- www/admin/class_test.db.php | 2 +- www/admin/class_test.db.query-placeholder.php | 2 +- www/admin/class_test.db.single.php | 2 +- www/admin/class_test.db.types.php | 2 +- www/admin/class_test.debug.php | 2 +- www/admin/class_test.email.php | 2 +- www/admin/class_test.encoding.php | 2 +- www/admin/class_test.encryption.php | 2 +- www/admin/class_test.error_msg.php | 2 +- www/admin/class_test.file.php | 2 +- www/admin/class_test.hash.php | 2 +- www/admin/class_test.html.php | 2 +- www/admin/class_test.html_build.block.php | 2 +- www/admin/class_test.html_build.element.php | 2 +- www/admin/class_test.html_build.replace.php | 2 +- www/admin/class_test.image.php | 2 +- www/admin/class_test.json.php | 2 +- www/admin/class_test.lang.php | 2 +- www/admin/class_test.logging.php | 2 +- www/admin/class_test.login.php | 2 +- www/admin/class_test.math.php | 2 +- www/admin/class_test.memoryusage.php | 2 +- www/admin/class_test.mime.php | 2 +- www/admin/class_test.output.form.php | 2 +- www/admin/class_test.password.php | 2 +- www/admin/class_test.php | 2 +- www/admin/class_test.phpv.php | 2 +- www/admin/class_test.randomkey.php | 2 +- www/admin/class_test.readenvfile.php | 2 +- www/admin/class_test.runningtime.php | 2 +- www/admin/class_test.session.php | 2 +- www/admin/class_test.session.read.php | 2 +- www/admin/class_test.smarty.php | 2 +- www/admin/class_test.strings.php | 2 +- www/admin/class_test.system.php | 2 +- www/admin/class_test.token.php | 2 +- www/admin/class_test.uids.php | 2 +- www/admin/class_test.url-requests.curl.php | 2 +- www/admin/class_test.varistype.php | 2 +- www/admin/subfolder/class_test.config.direct.php | 2 +- www/admin/test_edit_base.php | 2 +- 54 files changed, 54 insertions(+), 54 deletions(-) diff --git a/www/admin/class_test.admin.backend.php b/www/admin/class_test.admin.backend.php index 492bbd8e..86402b18 100644 --- a/www/admin/class_test.admin.backend.php +++ b/www/admin/class_test.admin.backend.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.array.php b/www/admin/class_test.array.php index bb64ea46..6491ee59 100644 --- a/www/admin/class_test.array.php +++ b/www/admin/class_test.array.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.autoloader.php b/www/admin/class_test.autoloader.php index 173ce5af..97c53180 100644 --- a/www/admin/class_test.autoloader.php +++ b/www/admin/class_test.autoloader.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); // basic class test file diff --git a/www/admin/class_test.byte.php b/www/admin/class_test.byte.php index 3105c09f..cc17ce2f 100644 --- a/www/admin/class_test.byte.php +++ b/www/admin/class_test.byte.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.check.colors.php b/www/admin/class_test.check.colors.php index 705ea6bf..73fb8658 100644 --- a/www/admin/class_test.check.colors.php +++ b/www/admin/class_test.check.colors.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.class-calls.php b/www/admin/class_test.class-calls.php index b184069b..dd5d52ce 100644 --- a/www/admin/class_test.class-calls.php +++ b/www/admin/class_test.class-calls.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.config.direct.php b/www/admin/class_test.config.direct.php index 9ce49225..7e824bda 100644 --- a/www/admin/class_test.config.direct.php +++ b/www/admin/class_test.config.direct.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.config.link.php b/www/admin/class_test.config.link.php index f19acbfe..d980e56c 100644 --- a/www/admin/class_test.config.link.php +++ b/www/admin/class_test.config.link.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.convert.colors.php b/www/admin/class_test.convert.colors.php index 4b5f68db..462c7ef1 100644 --- a/www/admin/class_test.convert.colors.php +++ b/www/admin/class_test.convert.colors.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.create_email.php b/www/admin/class_test.create_email.php index ece9c410..40643f50 100644 --- a/www/admin/class_test.create_email.php +++ b/www/admin/class_test.create_email.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.datetime.php b/www/admin/class_test.datetime.php index 18379ea8..e0374599 100644 --- a/www/admin/class_test.datetime.php +++ b/www/admin/class_test.datetime.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.db.convert-placeholder.php b/www/admin/class_test.db.convert-placeholder.php index e95586ad..6504326f 100644 --- a/www/admin/class_test.db.convert-placeholder.php +++ b/www/admin/class_test.db.convert-placeholder.php @@ -7,7 +7,7 @@ declare(strict_types=1); // turn on all error reporting -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.db.dbReturn.php b/www/admin/class_test.db.dbReturn.php index 17166558..85d56793 100644 --- a/www/admin/class_test.db.dbReturn.php +++ b/www/admin/class_test.db.dbReturn.php @@ -7,7 +7,7 @@ declare(strict_types=1); // turn on all error reporting -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.db.php b/www/admin/class_test.db.php index 1326a023..4f1bfb82 100644 --- a/www/admin/class_test.db.php +++ b/www/admin/class_test.db.php @@ -7,7 +7,7 @@ declare(strict_types=1); // turn on all error reporting -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.db.query-placeholder.php b/www/admin/class_test.db.query-placeholder.php index c934962a..87b57971 100644 --- a/www/admin/class_test.db.query-placeholder.php +++ b/www/admin/class_test.db.query-placeholder.php @@ -7,7 +7,7 @@ declare(strict_types=1); // turn on all error reporting -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.db.single.php b/www/admin/class_test.db.single.php index 102c3ce7..e3d631c4 100644 --- a/www/admin/class_test.db.single.php +++ b/www/admin/class_test.db.single.php @@ -7,7 +7,7 @@ declare(strict_types=1); // turn on all error reporting -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.db.types.php b/www/admin/class_test.db.types.php index 8f42aa66..d3c74038 100644 --- a/www/admin/class_test.db.types.php +++ b/www/admin/class_test.db.types.php @@ -7,7 +7,7 @@ declare(strict_types=1); // turn on all error reporting -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.debug.php b/www/admin/class_test.debug.php index bb3dd190..25427152 100644 --- a/www/admin/class_test.debug.php +++ b/www/admin/class_test.debug.php @@ -12,7 +12,7 @@ $PRINT_ALL = false; $ECHO_ALL = true; $DB_DEBUG = true; -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.email.php b/www/admin/class_test.email.php index 0e07cb7b..c23c143e 100644 --- a/www/admin/class_test.email.php +++ b/www/admin/class_test.email.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.encoding.php b/www/admin/class_test.encoding.php index bebac823..28081ccc 100644 --- a/www/admin/class_test.encoding.php +++ b/www/admin/class_test.encoding.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.encryption.php b/www/admin/class_test.encryption.php index 0846d4ea..eb366ee1 100644 --- a/www/admin/class_test.encryption.php +++ b/www/admin/class_test.encryption.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.error_msg.php b/www/admin/class_test.error_msg.php index c83a3cc5..e174003d 100644 --- a/www/admin/class_test.error_msg.php +++ b/www/admin/class_test.error_msg.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.file.php b/www/admin/class_test.file.php index 4cdaa8a9..890adad6 100644 --- a/www/admin/class_test.file.php +++ b/www/admin/class_test.file.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.hash.php b/www/admin/class_test.hash.php index 847e2bf5..c269a5dc 100644 --- a/www/admin/class_test.hash.php +++ b/www/admin/class_test.hash.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.html.php b/www/admin/class_test.html.php index b02241b2..04143e62 100644 --- a/www/admin/class_test.html.php +++ b/www/admin/class_test.html.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.html_build.block.php b/www/admin/class_test.html_build.block.php index b03ee9ee..57471b3b 100644 --- a/www/admin/class_test.html_build.block.php +++ b/www/admin/class_test.html_build.block.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.html_build.element.php b/www/admin/class_test.html_build.element.php index 303388b3..3d55832f 100644 --- a/www/admin/class_test.html_build.element.php +++ b/www/admin/class_test.html_build.element.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.html_build.replace.php b/www/admin/class_test.html_build.replace.php index 2e648b28..48366736 100644 --- a/www/admin/class_test.html_build.replace.php +++ b/www/admin/class_test.html_build.replace.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.image.php b/www/admin/class_test.image.php index db57964f..8e3e4d15 100644 --- a/www/admin/class_test.image.php +++ b/www/admin/class_test.image.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.json.php b/www/admin/class_test.json.php index 03244d55..6c596280 100644 --- a/www/admin/class_test.json.php +++ b/www/admin/class_test.json.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.lang.php b/www/admin/class_test.lang.php index 8deb2a2d..f9cf58c4 100644 --- a/www/admin/class_test.lang.php +++ b/www/admin/class_test.lang.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.logging.php b/www/admin/class_test.logging.php index 778bdd63..e1c71bfa 100644 --- a/www/admin/class_test.logging.php +++ b/www/admin/class_test.logging.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.login.php b/www/admin/class_test.login.php index 1be459a9..2049635e 100644 --- a/www/admin/class_test.login.php +++ b/www/admin/class_test.login.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.math.php b/www/admin/class_test.math.php index d3af1f39..dfa40f3b 100644 --- a/www/admin/class_test.math.php +++ b/www/admin/class_test.math.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.memoryusage.php b/www/admin/class_test.memoryusage.php index 7e955a85..b105af9e 100644 --- a/www/admin/class_test.memoryusage.php +++ b/www/admin/class_test.memoryusage.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.mime.php b/www/admin/class_test.mime.php index f1bdab61..c2d1b233 100644 --- a/www/admin/class_test.mime.php +++ b/www/admin/class_test.mime.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.output.form.php b/www/admin/class_test.output.form.php index 1a1d4066..4e992666 100644 --- a/www/admin/class_test.output.form.php +++ b/www/admin/class_test.output.form.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.password.php b/www/admin/class_test.password.php index 0d1c390a..b1aa9de1 100644 --- a/www/admin/class_test.password.php +++ b/www/admin/class_test.password.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.php b/www/admin/class_test.php index d8470134..a54b2bce 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.phpv.php b/www/admin/class_test.phpv.php index 63ac0bf5..9e0d077d 100644 --- a/www/admin/class_test.phpv.php +++ b/www/admin/class_test.phpv.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.randomkey.php b/www/admin/class_test.randomkey.php index d49d8e0c..4416b63d 100644 --- a/www/admin/class_test.randomkey.php +++ b/www/admin/class_test.randomkey.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.readenvfile.php b/www/admin/class_test.readenvfile.php index 2988dbcc..adb046d6 100644 --- a/www/admin/class_test.readenvfile.php +++ b/www/admin/class_test.readenvfile.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.runningtime.php b/www/admin/class_test.runningtime.php index 27acde98..8e33dd8c 100644 --- a/www/admin/class_test.runningtime.php +++ b/www/admin/class_test.runningtime.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.session.php b/www/admin/class_test.session.php index a8227c78..eb73256d 100644 --- a/www/admin/class_test.session.php +++ b/www/admin/class_test.session.php @@ -2,7 +2,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); /** * Undocumented function diff --git a/www/admin/class_test.session.read.php b/www/admin/class_test.session.read.php index b2e6e8e3..5750e430 100644 --- a/www/admin/class_test.session.read.php +++ b/www/admin/class_test.session.read.php @@ -2,7 +2,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); /** * Undocumented function diff --git a/www/admin/class_test.smarty.php b/www/admin/class_test.smarty.php index df44f34f..31635910 100644 --- a/www/admin/class_test.smarty.php +++ b/www/admin/class_test.smarty.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.strings.php b/www/admin/class_test.strings.php index 93c5bf0a..f1ffb689 100644 --- a/www/admin/class_test.strings.php +++ b/www/admin/class_test.strings.php @@ -2,7 +2,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.system.php b/www/admin/class_test.system.php index f59a68a1..f84bf19f 100644 --- a/www/admin/class_test.system.php +++ b/www/admin/class_test.system.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.token.php b/www/admin/class_test.token.php index f46d5166..be098831 100644 --- a/www/admin/class_test.token.php +++ b/www/admin/class_test.token.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.uids.php b/www/admin/class_test.uids.php index 3302fe8b..d01598d5 100644 --- a/www/admin/class_test.uids.php +++ b/www/admin/class_test.uids.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.url-requests.curl.php b/www/admin/class_test.url-requests.curl.php index c43ce781..b3773c23 100644 --- a/www/admin/class_test.url-requests.curl.php +++ b/www/admin/class_test.url-requests.curl.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/class_test.varistype.php b/www/admin/class_test.varistype.php index 57f7487a..fc757965 100644 --- a/www/admin/class_test.varistype.php +++ b/www/admin/class_test.varistype.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/subfolder/class_test.config.direct.php b/www/admin/subfolder/class_test.config.direct.php index de6d32de..43d27810 100644 --- a/www/admin/subfolder/class_test.config.direct.php +++ b/www/admin/subfolder/class_test.config.direct.php @@ -6,7 +6,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); diff --git a/www/admin/test_edit_base.php b/www/admin/test_edit_base.php index 81558f72..ebda77ee 100644 --- a/www/admin/test_edit_base.php +++ b/www/admin/test_edit_base.php @@ -4,7 +4,7 @@ declare(strict_types=1); -error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); +error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); ob_start(); From 37e2e54b2a6dfd169feb7a6a82248cbee4949f5e Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 17 Dec 2024 15:16:48 +0900 Subject: [PATCH 037/105] Add asymmetric anonymous encryption Private/Public key encryption for anonymous messages (not receipient) --- .../Security/AsymmetricEncryption.php | 380 ++++++++++++++++++ www/lib/CoreLibs/Security/CreateKey.php | 35 +- www/lib/CoreLibs/Security/Password.php | 13 +- 3 files changed, 420 insertions(+), 8 deletions(-) create mode 100644 www/lib/CoreLibs/Security/AsymmetricEncryption.php diff --git a/www/lib/CoreLibs/Security/AsymmetricEncryption.php b/www/lib/CoreLibs/Security/AsymmetricEncryption.php new file mode 100644 index 00000000..6d3f3749 --- /dev/null +++ b/www/lib/CoreLibs/Security/AsymmetricEncryption.php @@ -0,0 +1,380 @@ +setPublicKey($public_key); + } + if ($key_pair !== null) { + $this->setKeyPair($key_pair); + if (empty($public_key)) { + $public_key = CreateKey::getPublicKey($key_pair); + $this->setPublicKey($public_key); + } + } + } + + /** + * Returns the singleton self object. + * For function wrapper use + * + * @param string|null $key_pair + * @param string|null $public_key + * @return AsymmetricAnonymousEncryption object + */ + public static function getInstance( + #[\SensitiveParameter] + string|null $key_pair = null, + string|null $public_key = null + ): self { + // new if no instsance or key is different + if ( + empty(self::$instance) || + self::$instance->key_pair != $key_pair + ) { + self::$instance = new self($key_pair, $public_key); + } + return self::$instance; + } + + /** + * clean up + */ + public function __destruct() + { + if (empty($this->key_pair)) { + return; + } + try { + // would set it to null, but we we do not want to make key null + sodium_memzero($this->key_pair); + return; + } catch (SodiumException) { + // empty catch + } + if (is_null($this->key_pair)) { + return; + } + $zero = str_repeat("\0", mb_strlen($this->key_pair, '8bit')); + $this->key_pair = $this->key_pair ^ ( + $zero ^ $this->key_pair + ); + unset($zero); + unset($this->key_pair); + } + + /* ************************************************************************ + * MARK: PRIVATE + * *************************************************************************/ + + /** + * Create the internal key pair in binary + * + * @param ?string $key_pair + * @return string + */ + private function createKeyPair( + #[\SensitiveParameter] + ?string $key_pair + ): string { + if (empty($key_pair)) { + throw new \UnexpectedValueException('Key pair cannot be empty'); + } + try { + $key_pair = CreateKey::hex2bin($key_pair); + } catch (SodiumException $e) { + sodium_memzero($key_pair); + throw new \UnexpectedValueException('Invalid hex key pair: ' . $e->getMessage()); + } + if (mb_strlen($key_pair, '8bit') !== SODIUM_CRYPTO_BOX_KEYPAIRBYTES) { + sodium_memzero($key_pair); + throw new \RangeException( + 'Key pair is not the correct size (must be ' + . SODIUM_CRYPTO_BOX_KEYPAIRBYTES . ' bytes long).' + ); + } + return $key_pair; + } + + /** + * create the internal public key in binary + * + * @param ?string $public_key + * @return string + */ + private function createPublicKey(?string $public_key): string + { + if (empty($public_key)) { + throw new \UnexpectedValueException('Public key cannot be empty'); + } + try { + $public_key = CreateKey::hex2bin($public_key); + } catch (SodiumException $e) { + sodium_memzero($public_key); + throw new \UnexpectedValueException('Invalid hex public key: ' . $e->getMessage()); + } + if (mb_strlen($public_key, '8bit') !== SODIUM_CRYPTO_BOX_PUBLICKEYBYTES) { + sodium_memzero($public_key); + throw new \RangeException( + 'Public key is not the correct size (must be ' + . SODIUM_CRYPTO_BOX_PUBLICKEYBYTES . ' bytes long).' + ); + } + return $public_key; + } + + /** + * encrypt a message asymmetric with a bpulic key + * + * @param string $message + * @param ?string $public_key + * @return string + */ + private function asymmetricEncryption( + #[\SensitiveParameter] + string $message, + ?string $public_key + ): string { + $public_key = $this->createPublicKey($public_key); + try { + $encrypted = sodium_crypto_box_seal($message, $public_key); + } catch (SodiumException $e) { + sodium_memzero($message); + throw new \UnexpectedValueException("Create encrypted message failed: " . $e->getMessage()); + } + sodium_memzero($message); + try { + $result = sodium_bin2base64($encrypted, SODIUM_BASE64_VARIANT_ORIGINAL); + } catch (SodiumException $e) { + sodium_memzero($encrypted); + throw new \UnexpectedValueException("bin2base64 failed: " . $e->getMessage()); + } + sodium_memzero($encrypted); + return $result; + } + + /** + * decrypt a message that is asymmetric encrypted with a key pair + * + * @param string $message + * @param ?string $key_pair + * @return string + */ + private function asymmetricDecryption( + #[\SensitiveParameter] + string $message, + #[\SensitiveParameter] + ?string $key_pair + ): string { + if (empty($message)) { + throw new \UnexpectedValueException('Message string cannot be empty'); + } + $key_pair = $this->createKeyPair($key_pair); + try { + $result = sodium_base642bin($message, SODIUM_BASE64_VARIANT_ORIGINAL); + } catch (SodiumException $e) { + sodium_memzero($message); + sodium_memzero($key_pair); + throw new \UnexpectedValueException("base642bin failed: " . $e->getMessage()); + } + sodium_memzero($message); + $plaintext = false; + try { + $plaintext = sodium_crypto_box_seal_open($result, $key_pair); + } catch (SodiumException $e) { + sodium_memzero($message); + sodium_memzero($key_pair); + throw new \UnexpectedValueException("Decrypting message failed: " . $e->getMessage()); + } + if (!is_string($plaintext)) { + sodium_memzero($key_pair); + throw new \UnexpectedValueException('Could not decrypt message'); + } + sodium_memzero($result); + sodium_memzero($key_pair); + return $plaintext; + } + + /* ************************************************************************ + * MARK: PUBLIC + * *************************************************************************/ + + /** + * sets the private key for encryption + * + * @param string $key_pair Key pair in hex + * @return void + */ + public function setKeyPair( + #[\SensitiveParameter] + string $key_pair + ) { + if (empty($key_pair)) { + throw new \UnexpectedValueException('Key pair cannot be empty'); + } + $this->key_pair = $key_pair; + sodium_memzero($key_pair); + } + + /** + * check if set key pair matches given one + * + * @param string $key_pair + * @return bool + */ + public function compareKeyPair( + #[\SensitiveParameter] + string $key_pair + ): bool { + return $this->key_pair === $key_pair; + } + + /** + * get the current set key pair, null if not set + * + * @return string|null + */ + public function getKeyPair(): ?string + { + return $this->key_pair; + } + + /** + * sets the public key for decryption + * if only key pair exists Security\Create::getPublicKey() can be used to + * extract the public key from the key pair + * + * @param string $public_key Public Key in hex + * @return void + */ + public function setPublicKey(string $public_key) + { + if (empty($public_key)) { + throw new \UnexpectedValueException('Public key cannot be empty'); + } + $this->public_key = $public_key; + sodium_memzero($public_key); + } + + /** + * check if the set public key matches the given one + * + * @param string $public_key + * @return bool + */ + public function comparePublicKey(string $public_key): bool + { + return $this->public_key === $public_key; + } + + /** + * get the current set public key, null if not set + * + * @return string|null + */ + public function getPublicKey(): ?string + { + return $this->public_key; + } + + /** + * Encrypt a message with a public key + * static version + * + * @param string $message Message to encrypt + * @param string $public_key Public key in hex to encrypt message with + * @return string Encrypted message as hex string + */ + public static function encryptKey( + #[\SensitiveParameter] + string $message, + string $public_key + ): string { + return self::getInstance()->asymmetricEncryption($message, $public_key); + } + + /** + * Encrypt a message + * + * @param string $message Message to ecnrypt + * @return string Encrypted message as hex string + */ + public function encrypt( + #[\SensitiveParameter] + string $message + ): string { + return $this->asymmetricEncryption($message, $this->public_key); + } + + /** + * decrypt a message with a key pair + * static version + * + * @param string $message Message to decrypt in hex + * @param string $key_pair Key pair in hex to decrypt the message with + * @return string Decrypted message + */ + public static function decryptKey( + #[\SensitiveParameter] + string $message, + #[\SensitiveParameter] + string $key_pair + ): string { + return self::getInstance()->asymmetricDecryption($message, $key_pair); + } + + /** + * decrypt a message + * + * @param string $message Message to decrypt in hex + * @return string Decrypted message + */ + public function decrypt( + #[\SensitiveParameter] + string $message + ): string { + return $this->asymmetricDecryption($message, $this->key_pair); + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Security/CreateKey.php b/www/lib/CoreLibs/Security/CreateKey.php index add2773e..e9f7c53c 100644 --- a/www/lib/CoreLibs/Security/CreateKey.php +++ b/www/lib/CoreLibs/Security/CreateKey.php @@ -35,14 +35,39 @@ class CreateKey return random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES); } + /** + * creates a sodium cyptobox keypair as hex string + * + * @return string hex string for the keypair + */ + public static function createKeyPair(): string + { + return self::bin2hex(sodium_crypto_box_keypair()); + } + + /** + * extracts the public key and returns it as hex string from the hex keypari + * + * @param string $hex_keypair hex encoded keypair + * @return string hex encoded public key + */ + public static function getPublicKey( + #[\SensitiveParameter] + string $hex_keypair + ): string { + return self::bin2hex(sodium_crypto_box_publickey(self::hex2bin($hex_keypair))); + } + /** * convert binary key to hex string * * @param string $hex_key Convert binary key string to hex * @return string */ - public static function bin2hex(string $hex_key): string - { + public static function bin2hex( + #[\SensitiveParameter] + string $hex_key + ): string { return sodium_bin2hex($hex_key); } @@ -52,8 +77,10 @@ class CreateKey * @param string $string_key Convery hex key string to binary * @return string */ - public static function hex2bin(string $string_key): string - { + public static function hex2bin( + #[\SensitiveParameter] + string $string_key + ): string { return sodium_hex2bin($string_key); } } diff --git a/www/lib/CoreLibs/Security/Password.php b/www/lib/CoreLibs/Security/Password.php index 984fa5cb..8c64228e 100644 --- a/www/lib/CoreLibs/Security/Password.php +++ b/www/lib/CoreLibs/Security/Password.php @@ -16,8 +16,10 @@ class Password * @param string $password password * @return string hashed password */ - public static function passwordSet(string $password): string - { + public static function passwordSet( + #[\SensitiveParameter] + string $password + ): string { // always use the PHP default for the password // password options ca be set in the password init, // but should be kept as default @@ -31,8 +33,11 @@ class Password * @param string $hash password hash * @return bool true or false */ - public static function passwordVerify(string $password, string $hash): bool - { + public static function passwordVerify( + #[\SensitiveParameter] + string $password, + string $hash + ): bool { if (password_verify($password, $hash)) { return true; } else { From cc067cc2020ea26436ce7c09d35048cb8a4a3482 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 17 Dec 2024 15:18:06 +0900 Subject: [PATCH 038/105] Update symmetric encryption with compare/get key, empty key test, unset on end All key and messages are set SensitiveParameter type On end, unset the key parameter with sodium mem zero Get/Compare key set methods Additional check on empty key Add missing sodium mem zero for inner function variable clean up --- ...oreLibsSecuritySymmetricEncryptionTest.php | 88 ++++++++-- .../CoreLibs/Security/SymmetricEncryption.php | 154 +++++++++++++----- 2 files changed, 189 insertions(+), 53 deletions(-) diff --git a/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php index d7486501..a9e4311e 100644 --- a/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php +++ b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php @@ -15,6 +15,56 @@ use CoreLibs\Security\SymmetricEncryption; */ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase { + /** + * Undocumented function + * + * @covers ::compareKey + * @covers ::getKey + * @testdox Check if init class set key matches to created key + * + * @return void + */ + public function testKeyInitGetCompare(): void + { + $key = CreateKey::generateRandomKey(); + $crypt = new SymmetricEncryption($key); + $this->assertTrue( + $crypt->compareKey($key), + 'set key not equal to original key' + ); + $this->assertEquals( + $key, + $crypt->getKey(), + 'set key returned not equal to original key' + ); + } + + /** + * Undocumented function + * + * @covers ::setKey + * @covers ::compareKey + * @covers ::getKey + * @testdox Check if set key after class init matches to created key + * + * @return void + */ + public function testKeySetGetCompare(): void + { + $key = CreateKey::generateRandomKey(); + $crypt = new SymmetricEncryption(); + $crypt->setKey($key); + $this->assertTrue( + $crypt->compareKey($key), + 'set key not equal to original key' + ); + $this->assertEquals( + $key, + $crypt->getKey(), + 'set key returned not equal to original key' + ); + } + /** * Undocumented function * @@ -216,6 +266,10 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase 'key' => '1cabd5cba9e042f12522f4ff2de5c31d233b', 'excpetion_message' => 'Key is not the correct size (must be ' ], + 'empty key' => [ + 'key' => '', + 'excpetion_message' => 'Key cannot be empty' + ] ]; } @@ -236,6 +290,9 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $enc_key = CreateKey::generateRandomKey(); // class + if (empty($key)) { + $this->expectExceptionMessage($exception_message); + } $crypt = new SymmetricEncryption($key); $this->expectExceptionMessage($exception_message); $crypt->encrypt('test'); @@ -244,22 +301,6 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $this->expectExceptionMessage($exception_message); $crypt->setKey($key); $crypt->decrypt($encrypted); - - // class instance - $this->expectExceptionMessage($exception_message); - SymmetricEncryption::getInstance($key)->encrypt('test'); - // we must encrypt valid thing first so we can fail with the wrong key - $encrypted = SymmetricEncryption::getInstance($enc_key)->encrypt('test'); - $this->expectExceptionMessage($exception_message); - SymmetricEncryption::getInstance($key)->decrypt($encrypted); - - // class static - $this->expectExceptionMessage($exception_message); - SymmetricEncryption::encryptKey('test', $key); - // we must encrypt valid thing first so we can fail with the wrong key - $encrypted = SymmetricEncryption::encryptKey('test', $enc_key); - $this->expectExceptionMessage($exception_message); - SymmetricEncryption::decryptKey($encrypted, $key); } /** @@ -397,6 +438,21 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $this->expectExceptionMessage($exception_message); SymmetricEncryption::decryptKey($input, $key); } + + /** + * Undocumented function + * + * @covers ::decryptKey + * @covers ::decrypt + * @testdox Test empty encrypted string to decrypt + * + * @return void + */ + public function testEmptyDecryptionString(): void + { + $this->expectExceptionMessage('Encrypted string cannot be empty'); + SymmetricEncryption::decryptKey('', CreateKey::generateRandomKey()); + } } // __END__ diff --git a/www/lib/CoreLibs/Security/SymmetricEncryption.php b/www/lib/CoreLibs/Security/SymmetricEncryption.php index 2f8fb75e..d1dcff3f 100644 --- a/www/lib/CoreLibs/Security/SymmetricEncryption.php +++ b/www/lib/CoreLibs/Security/SymmetricEncryption.php @@ -24,19 +24,19 @@ class SymmetricEncryption /** @var SymmetricEncryption self instance */ private static SymmetricEncryption $instance; - /** @var string bin hex key */ - private string $key = ''; + /** @var ?string bin hex key */ + private ?string $key = null; /** * init class * if key not passed, key must be set with createKey * - * @param string|null|null $key + * @param string|null $key encryption key */ public function __construct( - string|null $key = null + ?string $key = null ) { - if ($key != null) { + if ($key !== null) { $this->setKey($key); } } @@ -45,9 +45,10 @@ class SymmetricEncryption * Returns the singleton self object. * For function wrapper use * + * @param string|null $key encryption key * @return SymmetricEncryption object */ - public static function getInstance(string|null $key = null): self + public static function getInstance(?string $key = null): self { // new if no instsance or key is different if ( @@ -59,6 +60,34 @@ class SymmetricEncryption return self::$instance; } + /** + * clean up + * + * @return void + */ + public function __deconstruct() + { + if (empty($this->key)) { + return; + } + try { + // would set it to null, but we we do not want to make key null + sodium_memzero($this->key); + return; + } catch (SodiumException) { + // empty catch + } + if (is_null($this->key)) { + return; + } + $zero = str_repeat("\0", mb_strlen($this->key, '8bit')); + $this->key = $this->key ^ ( + $zero ^ $this->key + ); + unset($zero); + unset($this->key); + } + /* ************************************************************************ * MARK: PRIVATE * *************************************************************************/ @@ -66,11 +95,16 @@ class SymmetricEncryption /** * create key and check validity * - * @param string $key The key from which the binary key will be created - * @return string Binary key string + * @param ?string $key The key from which the binary key will be created + * @return string Binary key string */ - private function createKey(string $key): string - { + private function createKey( + #[\SensitiveParameter] + ?string $key + ): string { + if (empty($key)) { + throw new \UnexpectedValueException('Key cannot be empty'); + } try { $key = CreateKey::hex2bin($key); } catch (SodiumException $e) { @@ -95,32 +129,38 @@ class SymmetricEncryption * @throws \UnexpectedValueException * @throws \UnexpectedValueException */ - private function decryptData(string $encrypted, ?string $key): string - { - if (empty($key)) { - throw new \UnexpectedValueException('Key not set'); + private function decryptData( + #[\SensitiveParameter] + string $encrypted, + #[\SensitiveParameter] + ?string $key + ): string { + if (empty($encrypted)) { + throw new \UnexpectedValueException('Encrypted string cannot be empty'); } $key = $this->createKey($key); $decoded = base64_decode($encrypted); $nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit'); $ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit'); - $plain = false; + $plaintext = false; try { - $plain = sodium_crypto_secretbox_open( + $plaintext = sodium_crypto_secretbox_open( $ciphertext, $nonce, $key ); } catch (SodiumException $e) { + sodium_memzero($ciphertext); + sodium_memzero($key); throw new \UnexpectedValueException('Decipher message failed: ' . $e->getMessage()); } - if (!is_string($plain)) { - throw new \UnexpectedValueException('Invalid Key'); - } sodium_memzero($ciphertext); sodium_memzero($key); - return $plain; + if (!is_string($plaintext)) { + throw new \UnexpectedValueException('Invalid Key'); + } + return $plaintext; } /** @@ -128,15 +168,16 @@ class SymmetricEncryption * * @param string $message Message to encrypt * @param ?string $key Mandatory encryption key, will throw exception if empty - * @return string + * @return string Ciphered text * @throws \Exception * @throws \RangeException */ - private function encryptData(string $message, ?string $key): string - { - if ($key === null) { - throw new \UnexpectedValueException('Key not set'); - } + private function encryptData( + #[\SensitiveParameter] + string $message, + #[\SensitiveParameter] + ?string $key + ): string { $key = $this->createKey($key); $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); try { @@ -149,6 +190,8 @@ class SymmetricEncryption ) ); } catch (SodiumException $e) { + sodium_memzero($message); + sodium_memzero($key); throw new \UnexpectedValueException("Create encrypted message failed: " . $e->getMessage()); } sodium_memzero($message); @@ -160,19 +203,44 @@ class SymmetricEncryption * MARK: PUBLIC * *************************************************************************/ - /** * set a new key for encryption * * @param string $key * @return void */ - public function setKey(string $key) - { + public function setKey( + #[\SensitiveParameter] + string $key + ) { if (empty($key)) { throw new \UnexpectedValueException('Key cannot be empty'); } $this->key = $key; + sodium_memzero($key); + } + + /** + * Checks if set key is equal to parameter key + * + * @param string $key + * @return bool + */ + public function compareKey( + #[\SensitiveParameter] + string $key + ): bool { + return $key === $this->key; + } + + /** + * returns the current set key, null if not set + * + * @return ?string + */ + public function getKey(): ?string + { + return $this->key; } /** @@ -187,8 +255,12 @@ class SymmetricEncryption * @throws \UnexpectedValueException * @throws \UnexpectedValueException */ - public static function decryptKey(string $encrypted, string $key): string - { + public static function decryptKey( + #[\SensitiveParameter] + string $encrypted, + #[\SensitiveParameter] + string $key + ): string { return self::getInstance()->decryptData($encrypted, $key); } @@ -201,8 +273,10 @@ class SymmetricEncryption * @throws \UnexpectedValueException * @throws \UnexpectedValueException */ - public function decrypt(string $encrypted): string - { + public function decrypt( + #[\SensitiveParameter] + string $encrypted + ): string { return $this->decryptData($encrypted, $this->key); } @@ -216,8 +290,12 @@ class SymmetricEncryption * @throws \Exception * @throws \RangeException */ - public static function encryptKey(string $message, string $key): string - { + public static function encryptKey( + #[\SensitiveParameter] + string $message, + #[\SensitiveParameter] + string $key + ): string { return self::getInstance()->encryptData($message, $key); } @@ -229,8 +307,10 @@ class SymmetricEncryption * @throws \Exception * @throws \RangeException */ - public function encrypt(string $message): string - { + public function encrypt( + #[\SensitiveParameter] + string $message + ): string { return $this->encryptData($message, $this->key); } } From 185d044a0bf76181bd9ef15d23a741e1c2f7bccf Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 17 Dec 2024 18:23:10 +0900 Subject: [PATCH 039/105] Symmetric encryption key set tests --- ...oreLibsSecuritySymmetricEncryptionTest.php | 46 +++++++++++-------- .../CoreLibs/Security/SymmetricEncryption.php | 27 +++++------ 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php index a9e4311e..64cfd156 100644 --- a/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php +++ b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php @@ -15,6 +15,8 @@ use CoreLibs\Security\SymmetricEncryption; */ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase { + // MARK: key set compare + /** * Undocumented function * @@ -65,6 +67,25 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase ); } + // MARK: empty encrypted string + + /** + * Undocumented function + * + * @covers ::decryptKey + * @covers ::decrypt + * @testdox Test empty encrypted string to decrypt + * + * @return void + */ + public function testEmptyDecryptionString(): void + { + $this->expectExceptionMessage('Encrypted string cannot be empty'); + SymmetricEncryption::decryptKey('', CreateKey::generateRandomKey()); + } + + // MARK: encrypt/decrypt compare + /** * Undocumented function * @@ -161,6 +182,8 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase ); } + // MARK: invalid key + /** * Undocumented function * @@ -250,6 +273,8 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase SymmetricEncryption::decryptKey($encrypted, $wrong_key); } + // MARK: wrong key + /** * Undocumented function * @@ -290,9 +315,7 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $enc_key = CreateKey::generateRandomKey(); // class - if (empty($key)) { - $this->expectExceptionMessage($exception_message); - } + $this->expectExceptionMessage($exception_message); $crypt = new SymmetricEncryption($key); $this->expectExceptionMessage($exception_message); $crypt->encrypt('test'); @@ -353,6 +376,8 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase SymmetricEncryption::decryptKey($encrypted, $key); } + // MARK: wrong input + /** * Undocumented function * @@ -438,21 +463,6 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $this->expectExceptionMessage($exception_message); SymmetricEncryption::decryptKey($input, $key); } - - /** - * Undocumented function - * - * @covers ::decryptKey - * @covers ::decrypt - * @testdox Test empty encrypted string to decrypt - * - * @return void - */ - public function testEmptyDecryptionString(): void - { - $this->expectExceptionMessage('Encrypted string cannot be empty'); - SymmetricEncryption::decryptKey('', CreateKey::generateRandomKey()); - } } // __END__ diff --git a/www/lib/CoreLibs/Security/SymmetricEncryption.php b/www/lib/CoreLibs/Security/SymmetricEncryption.php index d1dcff3f..f22e9116 100644 --- a/www/lib/CoreLibs/Security/SymmetricEncryption.php +++ b/www/lib/CoreLibs/Security/SymmetricEncryption.php @@ -97,6 +97,9 @@ class SymmetricEncryption * * @param ?string $key The key from which the binary key will be created * @return string Binary key string + * @throws \UnexpectedValueException empty key + * @throws \UnexpectedValueException invalid hex key + * @throws \RangeException invalid length */ private function createKey( #[\SensitiveParameter] @@ -125,9 +128,9 @@ class SymmetricEncryption * @param string $encrypted Text to decrypt * @param ?string $key Mandatory encryption key, will throw exception if empty * @return string Plain text - * @throws \RangeException - * @throws \UnexpectedValueException - * @throws \UnexpectedValueException + * @throws \UnexpectedValueException key cannot be empty + * @throws \UnexpectedValueException decipher message failed + * @throws \UnexpectedValueException invalid key */ private function decryptData( #[\SensitiveParameter] @@ -169,8 +172,7 @@ class SymmetricEncryption * @param string $message Message to encrypt * @param ?string $key Mandatory encryption key, will throw exception if empty * @return string Ciphered text - * @throws \Exception - * @throws \RangeException + * @throws \UnexpectedValueException create message failed */ private function encryptData( #[\SensitiveParameter] @@ -208,6 +210,7 @@ class SymmetricEncryption * * @param string $key * @return void + * @throws \UnexpectedValueException key cannot be empty */ public function setKey( #[\SensitiveParameter] @@ -216,6 +219,9 @@ class SymmetricEncryption if (empty($key)) { throw new \UnexpectedValueException('Key cannot be empty'); } + // check that this is a valid key + $this->createKey($key); + // set key $this->key = $key; sodium_memzero($key); } @@ -250,10 +256,6 @@ class SymmetricEncryption * @param string $encrypted Message encrypted with safeEncrypt() * @param string $key Encryption key (as hex string) * @return string - * @throws \Exception - * @throws \RangeException - * @throws \UnexpectedValueException - * @throws \UnexpectedValueException */ public static function decryptKey( #[\SensitiveParameter] @@ -269,9 +271,6 @@ class SymmetricEncryption * * @param string $encrypted Message encrypted with safeEncrypt() * @return string - * @throws \RangeException - * @throws \UnexpectedValueException - * @throws \UnexpectedValueException */ public function decrypt( #[\SensitiveParameter] @@ -287,8 +286,6 @@ class SymmetricEncryption * @param string $message Message to encrypt * @param string $key Encryption key (as hex string) * @return string - * @throws \Exception - * @throws \RangeException */ public static function encryptKey( #[\SensitiveParameter] @@ -304,8 +301,6 @@ class SymmetricEncryption * * @param string $message Message to encrypt * @return string - * @throws \Exception - * @throws \RangeException */ public function encrypt( #[\SensitiveParameter] From 881c93c3437f0611433515592b97ffee00d9ef3d Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 18 Dec 2024 09:56:48 +0900 Subject: [PATCH 040/105] Asymmetric Anoymouse Encryption phpunit tests --- ...urityAsymmetricAnonymousEncryptionTest.php | 838 ++++++++++++++++++ ...oreLibsSecuritySymmetricEncryptionTest.php | 16 +- www/admin/class_test.encryption.php | 46 + .../Security/AsymmetricEncryption.php | 42 +- .../CoreLibs/Security/SymmetricEncryption.php | 2 +- 5 files changed, 927 insertions(+), 17 deletions(-) create mode 100644 4dev/tests/Security/CoreLibsSecurityAsymmetricAnonymousEncryptionTest.php diff --git a/4dev/tests/Security/CoreLibsSecurityAsymmetricAnonymousEncryptionTest.php b/4dev/tests/Security/CoreLibsSecurityAsymmetricAnonymousEncryptionTest.php new file mode 100644 index 00000000..d5d6b461 --- /dev/null +++ b/4dev/tests/Security/CoreLibsSecurityAsymmetricAnonymousEncryptionTest.php @@ -0,0 +1,838 @@ +assertTrue( + $crypt->compareKeyPair($key_pair), + 'set key pair not equal to original key pair' + ); + $this->assertTrue( + $crypt->comparePublicKey($public_key), + 'automatic set public key not equal to original public key' + ); + $this->assertEquals( + $key_pair, + $crypt->getKeyPair(), + 'set key pair returned not equal to original key pair' + ); + $this->assertEquals( + $public_key, + $crypt->getPublicKey(), + 'automatic set public key returned not equal to original public key' + ); + } + + /** + * Undocumented function + * + * @covers ::getKeyPair + * @covers ::compareKeyPair + * @covers ::getPublicKey + * @covers ::comparePublicKey + * @testdox Check if init class set key pair and public key matches to created key pair and public key + * + * @return void + */ + public function testKeyPairPublicKeyInitGetCompare(): void + { + $key_pair = CreateKey::createKeyPair(); + $public_key = CreateKey::getPublicKey($key_pair); + $crypt = new AsymmetricAnonymousEncryption($key_pair, $public_key); + $this->assertTrue( + $crypt->compareKeyPair($key_pair), + 'set key pair not equal to original key pair' + ); + $this->assertTrue( + $crypt->comparePublicKey($public_key), + 'set public key not equal to original public key' + ); + $this->assertEquals( + $key_pair, + $crypt->getKeyPair(), + 'set key pair returned not equal to original key pair' + ); + $this->assertEquals( + $public_key, + $crypt->getPublicKey(), + 'set public key returned not equal to original public key' + ); + } + + /** + * Undocumented function + * + * @covers ::getKeyPair + * @covers ::getPublicKey + * @covers ::comparePublicKey + * @testdox Check if init class set public key matches to created public key + * + * @return void + */ + public function testPublicKeyInitGetCompare(): void + { + $key_pair = CreateKey::createKeyPair(); + $public_key = CreateKey::getPublicKey($key_pair); + $crypt = new AsymmetricAnonymousEncryption(public_key:$public_key); + $this->assertTrue( + $crypt->comparePublicKey($public_key), + 'set public key not equal to original public key' + ); + $this->assertEquals( + null, + $crypt->getKeyPair(), + 'unset set key pair returned not equal to original key pair' + ); + $this->assertEquals( + $public_key, + $crypt->getPublicKey(), + 'set public key returned not equal to original public key' + ); + } + + /** + * Undocumented function + * + * @covers ::setKeyPair + * @covers ::getKeyPair + * @covers ::compareKeyPair + * @covers ::getPublicKey + * @covers ::comparePublicKey + * @testdox Check if set key pair after class init matches to created key pair and public key + * + * @return void + */ + public function testKeyPairSetGetCompare(): void + { + $key_pair = CreateKey::createKeyPair(); + $public_key = CreateKey::getPublicKey($key_pair); + $crypt = new AsymmetricAnonymousEncryption(); + $crypt->setKeyPair($key_pair); + $this->assertTrue( + $crypt->compareKeyPair($key_pair), + 'post class init set key pair not equal to original key pair' + ); + $this->assertTrue( + $crypt->comparePublicKey($public_key), + 'post class init automatic set public key not equal to original public key' + ); + $this->assertEquals( + $key_pair, + $crypt->getKeyPair(), + 'post class init set key pair returned not equal to original key pair' + ); + $this->assertEquals( + $public_key, + $crypt->getPublicKey(), + 'post class init automatic set public key returned not equal to original public key' + ); + } + + /** + * Undocumented function + * + * @covers ::setKeyPair + * @covers ::setPublicKey + * @covers ::getKeyPair + * @covers ::compareKeyPair + * @covers ::getPublicKey + * @covers ::comparePublicKey + * @testdox Check if set key pair after class init matches to created key pair and public key + * + * @return void + */ + public function testKeyPairPublicKeySetGetCompare(): void + { + $key_pair = CreateKey::createKeyPair(); + $public_key = CreateKey::getPublicKey($key_pair); + $crypt = new AsymmetricAnonymousEncryption(); + $crypt->setKeyPair($key_pair); + $crypt->setPublicKey($public_key); + $this->assertTrue( + $crypt->compareKeyPair($key_pair), + 'post class init set key pair not equal to original key pair' + ); + $this->assertTrue( + $crypt->comparePublicKey($public_key), + 'post class init set public key not equal to original public key' + ); + $this->assertEquals( + $key_pair, + $crypt->getKeyPair(), + 'post class init set key pair returned not equal to original key pair' + ); + $this->assertEquals( + $public_key, + $crypt->getPublicKey(), + 'post class init set public key returned not equal to original public key' + ); + } + + /** + * Undocumented function + * + * @covers ::setPublicKey + * @covers ::getKeyPair + * @covers ::compareKeyPair + * @covers ::getPublicKey + * @covers ::comparePublicKey + * @testdox Check if set key pair after class init matches to created key pair and public key + * + * @return void + */ + public function testPublicKeySetGetCompare(): void + { + $key_pair = CreateKey::createKeyPair(); + $public_key = CreateKey::getPublicKey($key_pair); + $crypt = new AsymmetricAnonymousEncryption(); + $crypt->setPublicKey($public_key); + $this->assertTrue( + $crypt->comparePublicKey($public_key), + 'post class init set public key not equal to original public key' + ); + $this->assertEquals( + null, + $crypt->getKeyPair(), + 'post class init unset key pair returned not equal to original key pair' + ); + $this->assertEquals( + $public_key, + $crypt->getPublicKey(), + 'post class init set public key returned not equal to original public key' + ); + } + + /** + * Undocumented function + * + * @testdox Check different key pair and public key set + * + * @return void + */ + public function testDifferentSetKeyPairPublicKey() + { + $key_pair = CreateKey::createKeyPair(); + $public_key = CreateKey::getPublicKey($key_pair); + $key_pair_2 = CreateKey::createKeyPair(); + $public_key_2 = CreateKey::getPublicKey($key_pair_2); + $crypt = new AsymmetricAnonymousEncryption($key_pair, $public_key_2); + $this->assertTrue( + $crypt->compareKeyPair($key_pair), + 'key pair set matches key pair created' + ); + $this->assertTrue( + $crypt->comparePublicKey($public_key_2), + 'alternate public key set matches alternate public key created' + ); + $this->assertFalse( + $crypt->comparePublicKey($public_key), + 'alternate public key set does not match key pair public key' + ); + } + + /** + * Undocumented function + * + * @testdox Check if new set privat key does not overwrite set public key + * + * @return void + */ + public function testUpdateKeyPairNotUpdatePublicKey(): void + { + $key_pair = CreateKey::createKeyPair(); + $public_key = CreateKey::getPublicKey($key_pair); + $crypt = new AsymmetricAnonymousEncryption($key_pair); + $this->assertTrue( + $crypt->compareKeyPair($key_pair), + 'set key pair not equal to original key pair' + ); + $this->assertTrue( + $crypt->comparePublicKey($public_key), + 'set public key not equal to original public key' + ); + $key_pair_2 = CreateKey::createKeyPair(); + $public_key_2 = CreateKey::getPublicKey($key_pair_2); + $crypt->setKeyPair($key_pair_2); + $this->assertTrue( + $crypt->compareKeyPair($key_pair_2), + 'new set key pair not equal to original new key pair' + ); + $this->assertTrue( + $crypt->comparePublicKey($public_key), + 'original set public key not equal to original public key' + ); + $this->assertFalse( + $crypt->comparePublicKey($public_key_2), + 'new public key equal to original public key' + ); + } + + // MARK: empty encrytped string + + /** + * Undocumented function + * + * @covers ::decryptKey + * @covers ::decrypt + * @testdox Test empty encrypted string to decrypt + * + * @return void + */ + public function testEmptyDecryptionString(): void + { + $this->expectExceptionMessage('Encrypted string cannot be empty'); + AsymmetricAnonymousEncryption::decryptKey('', CreateKey::generateRandomKey()); + } + + // MARK: encrypt/decrypt + + /** + * Undocumented function + * + * @return array + */ + public function providerEncryptDecryptSuccess(): array + { + return [ + 'valid string' => [ + 'input' => 'I am a secret', + 'expected' => 'I am a secret', + ], + ]; + } + + /** + * test encrypt/decrypt produce correct output + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptDecryptSuccess + * @testdox encrypt/decrypt $input must be $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testEncryptDecryptSuccess(string $input, string $expected): void + { + $key_pair = CreateKey::createKeyPair(); + $public_key = CreateKey::getPublicKey($key_pair); + // test class + $crypt = new AsymmetricAnonymousEncryption($key_pair); + $encrypted = $crypt->encrypt($input); + $decrypted = $crypt->decrypt($encrypted); + $this->assertEquals( + $expected, + $decrypted, + 'Class call', + ); + $crypt = new AsymmetricAnonymousEncryption($key_pair, $public_key); + $encrypted = $crypt->encrypt($input); + $decrypted = $crypt->decrypt($encrypted); + $this->assertEquals( + $expected, + $decrypted, + 'Class call botjh set', + ); + } + + /** + * test encrypt/decrypt produce correct output + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptDecryptSuccess + * @testdox encrypt/decrypt indirect $input must be $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testEncryptDecryptSuccessIndirect(string $input, string $expected): void + { + $key_pair = CreateKey::createKeyPair(); + $public_key = CreateKey::getPublicKey($key_pair); + // test indirect + $encrypted = AsymmetricAnonymousEncryption::getInstance(public_key:$public_key)->encrypt($input); + $decrypted = AsymmetricAnonymousEncryption::getInstance($key_pair)->decrypt($encrypted); + $this->assertEquals( + $expected, + $decrypted, + 'Class Instance call', + ); + } + + /** + * test encrypt/decrypt produce correct output + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptDecryptSuccess + * @testdox encrypt/decrypt indirect with public key $input must be $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testEncryptDecryptSuccessIndirectPublicKey(string $input, string $expected): void + { + $key_pair = CreateKey::createKeyPair(); + $public_key = CreateKey::getPublicKey($key_pair); + // test indirect + $encrypted = AsymmetricAnonymousEncryption::getInstance(public_key:$public_key)->encrypt($input); + $decrypted = AsymmetricAnonymousEncryption::getInstance($key_pair)->decrypt($encrypted); + $this->assertEquals( + $expected, + $decrypted, + 'Class Instance call public key', + ); + } + + /** + * test encrypt/decrypt produce correct output + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptDecryptSuccess + * @testdox encrypt/decrypt static $input must be $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testEncryptDecryptSuccessStatic(string $input, string $expected): void + { + $key_pair = CreateKey::createKeyPair(); + $public_key = CreateKey::getPublicKey($key_pair); + // test static + $encrypted = AsymmetricAnonymousEncryption::encryptKey($input, $public_key); + $decrypted = AsymmetricAnonymousEncryption::decryptKey($encrypted, $key_pair); + + $this->assertEquals( + $expected, + $decrypted, + 'Static call', + ); + } + + // MARK: invalid decrypt key + + /** + * Undocumented function + * + * @return array + */ + public function providerEncryptFailed(): array + { + return [ + 'wrong decryption key' => [ + 'input' => 'I am a secret', + 'excpetion_message' => 'Invalid key pair' + ], + ]; + } + + /** + * Test decryption with wrong key + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptFailed + * @testdox decrypt with wrong key $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testEncryptFailed(string $input, string $exception_message): void + { + $key_pair = CreateKey::createKeyPair(); + $public_key = CreateKey::getPublicKey($key_pair); + $wrong_key_pair = CreateKey::createKeyPair(); + + // wrong key in class call + $crypt = new AsymmetricAnonymousEncryption(public_key:$public_key); + $encrypted = $crypt->encrypt($input); + $this->expectExceptionMessage($exception_message); + $crypt->setKeyPair($wrong_key_pair); + $crypt->decrypt($encrypted); + } + + /** + * Test decryption with wrong key + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptFailed + * @testdox decrypt indirect with wrong key $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testEncryptFailedIndirect(string $input, string $exception_message): void + { + $key_pair = CreateKey::createKeyPair(); + $public_key = CreateKey::getPublicKey($key_pair); + $wrong_key_pair = CreateKey::createKeyPair(); + + // class instance + $encrypted = AsymmetricAnonymousEncryption::getInstance(public_key:$public_key)->encrypt($input); + $this->expectExceptionMessage($exception_message); + AsymmetricAnonymousEncryption::getInstance($wrong_key_pair)->decrypt($encrypted); + } + + /** + * Test decryption with wrong key + * + * @covers ::generateRandomKey + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerEncryptFailed + * @testdox decrypt static with wrong key $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testEncryptFailedStatic(string $input, string $exception_message): void + { + $key_pair = CreateKey::createKeyPair(); + $public_key = CreateKey::getPublicKey($key_pair); + $wrong_key_pair = CreateKey::createKeyPair(); + + // class static + $encrypted = AsymmetricAnonymousEncryption::encryptKey($input, $public_key); + $this->expectExceptionMessage($exception_message); + AsymmetricAnonymousEncryption::decryptKey($encrypted, $wrong_key_pair); + } + + // MARK: invalid key pair + + /** + * Undocumented function + * + * @return array + */ + public function providerWrongKeyPair(): array + { + return [ + 'not hex key pair' => [ + 'key_pair' => 'not_a_hex_key_pair', + 'exception_message' => 'Invalid hex key pair' + ], + 'too short hex key pair' => [ + 'key_pair' => '1cabd5cba9e042f12522f4ff2de5c31d233b', + 'excpetion_message' => 'Key pair is not the correct size (must be ' + ], + 'empty key pair' => [ + 'key_pair' => '', + 'excpetion_message' => 'Key pair cannot be empty' + ] + ]; + } + + /** + * test invalid key provided to decrypt or encrypt + * + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerWrongKeyPair + * @testdox wrong key pair $key_pair throws $exception_message [$_dataName] + * + * @param string $key_pair + * @param string $exception_message + * @return void + */ + public function testWrongKeyPair(string $key_pair, string $exception_message): void + { + $enc_key_pair = CreateKey::createKeyPair(); + + // class + $this->expectExceptionMessage($exception_message); + $crypt = new AsymmetricAnonymousEncryption($key_pair); + $this->expectExceptionMessage($exception_message); + $crypt->encrypt('test'); + $crypt->setKeyPair($enc_key_pair); + $encrypted = $crypt->encrypt('test'); + $this->expectExceptionMessage($exception_message); + $crypt->setKeyPair($key_pair); + $crypt->decrypt($encrypted); + } + + /** + * test invalid key provided to decrypt or encrypt + * + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerWrongKeyPair + * @testdox wrong key pair indirect $key_pair throws $exception_message [$_dataName] + * + * @param string $key_pair + * @param string $exception_message + * @return void + */ + public function testWrongKeyPairIndirect(string $key_pair, string $exception_message): void + { + $enc_key_pair = CreateKey::createKeyPair(); + + // set valid encryption + $encrypted = AsymmetricAnonymousEncryption::getInstance($enc_key_pair)->encrypt('test'); + $this->expectExceptionMessage($exception_message); + AsymmetricAnonymousEncryption::getInstance($key_pair)->decrypt($encrypted); + } + + /** + * test invalid key provided to decrypt or encrypt + * + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerWrongKeyPair + * @testdox wrong key pair static $key_pair throws $exception_message [$_dataName] + * + * @param string $key_pair + * @param string $exception_message + * @return void + */ + public function testWrongKeyPairStatic(string $key_pair, string $exception_message): void + { + $enc_key_pair = CreateKey::createKeyPair(); + + // set valid encryption + $encrypted = AsymmetricAnonymousEncryption::encryptKey('test', CreateKey::getPublicKey($enc_key_pair)); + $this->expectExceptionMessage($exception_message); + AsymmetricAnonymousEncryption::decryptKey($encrypted, $key_pair); + } + + // MARK: invalid public key + + /** + * Undocumented function + * + * @return array + */ + public function providerWrongPublicKey(): array + { + return [ + 'not hex public key' => [ + 'public_key' => 'not_a_hex_public_key', + 'exception_message' => 'Invalid hex public key' + ], + 'too short hex public key' => [ + 'public_key' => '1cabd5cba9e042f12522f4ff2de5c31d233b', + 'excpetion_message' => 'Public key is not the correct size (must be ' + ], + 'empty public key' => [ + 'public_key' => '', + 'excpetion_message' => 'Public key cannot be empty' + ] + ]; + } + + /** + * test invalid key provided to decrypt or encrypt + * + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerWrongPublicKey + * @testdox wrong public key $public_key throws $exception_message [$_dataName] + * + * @param string $public_key + * @param string $exception_message + * @return void + */ + public function testWrongPublicKey(string $public_key, string $exception_message): void + { + $enc_key_pair = CreateKey::createKeyPair(); + // $enc_public_key = CreateKey::getPublicKey($enc_key_pair); + + // class + $this->expectExceptionMessage($exception_message); + $crypt = new AsymmetricAnonymousEncryption(public_key:$public_key); + $this->expectExceptionMessage($exception_message); + $crypt->decrypt('test'); + $crypt->setKeyPair($enc_key_pair); + $encrypted = $crypt->encrypt('test'); + $this->expectExceptionMessage($exception_message); + $crypt->setPublicKey($public_key); + $crypt->decrypt($encrypted); + } + + /** + * test invalid key provided to decrypt or encrypt + * + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerWrongPublicKey + * @testdox wrong public key indirect $key throws $exception_message [$_dataName] + * + * @param string $key + * @param string $exception_message + * @return void + */ + public function testWrongPublicKeyIndirect(string $key, string $exception_message): void + { + $enc_key = CreateKey::createKeyPair(); + + // class instance + $this->expectExceptionMessage($exception_message); + AsymmetricAnonymousEncryption::getInstance(public_key:$key)->encrypt('test'); + // we must encrypt valid thing first so we can fail with the wrong key + $encrypted = AsymmetricAnonymousEncryption::getInstance($enc_key)->encrypt('test'); + // $this->expectExceptionMessage($exception_message); + AsymmetricAnonymousEncryption::getInstance($key)->decrypt($encrypted); + } + + /** + * test invalid key provided to decrypt or encrypt + * + * @covers ::encrypt + * @covers ::decrypt + * @dataProvider providerWrongPublicKey + * @testdox wrong public key static $key throws $exception_message [$_dataName] + * + * @param string $key + * @param string $exception_message + * @return void + */ + public function testWrongPublicKeyStatic(string $key, string $exception_message): void + { + $enc_key = CreateKey::createKeyPair(); + + // class static + $this->expectExceptionMessage($exception_message); + AsymmetricAnonymousEncryption::encryptKey('test', $key); + // we must encrypt valid thing first so we can fail with the wrong key + $encrypted = AsymmetricAnonymousEncryption::encryptKey('test', $enc_key); + $this->expectExceptionMessage($exception_message); + AsymmetricAnonymousEncryption::decryptKey($encrypted, $key); + } + + // MARK: wrong cipher text + + /** + * Undocumented function + * + * @return array + */ + public function providerWrongCiphertext(): array + { + return [ + 'invalid cipher text' => [ + 'input' => 'short', + 'exception_message' => 'base642bin failed: ' + ], + 'cannot decrypt' => [ + // phpcs:disable Generic.Files.LineLength + 'input' => 'Um8tBGiVfFAOg2YoUgA5fTqK1wXPB1S7uxhPNE1lqDxgntkEhYJDOmjXa0DMpBlYHjab6sC4mgzwZSzGCUnXDAgsHckwYwfAzs/r', + // phpcs:enable Generic.Files.LineLength + 'exception_message' => 'Invalid key pair' + ], + 'invalid text' => [ + 'input' => 'U29tZSB0ZXh0IGhlcmU=', + 'exception_message' => 'Invalid key pair' + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::decrypt + * @dataProvider providerWrongCiphertext + * @testdox too short ciphertext $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testWrongCiphertext(string $input, string $exception_message): void + { + $key = CreateKey::createKeyPair(); + // class + $crypt = new AsymmetricAnonymousEncryption($key); + $this->expectExceptionMessage($exception_message); + $crypt->decrypt($input); + } + + /** + * Undocumented function + * + * @covers ::decryptKey + * @dataProvider providerWrongCiphertext + * @testdox too short ciphertext indirect $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testWrongCiphertextIndirect(string $input, string $exception_message): void + { + $key = CreateKey::createKeyPair(); + + // class instance + $this->expectExceptionMessage($exception_message); + AsymmetricAnonymousEncryption::getInstance($key)->decrypt($input); + + // class static + $this->expectExceptionMessage($exception_message); + AsymmetricAnonymousEncryption::decryptKey($input, $key); + } + + /** + * Undocumented function + * + * @covers ::decryptKey + * @dataProvider providerWrongCiphertext + * @testdox too short ciphertext static $input throws $exception_message [$_dataName] + * + * @param string $input + * @param string $exception_message + * @return void + */ + public function testWrongCiphertextStatic(string $input, string $exception_message): void + { + $key = CreateKey::createKeyPair(); + // class static + $this->expectExceptionMessage($exception_message); + AsymmetricAnonymousEncryption::decryptKey($input, $key); + } +} + +// __END__ diff --git a/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php index 64cfd156..1251a6da 100644 --- a/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php +++ b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php @@ -159,8 +159,8 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase * test encrypt/decrypt produce correct output * * @covers ::generateRandomKey - * @covers ::encrypt - * @covers ::decrypt + * @covers ::encryptKey + * @covers ::decryptKey * @dataProvider providerEncryptDecryptSuccess * @testdox encrypt/decrypt static $input must be $expected [$_dataName] * @@ -253,8 +253,8 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase * Test decryption with wrong key * * @covers ::generateRandomKey - * @covers ::encrypt - * @covers ::decrypt + * @covers ::encryptKey + * @covers ::decryptKey * @dataProvider providerEncryptFailed * @testdox decrypt static with wrong key $input throws $exception_message [$_dataName] * @@ -354,8 +354,8 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase /** * test invalid key provided to decrypt or encrypt * - * @covers ::encrypt - * @covers ::decrypt + * @covers ::encryptKey + * @covers ::decryptKey * @dataProvider providerWrongKey * @testdox wrong key static $key throws $exception_message [$_dataName] * @@ -424,7 +424,7 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase /** * Undocumented function * - * @covers ::decrypt + * @covers ::decryptKey * @dataProvider providerWrongCiphertext * @testdox too short ciphertext indirect $input throws $exception_message [$_dataName] * @@ -448,7 +448,7 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase /** * Undocumented function * - * @covers ::decrypt + * @covers ::decryptKey * @dataProvider providerWrongCiphertext * @testdox too short ciphertext static $input throws $exception_message [$_dataName] * diff --git a/www/admin/class_test.encryption.php b/www/admin/class_test.encryption.php index eb366ee1..e1823554 100644 --- a/www/admin/class_test.encryption.php +++ b/www/admin/class_test.encryption.php @@ -18,6 +18,7 @@ require 'config.php'; $LOG_FILE_ID = 'classTest-encryption'; ob_end_flush(); +use CoreLibs\Security\AsymmetricAnonymousEncryption; use CoreLibs\Security\SymmetricEncryption; use CoreLibs\Security\CreateKey; @@ -36,6 +37,8 @@ print ""; print ''; print '

' . $PAGE_NAME . '

'; +print "

Symmetric Encryption

"; + $key = CreateKey::generateRandomKey(); print "Secret Key: " . $key . "
"; @@ -105,6 +108,49 @@ try { // $encrypted = $se->encrypt($string); // $decrypted = $se->decrypt($encrypted); +echo "
"; +print "

Asymmetric Encryption

"; + +$key_pair = CreateKey::createKeyPair(); +$public_key = CreateKey::getPublicKey($key_pair); + +$string = "I am some asymmetric secret"; +print "Message: " . $string . "
"; +$encrypted = sodium_crypto_box_seal($string, CreateKey::hex2bin($public_key)); +$message = sodium_bin2base64($encrypted, SODIUM_BASE64_VARIANT_ORIGINAL); +print "Encrypted PL: " . $message . "
"; +$result = sodium_base642bin($message, SODIUM_BASE64_VARIANT_ORIGINAL); +$decrypted = sodium_crypto_box_seal_open($result, CreateKey::hex2bin($key_pair)); +print "Decrypted PL: " . $decrypted . "
"; + +$encrypted = AsymmetricAnonymousEncryption::encryptKey($string, $public_key); +print "Encrypted ST: " . $encrypted . "
"; +$decrypted = AsymmetricAnonymousEncryption::decryptKey($encrypted, $key_pair); +print "Decrypted ST: " . $decrypted . "
"; + +$aa_crypt = new AsymmetricAnonymousEncryption($key_pair, $public_key); +$encrypted = $aa_crypt->encrypt($string); +print "Encrypted: " . $encrypted . "
"; +$decrypted = $aa_crypt->decrypt($encrypted); +print "Decrypted: " . $decrypted . "
"; + +print "Base64 encode: " . base64_encode('Some text here') . "
"; + +/// this has to fail +$crypt = new AsymmetricAnonymousEncryption(); +$crypt->setPublicKey(CreateKey::getPublicKey(CreateKey::createKeyPair())); +print "Public Key: " . $crypt->getPublicKey() . "
"; +try { + $crypt->setPublicKey(CreateKey::createKeyPair()); +} catch (RangeException $e) { + print "Invalid range:
$e
"; +} +try { + $crypt->setKeyPair(CreateKey::getPublicKey(CreateKey::createKeyPair())); +} catch (RangeException $e) { + print "Invalid range:
$e
"; +} + print ""; // __END__ diff --git a/www/lib/CoreLibs/Security/AsymmetricEncryption.php b/www/lib/CoreLibs/Security/AsymmetricEncryption.php index 6d3f3749..47c357d7 100644 --- a/www/lib/CoreLibs/Security/AsymmetricEncryption.php +++ b/www/lib/CoreLibs/Security/AsymmetricEncryption.php @@ -70,7 +70,8 @@ class AsymmetricAnonymousEncryption // new if no instsance or key is different if ( empty(self::$instance) || - self::$instance->key_pair != $key_pair + self::$instance->key_pair != $key_pair || + self::$instance->public_key != $public_key ) { self::$instance = new self($key_pair, $public_key); } @@ -100,7 +101,7 @@ class AsymmetricAnonymousEncryption $zero ^ $this->key_pair ); unset($zero); - unset($this->key_pair); + unset($this->key_pair); /** @phan-suppress-current-line PhanTypeObjectUnsetDeclaredProperty */ } /* ************************************************************************ @@ -112,6 +113,9 @@ class AsymmetricAnonymousEncryption * * @param ?string $key_pair * @return string + * @throws \UnexpectedValueException key pair empty + * @throws \UnexpectedValueException invalid hex key pair + * @throws \UnexpectedValueException key pair not correct size */ private function createKeyPair( #[\SensitiveParameter] @@ -141,6 +145,9 @@ class AsymmetricAnonymousEncryption * * @param ?string $public_key * @return string + * @throws \UnexpectedValueException public key empty + * @throws \UnexpectedValueException invalid hex key + * @throws \UnexpectedValueException invalid key length */ private function createPublicKey(?string $public_key): string { @@ -169,6 +176,8 @@ class AsymmetricAnonymousEncryption * @param string $message * @param ?string $public_key * @return string + * @throws \UnexpectedValueException create encryption failed + * @throws \UnexpectedValueException convert to base64 failed */ private function asymmetricEncryption( #[\SensitiveParameter] @@ -199,6 +208,10 @@ class AsymmetricAnonymousEncryption * @param string $message * @param ?string $key_pair * @return string + * @throws \UnexpectedValueException message string empty + * @throws \UnexpectedValueException base64 decoding failed + * @throws \UnexpectedValueException decryption failed + * @throws \UnexpectedValueException could not decrypt message */ private function asymmetricDecryption( #[\SensitiveParameter] @@ -207,7 +220,7 @@ class AsymmetricAnonymousEncryption ?string $key_pair ): string { if (empty($message)) { - throw new \UnexpectedValueException('Message string cannot be empty'); + throw new \UnexpectedValueException('Encrypted string cannot be empty'); } $key_pair = $this->createKeyPair($key_pair); try { @@ -224,14 +237,14 @@ class AsymmetricAnonymousEncryption } catch (SodiumException $e) { sodium_memzero($message); sodium_memzero($key_pair); + sodium_memzero($result); throw new \UnexpectedValueException("Decrypting message failed: " . $e->getMessage()); } - if (!is_string($plaintext)) { - sodium_memzero($key_pair); - throw new \UnexpectedValueException('Could not decrypt message'); - } - sodium_memzero($result); sodium_memzero($key_pair); + sodium_memzero($result); + if (!is_string($plaintext)) { + throw new \UnexpectedValueException('Invalid key pair'); + } return $plaintext; } @@ -244,6 +257,7 @@ class AsymmetricAnonymousEncryption * * @param string $key_pair Key pair in hex * @return void + * @throws \UnexpectedValueException key pair empty */ public function setKeyPair( #[\SensitiveParameter] @@ -252,8 +266,17 @@ class AsymmetricAnonymousEncryption if (empty($key_pair)) { throw new \UnexpectedValueException('Key pair cannot be empty'); } + // check if valid; + $this->createKeyPair($key_pair); + // set new key pair $this->key_pair = $key_pair; sodium_memzero($key_pair); + // set public key if not set + if (empty($this->public_key)) { + $this->public_key = CreateKey::getPublicKey($this->key_pair); + // check if valid + $this->createPublicKey($this->public_key); + } } /** @@ -286,12 +309,15 @@ class AsymmetricAnonymousEncryption * * @param string $public_key Public Key in hex * @return void + * @throws \UnexpectedValueException public key empty */ public function setPublicKey(string $public_key) { if (empty($public_key)) { throw new \UnexpectedValueException('Public key cannot be empty'); } + // check if valid + $this->createPublicKey($public_key); $this->public_key = $public_key; sodium_memzero($public_key); } diff --git a/www/lib/CoreLibs/Security/SymmetricEncryption.php b/www/lib/CoreLibs/Security/SymmetricEncryption.php index f22e9116..91d8c2cb 100644 --- a/www/lib/CoreLibs/Security/SymmetricEncryption.php +++ b/www/lib/CoreLibs/Security/SymmetricEncryption.php @@ -85,7 +85,7 @@ class SymmetricEncryption $zero ^ $this->key ); unset($zero); - unset($this->key); + unset($this->key); /** @phan-suppress-current-line PhanTypeObjectUnsetDeclaredProperty */ } /* ************************************************************************ From 41e116f7d4014218f3e161f08c9df4c724a8aa53 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 18 Dec 2024 10:11:47 +0900 Subject: [PATCH 041/105] phpstan checks for level 9 --- phpstan.neon | 2 +- www/lib/FileUpload/Core/qqUploadedFileXhr.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 1d6c4ff5..6d3c1508 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,7 +9,7 @@ parameters: #friendly: # lineBefore: 3 # lineAfter: 3 - level: 8 # max is now 9 + level: 8 # max is now 10 # strictRules: # allRules: false checkMissingCallableSignature: true diff --git a/www/lib/FileUpload/Core/qqUploadedFileXhr.php b/www/lib/FileUpload/Core/qqUploadedFileXhr.php index 98494145..c1a359cc 100644 --- a/www/lib/FileUpload/Core/qqUploadedFileXhr.php +++ b/www/lib/FileUpload/Core/qqUploadedFileXhr.php @@ -46,19 +46,19 @@ class qqUploadedFileXhr implements qqUploadedFile // phpcs:ignore Squiz.Classes. */ public function getName(): string { - return $_GET['qqfile'] ?? ''; + return !empty($_GET['qqfile']) && is_string($_GET['qqfile']) ? $_GET['qqfile'] : ''; } /** * Get file size from _SERVERa array, throws an error if not possible * - * @return int + * @return int size of the file * * @throws \Exception */ public function getSize(): int { - if (isset($_SERVER['CONTENT_LENGTH'])) { + if (isset($_SERVER['CONTENT_LENGTH']) && is_numeric($_SERVER['CONTENT_LENGTH'])) { return (int)$_SERVER['CONTENT_LENGTH']; } else { throw new \Exception('Getting content length is not supported.'); From 10935214eb19b4596625547a4c8ee7716b6ae318 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 18 Dec 2024 10:50:27 +0900 Subject: [PATCH 042/105] Fix Class file name for asymmetric anonymous encryption --- ...AsymmetricEncryption.php => AsymmetricAnonymousEncryption.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename www/lib/CoreLibs/Security/{AsymmetricEncryption.php => AsymmetricAnonymousEncryption.php} (100%) diff --git a/www/lib/CoreLibs/Security/AsymmetricEncryption.php b/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php similarity index 100% rename from www/lib/CoreLibs/Security/AsymmetricEncryption.php rename to www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php From 7248906da72e6abce773de2c8297fd4ef28686cc Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 20 Dec 2024 15:13:22 +0900 Subject: [PATCH 043/105] Allow chaining of key set functions for encryption --- www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php | 6 ++++-- www/lib/CoreLibs/Security/SymmetricEncryption.php | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php b/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php index 47c357d7..82d16213 100644 --- a/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php +++ b/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php @@ -262,7 +262,7 @@ class AsymmetricAnonymousEncryption public function setKeyPair( #[\SensitiveParameter] string $key_pair - ) { + ): AsymmetricAnonymousEncryption { if (empty($key_pair)) { throw new \UnexpectedValueException('Key pair cannot be empty'); } @@ -277,6 +277,7 @@ class AsymmetricAnonymousEncryption // check if valid $this->createPublicKey($this->public_key); } + return $this; } /** @@ -311,7 +312,7 @@ class AsymmetricAnonymousEncryption * @return void * @throws \UnexpectedValueException public key empty */ - public function setPublicKey(string $public_key) + public function setPublicKey(string $public_key): AsymmetricAnonymousEncryption { if (empty($public_key)) { throw new \UnexpectedValueException('Public key cannot be empty'); @@ -320,6 +321,7 @@ class AsymmetricAnonymousEncryption $this->createPublicKey($public_key); $this->public_key = $public_key; sodium_memzero($public_key); + return $this; } /** diff --git a/www/lib/CoreLibs/Security/SymmetricEncryption.php b/www/lib/CoreLibs/Security/SymmetricEncryption.php index 91d8c2cb..ed0ca398 100644 --- a/www/lib/CoreLibs/Security/SymmetricEncryption.php +++ b/www/lib/CoreLibs/Security/SymmetricEncryption.php @@ -215,7 +215,7 @@ class SymmetricEncryption public function setKey( #[\SensitiveParameter] string $key - ) { + ): SymmetricEncryption { if (empty($key)) { throw new \UnexpectedValueException('Key cannot be empty'); } @@ -224,6 +224,7 @@ class SymmetricEncryption // set key $this->key = $key; sodium_memzero($key); + return $this; } /** From d1e65c702e88ba0c7c5772edaef8b52ec9a365f2 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 20 Dec 2024 18:48:00 +0900 Subject: [PATCH 044/105] Allow Seession settings to be changed eg set the auto write + others or set/unset can be chagned for single sets --- www/lib/CoreLibs/Create/Session.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/www/lib/CoreLibs/Create/Session.php b/www/lib/CoreLibs/Create/Session.php index b3f9f772..5932e75b 100644 --- a/www/lib/CoreLibs/Create/Session.php +++ b/www/lib/CoreLibs/Create/Session.php @@ -365,9 +365,10 @@ class Session * @param bool $flag * @return void */ - public function setAutoWriteClose(bool $flag): void + public function setAutoWriteClose(bool $flag): Session { $this->auto_write_close = $flag; + return $this; } /** @@ -515,12 +516,13 @@ class Session * @param mixed $value value to set (can be anything) * @return void */ - public function set(string $name, mixed $value): void + public function set(string $name, mixed $value): Session { $this->checkValidSessionEntryKey($name); $this->restartSession(); $_SESSION[$name] = $value; $this->closeSessionCall(); + return $this; } /** @@ -579,7 +581,7 @@ class Session * @param string $name _SESSION key name to remove * @return void */ - public function unset(string $name): void + public function unset(string $name): Session { if (!isset($_SESSION[$name])) { return; @@ -587,6 +589,7 @@ class Session $this->restartSession(); unset($_SESSION[$name]); $this->closeSessionCall(); + return $this; } /** From 8d0036eaac16beffdcfd8bc8f9a386aae50a7a7b Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 23 Dec 2024 11:26:50 +0900 Subject: [PATCH 045/105] Fix phpdoc return types --- www/lib/CoreLibs/Create/Session.php | 8 ++++---- .../CoreLibs/Security/AsymmetricAnonymousEncryption.php | 4 ++-- www/lib/CoreLibs/Security/SymmetricEncryption.php | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/www/lib/CoreLibs/Create/Session.php b/www/lib/CoreLibs/Create/Session.php index 5932e75b..4440b47e 100644 --- a/www/lib/CoreLibs/Create/Session.php +++ b/www/lib/CoreLibs/Create/Session.php @@ -363,7 +363,7 @@ class Session * set the auto write close flag * * @param bool $flag - * @return void + * @return Session */ public function setAutoWriteClose(bool $flag): Session { @@ -514,7 +514,7 @@ class Session * * @param string $name array name in _SESSION * @param mixed $value value to set (can be anything) - * @return void + * @return Session */ public function set(string $name, mixed $value): Session { @@ -579,12 +579,12 @@ class Session * unset one _SESSION entry 'name' if exists * * @param string $name _SESSION key name to remove - * @return void + * @return Session */ public function unset(string $name): Session { if (!isset($_SESSION[$name])) { - return; + return $this; } $this->restartSession(); unset($_SESSION[$name]); diff --git a/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php b/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php index 82d16213..46b01e7a 100644 --- a/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php +++ b/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php @@ -256,7 +256,7 @@ class AsymmetricAnonymousEncryption * sets the private key for encryption * * @param string $key_pair Key pair in hex - * @return void + * @return AsymmetricAnonymousEncryption * @throws \UnexpectedValueException key pair empty */ public function setKeyPair( @@ -309,7 +309,7 @@ class AsymmetricAnonymousEncryption * extract the public key from the key pair * * @param string $public_key Public Key in hex - * @return void + * @return AsymmetricAnonymousEncryption * @throws \UnexpectedValueException public key empty */ public function setPublicKey(string $public_key): AsymmetricAnonymousEncryption diff --git a/www/lib/CoreLibs/Security/SymmetricEncryption.php b/www/lib/CoreLibs/Security/SymmetricEncryption.php index ed0ca398..08826212 100644 --- a/www/lib/CoreLibs/Security/SymmetricEncryption.php +++ b/www/lib/CoreLibs/Security/SymmetricEncryption.php @@ -209,7 +209,7 @@ class SymmetricEncryption * set a new key for encryption * * @param string $key - * @return void + * @return SymmetricEncryption * @throws \UnexpectedValueException key cannot be empty */ public function setKey( From 10319ef72835b82f6d832ede273a49d9adb871dd Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 23 Dec 2024 12:56:57 +0900 Subject: [PATCH 046/105] Fix throws type for AsymmetricAnonymousEncryption in the phpdoc part --- www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php b/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php index 46b01e7a..a30cb3b1 100644 --- a/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php +++ b/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php @@ -115,7 +115,7 @@ class AsymmetricAnonymousEncryption * @return string * @throws \UnexpectedValueException key pair empty * @throws \UnexpectedValueException invalid hex key pair - * @throws \UnexpectedValueException key pair not correct size + * @throws \RangeException key pair not correct size */ private function createKeyPair( #[\SensitiveParameter] @@ -147,7 +147,7 @@ class AsymmetricAnonymousEncryption * @return string * @throws \UnexpectedValueException public key empty * @throws \UnexpectedValueException invalid hex key - * @throws \UnexpectedValueException invalid key length + * @throws \RangeException invalid key length */ private function createPublicKey(?string $public_key): string { From ce1c72a0bc1dfb49fbdf260c01a0d9b8264d9e8f Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 24 Dec 2024 12:43:30 +0900 Subject: [PATCH 047/105] Bug fix for DB IO parameters in CASE calls --- 4dev/tests/DB/CoreLibsDBIOTest.php | 21 ++++++++++++++++++ www/admin/class_test.db.query-placeholder.php | 22 ++++++++++++++++++- .../DB/Support/ConvertPlaceholder.php | 4 +++- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/4dev/tests/DB/CoreLibsDBIOTest.php b/4dev/tests/DB/CoreLibsDBIOTest.php index 14767292..ac6314d6 100644 --- a/4dev/tests/DB/CoreLibsDBIOTest.php +++ b/4dev/tests/DB/CoreLibsDBIOTest.php @@ -5196,6 +5196,27 @@ final class CoreLibsDBIOTest extends TestCase SQL, 'count' => 1, 'convert' => false, + ], + 'update with case' => [ + 'query' => << 3, + 'convert' => false, + ], + 'select with case' => [ + 'query' => << 2, + 'convert' => false, ] ]; } diff --git a/www/admin/class_test.db.query-placeholder.php b/www/admin/class_test.db.query-placeholder.php index 87b57971..c77cff6a 100644 --- a/www/admin/class_test.db.query-placeholder.php +++ b/www/admin/class_test.db.query-placeholder.php @@ -174,6 +174,26 @@ while (is_array($res = $db->dbReturnParams($query, [$query_value]))) { echo "
"; +echo "CASE part
"; +$query = << 1 THEN $1 + ELSE 1::INT + END)::INT +WHERE + string_a = $2 +SQL; +echo "QUERY:
" . $query . "
"; +$res = $db->dbExecParams($query, [1, 'foobar']); +print "ERROR: " . $db->dbGetLastError(true) . "
"; + +echo "
"; + // test connectors: = , <> () for query detection // convert placeholder tests @@ -237,7 +257,7 @@ SQL, SQL, 'params' => [1, 2, 3, 4, 5, 6], 'direction' => 'pg' - ] + ], ]; $db->dbSetConvertPlaceholder(true); diff --git a/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php b/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php index dff03788..484b7828 100644 --- a/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php +++ b/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php @@ -26,7 +26,9 @@ class ConvertPlaceholder . '&&|' // array overlap . '\-\|\-|' // range overlap for array . '[^-]-{1}|' // single -, used in JSON too - . '->|->>|#>|#>>|@>|<@|@@|@\?|\?{1}|\?\||\?&|#-'; //JSON searches, Array searchs, etc + . '->|->>|#>|#>>|@>|<@|@@|@\?|\?{1}|\?\||\?&|#-|' // JSON searches, Array searchs, etc + . 'THEN|ELSE' // command parts (CASE) + ; /** @var string the main regex including the pattern query split */ private const PATTERN_ELEMENT = '(?:\'.*?\')?\s*(?:' . self::PATTERN_QUERY_SPLIT . ')\s*'; /** @var string comment regex From c778a4eb81620eff076e0f1cafc3c8c2becdba9a Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 27 Dec 2024 09:32:54 +0900 Subject: [PATCH 048/105] Add phive back in for static tools like phpunit instead of using the composer package --- .phive/phars.xml | 8 ++--- www/admin/class_test.db.php | 20 ++++++------- www/admin/class_test.db.types.php | 45 +++++++++++++++++++++++++--- www/admin/class_test.output.form.php | 8 +++-- www/admin/class_test.smarty.php | 2 ++ 5 files changed, 62 insertions(+), 21 deletions(-) diff --git a/.phive/phars.xml b/.phive/phars.xml index e1f29fd0..57263c64 100644 --- a/.phive/phars.xml +++ b/.phive/phars.xml @@ -1,10 +1,10 @@ - + - - - + + + diff --git a/www/admin/class_test.db.php b/www/admin/class_test.db.php index 4f1bfb82..eb9ba2e6 100644 --- a/www/admin/class_test.db.php +++ b/www/admin/class_test.db.php @@ -273,8 +273,8 @@ $query_insert = <<PREPARE QUERIES
"; // READ PREPARE $q_prep = <<dbPrepare('sel_test_foo', $q_prep) === false) { // sel test with ANY () type $q_prep = "SELECT test_foo_id, test, some_bool, string_a, number_a, " - . "number_a_numeric, some_time " + . "numeric_a, some_time " . "FROM test_foo " . "WHERE test = ANY($1) " . "ORDER BY test_foo_id DESC LIMIT 5"; @@ -618,7 +618,7 @@ $test_bar = $db->dbEscapeLiteral('SOMETHING DIFFERENT'); $q = <<"; $q = <<"; print "DB RETURN PARAMS LIKE
"; $q = <<"; print "DB RETURN PARAMS ANY
"; $q = <<dbGetDbh()) instanceof \PgSql\Connection) { print "TRUNCATE test_foo
"; $db->dbExec("TRUNCATE test_foo"); +/* +BELOW IS THE FULL TABLE WITH ALL PostgreSQL Types +=> \d test_foo + Table "public.test_foo" +Column | Type | Nullable | Default +------------------+-----------------------------+----------+----------------------------------------------- +test | character varying | | +some_bool | boolean | | +string_a | character varying | | +number_a | integer | | +numeric_a | numeric | | +some_internval | interval | | +test_foo_id | integer | not null | generated always as identity +json_string | jsonb | | +some_timestamp | timestamp without time zone | | +some_binary | bytea | | +null_var | character varying | | +smallint_a | smallint | | +number_real | real | | +number_double | double precision | | +number_serial | integer | not null | nextval('test_foo_number_serial_seq'::regclass) +array_char_1 | character varying[] | | +array_char_2 | character varying[] | | +array_int_1 | integer[] | | +array_int_2 | integer[] | | +composite_item | inventory_item | | +array_composite | inventory_item[] | | +numeric_3 | numeric(3,0) | | +identity_always | bigint | not null | generated always as identity +identitiy_default | bigint | not null | generated by default as identity +uuid_var | uuid | | gen_random_uuid() +some_date | date | | +some_time | time without time zone | | +bigint_a | bigint | | +default_uuid | uuid | | gen_random_uuid() +*/ + /* $q = <<"; $query_select = << [ + 'foo', + 'bar' ], // laod query - 'load_query' => '', + 'load_query' => 'SELECT uuid_nr, foo, bar FROM test', // database table to load from - 'table_name' => '', + 'table_name' => 'test', // for load dro pdown, format output 'show_fields' => [ [ - 'name' => 'name' + 'name' => 'foo' ], [ 'name' => 'enabled', diff --git a/www/admin/class_test.smarty.php b/www/admin/class_test.smarty.php index 31635910..718bf3d5 100644 --- a/www/admin/class_test.smarty.php +++ b/www/admin/class_test.smarty.php @@ -4,6 +4,8 @@ * @phan-file-suppress PhanTypeSuspiciousStringExpression */ +// FIXME: Smarty Class must be updated for PHP 8.4 + declare(strict_types=1); error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR); From fbe827e989173ecaeb18834dbade2a1144a91cfe Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 27 Dec 2024 11:30:55 +0900 Subject: [PATCH 049/105] Update Smarty Extended for Smarty-extended v5 upgrade --- www/admin/class_test.smarty.php | 1 + www/composer.json | 2 +- www/lib/CoreLibs/Template/SmartyExtend.php | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/www/admin/class_test.smarty.php b/www/admin/class_test.smarty.php index 718bf3d5..72996954 100644 --- a/www/admin/class_test.smarty.php +++ b/www/admin/class_test.smarty.php @@ -47,6 +47,7 @@ $adm = new CoreLibs\Admin\Backend( ); $adm->DATA['adm_set'] = 'SET from admin class'; + $PAGE_NAME = 'TEST CLASS: SMARTY'; print ""; print "" . $PAGE_NAME . ""; diff --git a/www/composer.json b/www/composer.json index 74c42f31..8fe948fd 100644 --- a/www/composer.json +++ b/www/composer.json @@ -21,7 +21,7 @@ } }, "require": { - "egrajp/smarty-extended": "^4.3", + "egrajp/smarty-extended": "^5.4", "php": ">=8.1", "gullevek/dotenv": "^2.0", "psr/log": "^2.0 || ^3.0" diff --git a/www/lib/CoreLibs/Template/SmartyExtend.php b/www/lib/CoreLibs/Template/SmartyExtend.php index e9b906d6..5b5bfd5d 100644 --- a/www/lib/CoreLibs/Template/SmartyExtend.php +++ b/www/lib/CoreLibs/Template/SmartyExtend.php @@ -20,7 +20,7 @@ declare(strict_types=1); namespace CoreLibs\Template; // leading slash if this is in lib\Smarty -class SmartyExtend extends \Smarty +class SmartyExtend extends \Smarty\Smarty { // internal translation engine /** @var \CoreLibs\Language\L10n */ From 9d65f5d7c152e29ca12bd1f0ddbd12d8998ef6d2 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 27 Dec 2024 14:00:12 +0900 Subject: [PATCH 050/105] phpunit script update, SmartyExtended allow load of plugins - phpunit has better options set for testdox/php version - SmartyExtended has logger class as option (argument 2) and options - SmartyExtneded can via option set html escape and load of plugins - plugin array is set of - file: path to plugin file - type: what type this is - tag: tag name - callable: the callable for the tag name - will throw exceptions on plugin load - for all other things will set warning only and skip read - fix the Smarty call with the logger option - fix password test for PHP 8.4 password hash change *IMPORTANT* SmartyExtended($l10n, $logger, $cache_id, $compile_id) The second argument is now the Logger class, this MUST be updated for all calls --- 4dev/checking/phan.sh | 4 +- 4dev/checking/phpstan.sh | 4 +- 4dev/checking/phpunit.sh | 53 ++++---- .../Security/CoreLibsSecurityPasswordTest.php | 14 ++ www/admin/class_test.password.php | 5 + www/admin/class_test.smarty.php | 1 + www/includes/admin_header.php | 2 +- www/lib/CoreLibs/Admin/EditBase.php | 3 +- www/lib/CoreLibs/Template/SmartyExtend.php | 120 ++++++++++++++++-- 9 files changed, 157 insertions(+), 49 deletions(-) diff --git a/4dev/checking/phan.sh b/4dev/checking/phan.sh index 5c788c16..856a20c3 100755 --- a/4dev/checking/phan.sh +++ b/4dev/checking/phan.sh @@ -1,5 +1,5 @@ base="/storage/var/www/html/developers/clemens/core_data/php_libraries/trunk/"; # must be run in ${base} -cd $base; +cd $base || exit; ${base}tools/phan --progress-bar -C --analyze-twice; -cd ~; +cd ~ || exit; diff --git a/4dev/checking/phpstan.sh b/4dev/checking/phpstan.sh index 079bbf6c..00481ad8 100755 --- a/4dev/checking/phpstan.sh +++ b/4dev/checking/phpstan.sh @@ -1,5 +1,5 @@ base="/storage/var/www/html/developers/clemens/core_data/php_libraries/trunk/"; # must be run in ${base} -cd $base; +cd $base || exit; ${base}tools/phpstan; -cd ~; +cd ~ || exit; diff --git a/4dev/checking/phpunit.sh b/4dev/checking/phpunit.sh index 0fbf93d8..b0295408 100755 --- a/4dev/checking/phpunit.sh +++ b/4dev/checking/phpunit.sh @@ -3,47 +3,44 @@ base="/storage/var/www/html/developers/clemens/core_data/php_libraries/trunk/"; # -c phpunit.xml # --testdox -# call with "t" to give verbose testdox output +# call with "-tt" to give verbose testdox output # SUPPORTED: https://www.php.net/supported-versions.php -# call with php version number to force a certain php version +# call with -p to force a certain php version opt_testdox=""; -if [ "${1}" = "t" ] || [ "${2}" = "t" ]; then - opt_testdox="--testdox"; -fi; php_bin=""; -if [ -n "${1}" ]; then +while [ -n "${1-}" ]; do case "${1}" in - # "7.3") php_bin="/usr/bin/php7.3 "; ;; - # "7.4") php_bin="/usr/bin/php7.4 "; ;; - # "8.0") php_bin="/usr/bin/php8.0 "; ;; - # "8.1") php_bin="/usr/bin/php8.1 "; ;; - "8.2") php_bin="/usr/bin/php8.2 "; ;; - "8.3") php_bin="/usr/bin/php8.4 "; ;; - *) echo "Not support PHP: ${1}"; exit; ;; - esac; -fi; -if [ -n "${2}" ] && [ -z "${php_bin}" ]; then - case "${2}" in - # "7.3") php_bin="/usr/bin/php7.3 "; ;; - # "7.4") php_bin="/usr/bin/php7.4 "; ;; - # "8.0") php_bin="/usr/bin/php8.0 "; ;; - # "8.1") php_bin="/usr/bin/php8.1 "; ;; - "8.2") php_bin="/usr/bin/php8.2 "; ;; - "8.3") php_bin="/usr/bin/php8.3 "; ;; - *) echo "Not support PHP: ${1}"; exit; ;; - esac; + -t | --testdox) + opt_testdox="--testdox"; + ;; + -p | --php) + php_bin="/usr/bin/php${2-}"; + shift + ;; + # invalid option + -?*) + error "[!] Unknown option: '$1'." + ;; + esac + shift; +done; + +if [ ! -f "${php_bin}" ]; then + echo "Set php ${php_bin} does not exist"; + exit; fi; +php_bin="${php_bin} "; # Note 4dev/tests/bootstrap.php has to be set as bootstrap file in phpunit.xml phpunit_call="${php_bin}${base}vendor/bin/phpunit ${opt_testdox} -c ${base}phpunit.xml ${base}4dev/tests/"; ${phpunit_call}; -if [ ! -z "${php_bin}" ]; then - echo "CALLED WITH PHP: ${php_bin}"$(${php_bin} --version); +if [ -n "${php_bin}" ]; then + echo "CALLED WITH PHP: ${php_bin}$(${php_bin} --version)"; else - echo "Default PHP used: "$(php --version); + echo "Default PHP used: $(php --version)"; fi; # __END__ diff --git a/4dev/tests/Security/CoreLibsSecurityPasswordTest.php b/4dev/tests/Security/CoreLibsSecurityPasswordTest.php index 80adec64..7904997a 100644 --- a/4dev/tests/Security/CoreLibsSecurityPasswordTest.php +++ b/4dev/tests/Security/CoreLibsSecurityPasswordTest.php @@ -13,6 +13,11 @@ use PHPUnit\Framework\TestCase; */ final class CoreLibsSecurityPasswordTest extends TestCase { + /** + * Undocumented function + * + * @return array + */ public function passwordProvider(): array { return [ @@ -21,6 +26,11 @@ final class CoreLibsSecurityPasswordTest extends TestCase ]; } + /** + * Note: we need different hash types for PHP versions + * + * @return array + */ public function passwordRehashProvider(): array { return [ @@ -63,6 +73,10 @@ final class CoreLibsSecurityPasswordTest extends TestCase */ public function testPasswordRehashCheck(string $input, bool $expected): void { + // in PHP 8.4 the length is $12 + if (PHP_VERSION_ID > 80400) { + $input = str_replace('$2y$10$', '$2y$12$', $input); + } $this->assertEquals( $expected, \CoreLibs\Security\Password::passwordRehashCheck($input) diff --git a/www/admin/class_test.password.php b/www/admin/class_test.password.php index b1aa9de1..7a9a4556 100644 --- a/www/admin/class_test.password.php +++ b/www/admin/class_test.password.php @@ -51,6 +51,11 @@ print "PASSWORD REHASH: " . (string)$password_class::passwordRehashCheck($enc_pa // direct static print "S::PASSWORD VERFIY: " . (string)PwdChk::passwordVerify($password, $enc_password) . "
"; +$rehash_test = '$2y$10$EgWJ2WE73DWi.hIyFRCdpejLXTvHbmTK3LEOclO1tAvXAXUNuUS4W'; +if (PwdChk::passwordRehashCheck($rehash_test)) { + print "Bad password
"; +} + print ""; // __END__ diff --git a/www/admin/class_test.smarty.php b/www/admin/class_test.smarty.php index 72996954..49e95ae9 100644 --- a/www/admin/class_test.smarty.php +++ b/www/admin/class_test.smarty.php @@ -35,6 +35,7 @@ $l10n = new \CoreLibs\Language\L10n( ); $smarty = new CoreLibs\Template\SmartyExtend( $l10n, + $log, CACHE_ID, COMPILE_ID, ); diff --git a/www/includes/admin_header.php b/www/includes/admin_header.php index d6176102..9def1ebe 100644 --- a/www/includes/admin_header.php +++ b/www/includes/admin_header.php @@ -91,7 +91,7 @@ $l10n = new \CoreLibs\Language\L10n( ); // create smarty object -$smarty = new \CoreLibs\Template\SmartyExtend($l10n, CACHE_ID, COMPILE_ID); +$smarty = new \CoreLibs\Template\SmartyExtend($l10n, $log, CACHE_ID, COMPILE_ID); // create new Backend class with db and loger attached $cms = new \CoreLibs\Admin\Backend($db, $log, $session, $l10n, DEFAULT_ACL_LEVEL); // the menu show flag (what menu to show) diff --git a/www/lib/CoreLibs/Admin/EditBase.php b/www/lib/CoreLibs/Admin/EditBase.php index 27d4a162..93d589d7 100644 --- a/www/lib/CoreLibs/Admin/EditBase.php +++ b/www/lib/CoreLibs/Admin/EditBase.php @@ -538,8 +538,7 @@ class EditBase * builds the smarty content and runs smarty display output * * @return void - * @throws Exception - * @throws SmartyException + * @throws \Smarty\Exception */ public function editBaseRun( ?string $template_dir = null, diff --git a/www/lib/CoreLibs/Template/SmartyExtend.php b/www/lib/CoreLibs/Template/SmartyExtend.php index 5b5bfd5d..54b024f6 100644 --- a/www/lib/CoreLibs/Template/SmartyExtend.php +++ b/www/lib/CoreLibs/Template/SmartyExtend.php @@ -19,12 +19,13 @@ declare(strict_types=1); namespace CoreLibs\Template; -// leading slash if this is in lib\Smarty class SmartyExtend extends \Smarty\Smarty { // internal translation engine - /** @var \CoreLibs\Language\L10n */ + /** @var \CoreLibs\Language\L10n language class */ public \CoreLibs\Language\L10n $l10n; + /** @var \CoreLibs\Logging\Logging $log logging class */ + public \CoreLibs\Logging\Logging $log; // lang & encoding /** @var string */ @@ -157,14 +158,18 @@ class SmartyExtend extends \Smarty\Smarty * calls L10 for pass on internaly in smarty * also registers the getvar caller plugin * - * @param \CoreLibs\Language\L10n $l10n l10n language class - * @param string|null $cache_id - * @param string|null $compile_id + * @param \CoreLibs\Language\L10n $l10n l10n language class + * @param \CoreLibs\Logging\Logging $log Logger class + * @param string|null $cache_id [default=null] + * @param string|null $compile_id [default=null] + * @param array $options [default=[]] */ public function __construct( \CoreLibs\Language\L10n $l10n, + \CoreLibs\Logging\Logging $log, ?string $cache_id = null, - ?string $compile_id = null + ?string $compile_id = null, + array $options = [] ) { // trigger deprecation if ( @@ -177,14 +182,33 @@ class SmartyExtend extends \Smarty\Smarty E_USER_DEPRECATED ); } - // set variables (to be deprecated) - $cache_id = $cache_id ?? - (defined('CACHE_ID') ? CACHE_ID : ''); - $compile_id = $compile_id ?? - (defined('COMPILE_ID') ? COMPILE_ID : ''); + // set variables from global constants (deprecated) + if ($cache_id === null && defined('CACHE_ID')) { + trigger_error( + 'SmartyExtended: No cache_id set and CACHE_ID constant set, this is deprecated', + E_USER_DEPRECATED + ); + $cache_id = CACHE_ID; + } + if ($compile_id === null && defined('COMPILE_ID')) { + trigger_error( + 'SmartyExtended: No compile_id set and COMPILE_ID constant set, this is deprecated', + E_USER_DEPRECATED + ); + $compile_id = COMPILE_ID; + } + if (empty($cache_id)) { + throw new \BadMethodCallException('cache_id parameter is not set'); + } + if (empty($compile_id)) { + throw new \BadMethodCallException('compile_id parameter is not set'); + } + // call basic smarty - // or Smarty::__construct(); parent::__construct(); + + $this->log = $log; + // init lang $this->l10n = $l10n; // parse and read, legacy stuff @@ -194,7 +218,6 @@ class SmartyExtend extends \Smarty\Smarty $this->lang_short = $locale['lang_short']; $this->domain = $locale['domain']; $this->lang_dir = $locale['path']; - // opt load functions so we can use legacy init for smarty run perhaps \CoreLibs\Language\L10n::loadFunctions(); _setlocale(LC_MESSAGES, $locale['locale']); @@ -203,7 +226,6 @@ class SmartyExtend extends \Smarty\Smarty _bind_textdomain_codeset($this->domain, $this->encoding); // register smarty variable - // $this->registerPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, 'getvar', [&$this, 'getTemplateVars']); $this->registerPlugin(self::PLUGIN_MODIFIER, 'getvar', [&$this, 'getTemplateVars']); $this->page_name = \CoreLibs\Get\System::getPageName(); @@ -211,6 +233,76 @@ class SmartyExtend extends \Smarty\Smarty // set internal settings $this->CACHE_ID = $cache_id; $this->COMPILE_ID = $compile_id; + // set options + $this->setOptions($options); + } + + /** + * set options + * + * @param array $options + * @return void + */ + private function setOptions(array $options): void + { + // set escape html if option is set + if (!empty($options['escape_html'])) { + $this->setEscapeHtml(true); + } + // load plugins + // plugin array: + // 'file': string, path to plugin content to load + // 'type': a valid smarty type see Smarty PLUGIN_ constants for correct names + // 'tag': the smarty tag + // 'callback': the function to call in 'file' + if (!empty($options['plugins'])) { + foreach ($options['plugins'] as $plugin) { + // file is readable + if ( + empty($plugin['file']) || + !is_file($plugin['file']) || + !is_readable($plugin['file']) + ) { + $this->log->warning('SmartyExtended plugin load failed, file not accessable', [ + 'plugin' => $plugin, + ]); + continue; + } + // tag is alphanumeric + if (!preg_match("/^\w+$/", $plugin['tag'] ?? '')) { + $this->log->warning('SmartyExtended plugin load failed, invalid tag', [ + 'plugin' => $plugin, + ]); + continue; + } + // callback is alphanumeric + if (!preg_match("/^\w+$/", $plugin['callback'] ?? '')) { + $this->log->warning('SmartyExtended plugin load failed, invalid callback', [ + 'plugin' => $plugin, + ]); + continue; + } + try { + new \ReflectionClassConstant($this, $plugin['type']); + } catch (\ReflectionException $e) { + $this->log->error('SmartyExtended plugin load failed, type is not valid', [ + 'message' => $e->getMessage(), + 'plugin' => $plugin, + ]); + continue; + } + try { + require $plugin['file']; + $this->registerPlugin($plugin['type'], $plugin['tag'], $plugin['callback']); + } catch (\Smarty\Exception $e) { + $this->log->error('SmartyExtended plugin load failed with exception', [ + 'message' => $e->getMessage(), + 'plugin' => $plugin, + ]); + continue; + } + } + } } /** From de2ed8be3d2b00a84d6f17b754cf5d29b847764f Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 27 Dec 2024 17:07:44 +0900 Subject: [PATCH 051/105] EditBase SmartyExtended class call update --- www/lib/CoreLibs/Admin/EditBase.php | 1 + 1 file changed, 1 insertion(+) diff --git a/www/lib/CoreLibs/Admin/EditBase.php b/www/lib/CoreLibs/Admin/EditBase.php index 93d589d7..e7fb3fd5 100644 --- a/www/lib/CoreLibs/Admin/EditBase.php +++ b/www/lib/CoreLibs/Admin/EditBase.php @@ -63,6 +63,7 @@ class EditBase // smarty template engine (extended Translation version) $this->smarty = new \CoreLibs\Template\SmartyExtend( $l10n, + $log, $options['cache_id'] ?? '', $options['compile_id'] ?? '', ); From 72912c8c90a640e916d6985611ab6666c57cfc40 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 6 Jan 2025 13:52:28 +0900 Subject: [PATCH 052/105] Bad password check for PHP earlier than 8.4 --- www/admin/class_test.password.php | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/www/admin/class_test.password.php b/www/admin/class_test.password.php index 7a9a4556..7176fc80 100644 --- a/www/admin/class_test.password.php +++ b/www/admin/class_test.password.php @@ -37,6 +37,8 @@ print ""; print ''; print '

' . $PAGE_NAME . '

'; +print "PHP Version: " . PHP_VERSION . "
"; + $password = 'something1234'; $enc_password = $_password->passwordSet($password); print "PASSWORD: $password: " . $enc_password . "
"; @@ -51,9 +53,18 @@ print "PASSWORD REHASH: " . (string)$password_class::passwordRehashCheck($enc_pa // direct static print "S::PASSWORD VERFIY: " . (string)PwdChk::passwordVerify($password, $enc_password) . "
"; -$rehash_test = '$2y$10$EgWJ2WE73DWi.hIyFRCdpejLXTvHbmTK3LEOclO1tAvXAXUNuUS4W'; +if (PHP_VERSION_ID < 80400) { + $rehash_test = '$2y$10$EgWJ2WE73DWi.hIyFRCdpejLXTvHbmTK3LEOclO1tAvXAXUNuUS4W'; + $rehash_test_throw = '$2y$12$EgWJ2WE73DWi.hIyFRCdpejLXTvHbmTK3LEOclO1tAvXAXUNuUS4W'; +} else { + $rehash_test = '$2y$12$EgWJ2WE73DWi.hIyFRCdpejLXTvHbmTK3LEOclO1tAvXAXUNuUS4W'; + $rehash_test_throw = '$2y$10$EgWJ2WE73DWi.hIyFRCdpejLXTvHbmTK3LEOclO1tAvXAXUNuUS4W'; +} if (PwdChk::passwordRehashCheck($rehash_test)) { - print "Bad password
"; + print "Bad password [BAD]
"; +} +if (PwdChk::passwordRehashCheck($rehash_test_throw)) { + print "Bad password [OK]
"; } print ""; From 7fbc449a5c643f11b5cb7550b531d47bd1b7cb71 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 15 Jan 2025 11:47:05 +0900 Subject: [PATCH 053/105] PHPunit test call script update Fix for default PHP set via getting version from default PHP. Add a verbose option and remove the fixed verbose setting from the phpunit config Update the options call to add a usage info block --- 4dev/checking/phpunit.sh | 60 ++++++++++++++++++++++++++++++++++++---- phpunit.xml | 2 +- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/4dev/checking/phpunit.sh b/4dev/checking/phpunit.sh index b0295408..40271ec8 100755 --- a/4dev/checking/phpunit.sh +++ b/4dev/checking/phpunit.sh @@ -1,6 +1,40 @@ #!/bin/env bash -base="/storage/var/www/html/developers/clemens/core_data/php_libraries/trunk/"; +function error() { + if [ -t 1 ]; then echo "[MAK] ERROR: $*" >&2; fi; exit 0; +} + +usage() { + cat < to force a certain php version opt_testdox=""; -php_bin=""; +opt_verbose=""; +php_version=""; +no_php_version=0; while [ -n "${1-}" ]; do case "${1}" in -t | --testdox) opt_testdox="--testdox"; ;; + -v | --verbose) + opt_verbose="--verbose"; + ;; -p | --php) - php_bin="/usr/bin/php${2-}"; + php_version="${2-}"; shift ;; + -h | --help) + usage + ;; # invalid option -?*) error "[!] Unknown option: '$1'." @@ -26,6 +68,13 @@ while [ -n "${1-}" ]; do shift; done; +if [ -z "${php_version}" ]; then + php_version="${DEFAULT_PHP_VERSION}"; + no_php_version=1; +fi; +php_bin="${PHP_BIN_PATH}${php_version}"; +echo "Use PHP Version: ${php_version}"; + if [ ! -f "${php_bin}" ]; then echo "Set php ${php_bin} does not exist"; exit; @@ -33,11 +82,12 @@ fi; php_bin="${php_bin} "; # Note 4dev/tests/bootstrap.php has to be set as bootstrap file in phpunit.xml -phpunit_call="${php_bin}${base}vendor/bin/phpunit ${opt_testdox} -c ${base}phpunit.xml ${base}4dev/tests/"; +phpunit_call="${php_bin}${BASE_PATH}vendor/bin/phpunit ${opt_testdox} ${opt_verbose} -c ${PHPUNIT_CONFIG} ${BASE_PATH}4dev/tests/"; ${phpunit_call}; -if [ -n "${php_bin}" ]; then +echo -e "\nPHPUnit Config: ${PHPUNIT_CONFIG}"; +if [ "${no_php_version}" -eq 0 ]; then echo "CALLED WITH PHP: ${php_bin}$(${php_bin} --version)"; else echo "Default PHP used: $(php --version)"; diff --git a/phpunit.xml b/phpunit.xml index 90ff0695..dfc1669b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,7 +1,7 @@ From e063162161602854535bd4c2db0a1c6260d9ced0 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 15 Jan 2025 12:53:02 +0900 Subject: [PATCH 054/105] Remove not needed use parts and ignore noop new for phan check --- www/lib/CoreLibs/Admin/EditBase.php | 3 --- www/lib/CoreLibs/Template/SmartyExtend.php | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/www/lib/CoreLibs/Admin/EditBase.php b/www/lib/CoreLibs/Admin/EditBase.php index e7fb3fd5..20312b52 100644 --- a/www/lib/CoreLibs/Admin/EditBase.php +++ b/www/lib/CoreLibs/Admin/EditBase.php @@ -14,9 +14,6 @@ declare(strict_types=1); namespace CoreLibs\Admin; -use Exception; -use SmartyException; - class EditBase { /** @var array */ diff --git a/www/lib/CoreLibs/Template/SmartyExtend.php b/www/lib/CoreLibs/Template/SmartyExtend.php index 54b024f6..10eaa981 100644 --- a/www/lib/CoreLibs/Template/SmartyExtend.php +++ b/www/lib/CoreLibs/Template/SmartyExtend.php @@ -283,6 +283,7 @@ class SmartyExtend extends \Smarty\Smarty continue; } try { + /** @phan-suppress-next-line PhanNoopNew */ new \ReflectionClassConstant($this, $plugin['type']); } catch (\ReflectionException $e) { $this->log->error('SmartyExtended plugin load failed, type is not valid', [ From 08664e98349fdf2a19d1f80865c7344a503d8956 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 16 Jan 2025 10:40:41 +0900 Subject: [PATCH 055/105] Update log writing for login info Fix the deprecated message in the Admin/Backend one with a full sample Update the admin_header include sample page with the corret writeLog call --- www/includes/admin_header.php | 2 +- www/lib/CoreLibs/Admin/Backend.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/www/includes/admin_header.php b/www/includes/admin_header.php index 9def1ebe..b83f36d5 100644 --- a/www/includes/admin_header.php +++ b/www/includes/admin_header.php @@ -116,7 +116,7 @@ $data = [ // log action // no log if login if (!$login->loginActionRun()) { - $login->writeLog('Submit', $data, $cms->adbGetActionSet(), 'BINARY'); + $login->writeLog('Submit', $data, action_set:$cms->adbGetActionSet(), write_type:'BINARY'); } //------------------------------ logging end diff --git a/www/lib/CoreLibs/Admin/Backend.php b/www/lib/CoreLibs/Admin/Backend.php index 04e084ff..cd64e2e1 100644 --- a/www/lib/CoreLibs/Admin/Backend.php +++ b/www/lib/CoreLibs/Admin/Backend.php @@ -289,7 +289,7 @@ class Backend * JSON, STRING/SERIEAL, BINARY/BZIP or ZLIB * @param string|null $db_schema [default=null] override target schema * @return void - * @deprecated Use $login->writeLog() and set action_set from ->adbGetActionSet() + * @deprecated Use $login->writeLog($event, $data, action_set:$cms->adbGetActionSet(), write_type:$write_type) */ public function adbEditLog( string $event = '', From fd079316f54ca9af5796a4da447d28829cfbe610 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 16 Jan 2025 13:55:17 +0900 Subject: [PATCH 056/105] ACL Login: Add edit_access_id to unit detail block This is needed for a lot of legacy data lookup --- www/lib/CoreLibs/ACL/Login.php | 1 + 1 file changed, 1 insertion(+) diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 0b6c55ad..bb7afaa9 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -1552,6 +1552,7 @@ class Login $this->acl['unit_legacy'][$unit['id']] = $this->acl['unit'][$ea_cuid]; // detail name/level set $this->acl['unit_detail'][$ea_cuid] = [ + 'id' => $unit['id'], 'name' => $unit['name'], 'uid' => $unit['uid'], 'level' => $this->default_acl_list[$this->acl['unit'][$ea_cuid]]['name'] ?? -1, From 053ab6933035beb5f1e50ff35bb5c0220b7b9a2d Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 16 Jan 2025 14:04:30 +0900 Subject: [PATCH 057/105] Add edit access cuuid to the unit detail list --- www/lib/CoreLibs/ACL/Login.php | 1 + 1 file changed, 1 insertion(+) diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index bb7afaa9..b84a7331 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -1555,6 +1555,7 @@ class Login 'id' => $unit['id'], 'name' => $unit['name'], 'uid' => $unit['uid'], + 'cuuid' => $unit['cuuid'], 'level' => $this->default_acl_list[$this->acl['unit'][$ea_cuid]]['name'] ?? -1, 'default' => $unit['default'], 'data' => $unit['data'], From fc105f92953fc5b7c4214083b1d376c8d18375c0 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 16 Jan 2025 14:36:11 +0900 Subject: [PATCH 058/105] Add ACL Login lookup edit access id to edit access cuid --- 4dev/tests/ACL/CoreLibsACLLoginTest.php | 6 ++++++ www/lib/CoreLibs/ACL/Login.php | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/4dev/tests/ACL/CoreLibsACLLoginTest.php b/4dev/tests/ACL/CoreLibsACLLoginTest.php index 1582a1ee..e6abe067 100644 --- a/4dev/tests/ACL/CoreLibsACLLoginTest.php +++ b/4dev/tests/ACL/CoreLibsACLLoginTest.php @@ -1531,6 +1531,12 @@ final class CoreLibsACLLoginTest extends TestCase $login_mock->loginGetEditAccessCuidFromUid($mock_settings['edit_access_uid']), 'Assert check access uid to cuid valid' ); + // - loginGetEditAccessCuidFromId + $this->assertEquals( + $expected['check_access_cuid'], + $login_mock->loginGetEditAccessCuidFromUid($mock_settings['edit_access_id']), + 'Assert check access id to cuid valid' + ); // Deprecated // - loginCheckEditAccess $this->assertEquals( diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index b84a7331..09335563 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -1418,6 +1418,7 @@ class Login 'additional_acl' => Json::jsonConvertToArray($res['additional_acl']), 'data' => $ea_data ]; + // LEGACY LOOKUP $unit_access_eaid[$res['edit_access_id']] = [ 'cuid' => $res['cuid'], ]; @@ -3279,6 +3280,20 @@ HTML; return (int)$_SESSION['LOGIN_UNIT_CUID'][$uid]; } + /** + * Legacy lookup for edit access id to cuid + * + * @param int $id edit access id PK + * @return string|false edit access cuid or false if not found + */ + public function loginGetEditAccessCuidFromId(int $id): string|false + { + if (!isset($_SESSION['LOGIN_UNIT_ACL_LEVEL'][$id])) { + return false; + } + return (string)$_SESSION['LOGIN_UNIT_ACL_LEVEL'][$id]['cuid']; + } + /** * Check if admin flag is set * From 09c2ec653f8634e24ed3d1588479aca8f70c2245 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 16 Jan 2025 14:49:15 +0900 Subject: [PATCH 059/105] ACL Login set deprecated edit user id too We need that for some old calls in old projects --- www/lib/CoreLibs/ACL/Login.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 09335563..a91e5c0d 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -1478,6 +1478,8 @@ class Login // username (login), group name $this->acl['user_name'] = $_SESSION['LOGIN_USER_NAME']; $this->acl['group_name'] = $_SESSION['LOGIN_GROUP_NAME']; + // DEPRECATED + $this->acl['euid'] = $_SESSION['LOGIN_EUID']; // edit user cuid $this->acl['eucuid'] = $_SESSION['LOGIN_EUCUID']; $this->acl['eucuuid'] = $_SESSION['LOGIN_EUCUUID']; From cf6500b55ac54ea0f664b075e39c181c0267a8a7 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 17 Jan 2025 09:08:13 +0900 Subject: [PATCH 060/105] Logging class change to "." for block separator Blocks for info are now separated with "." and not "_" to make it visual more easy to see --- www/lib/CoreLibs/Logging/Logging.php | 44 +++++++++++++++++----------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/www/lib/CoreLibs/Logging/Logging.php b/www/lib/CoreLibs/Logging/Logging.php index d672d9a7..7476ef56 100644 --- a/www/lib/CoreLibs/Logging/Logging.php +++ b/www/lib/CoreLibs/Logging/Logging.php @@ -30,6 +30,10 @@ class Logging { /** @var int minimum size for a max file size, so we don't set 1 byte, 10kb */ public const MIN_LOG_MAX_FILESIZE = 10 * 1024; + /** @var string log file extension, not changeable */ + private const LOG_FILE_NAME_EXT = "log"; + /** @var string log file block separator, not changeable */ + private const LOG_FILE_BLOCK_SEPARATOR = '.'; // 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}} */ @@ -104,8 +108,6 @@ class Logging private string $log_folder = ''; /** @var string a alphanumeric name that has to be set as global definition */ private string $log_file_id = ''; - /** @var string log file name extension */ - private string $log_file_name_ext = 'log'; /** @var string log file name with folder, for actual writing */ private string $log_file_name = ''; /** @var int set in bytes */ @@ -431,7 +433,7 @@ class Logging private function buildLogFileName(Level $level, string $group_id = ''): string { // init base file path - $fn = $this->log_print_file . '.' . $this->log_file_name_ext; + $fn = $this->log_print_file . '.' . self::LOG_FILE_NAME_EXT; // log ID prefix settings, if not valid, replace with empty if (!empty($this->log_file_id)) { $rpl_string = $this->log_file_id; @@ -440,14 +442,15 @@ class Logging } $fn = str_replace('{LOGID}', $rpl_string, $fn); // log id (like a log file prefix) - $rpl_string = !$this->getLogFlag(Flag::per_level) ? '' : - '_' . $level->getName(); + $rpl_string = $this->getLogFlag(Flag::per_level) ? + self::LOG_FILE_BLOCK_SEPARATOR . $level->getName() : + ''; $fn = str_replace('{LEVEL}', $rpl_string, $fn); // create output filename // write per level - $rpl_string = !$this->getLogFlag(Flag::per_group) ? '' : + $rpl_string = $this->getLogFlag(Flag::per_group) ? // normalize level, replace all non alphanumeric characters with - - '_' . ( + self::LOG_FILE_BLOCK_SEPARATOR . ( // if return is only - then set error string preg_match( "/^-+$/", @@ -455,25 +458,29 @@ class Logging ) ? 'INVALID-LEVEL-STRING' : $level_string - ); + ) : + ''; $fn = str_replace('{GROUP}', $rpl_string, $fn); // create output filename // set per class, but don't use get_class as we will only get self - $rpl_string = !$this->getLogFlag(Flag::per_class) ? '' : '_' - // set sub class settings - . str_replace('\\', '-', Support::getCallerTopLevelClass()); + $rpl_string = $this->getLogFlag(Flag::per_class) ? + // set sub class settings + self::LOG_FILE_BLOCK_SEPARATOR . str_replace('\\', '-', Support::getCallerTopLevelClass()) : + ''; $fn = str_replace('{CLASS}', $rpl_string, $fn); // create output filename // if request to write to one file - $rpl_string = !$this->getLogFlag(Flag::per_page) ? - '' : - '_' . System::getPageName(System::NO_EXTENSION); + $rpl_string = $this->getLogFlag(Flag::per_page) ? + self::LOG_FILE_BLOCK_SEPARATOR . System::getPageName(System::NO_EXTENSION) : + ''; $fn = str_replace('{PAGENAME}', $rpl_string, $fn); // create output filename // if run id, we auto add ymd, so we ignore the log file date if ($this->getLogFlag(Flag::per_run)) { - $rpl_string = '_' . $this->getLogUniqueId(); // add 8 char unique string + // add 8 char unique string and date block with time + $rpl_string = self::LOG_FILE_BLOCK_SEPARATOR . $this->getLogUniqueId(); } elseif ($this->getLogFlag(Flag::per_date)) { - $rpl_string = '_' . $this->getLogDate(); // add date to file + // add date to file + $rpl_string = self::LOG_FILE_BLOCK_SEPARATOR . $this->getLogDate(); } else { $rpl_string = ''; } @@ -739,7 +746,10 @@ class Logging { if (empty($this->log_file_unique_id) || $override == true) { $this->log_file_unique_id = - date('Y-m-d_His') . '_U_' + date('Y-m-d_His') + . self::LOG_FILE_BLOCK_SEPARATOR + . 'U_' + // this doesn't have to be unique for everything, just for this logging purpose . substr(hash( 'sha1', random_bytes(63) From 443cc2751d2d5cff942924f330c525a7cf5b55ef Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 17 Jan 2025 09:33:05 +0900 Subject: [PATCH 061/105] Update Logging file name change unit tests --- 4dev/tests/Logging/CoreLibsLoggingLoggingTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/4dev/tests/Logging/CoreLibsLoggingLoggingTest.php b/4dev/tests/Logging/CoreLibsLoggingLoggingTest.php index da99916d..3d2b3894 100644 --- a/4dev/tests/Logging/CoreLibsLoggingLoggingTest.php +++ b/4dev/tests/Logging/CoreLibsLoggingLoggingTest.php @@ -395,7 +395,7 @@ final class CoreLibsLoggingLoggingTest extends TestCase } $per_run_id = $log->getLogUniqueId(); $this->assertMatchesRegularExpression( - "/^\d{4}-\d{2}-\d{2}_\d{6}_U_[a-z0-9]{8}$/", + "/^\d{4}-\d{2}-\d{2}_\d{6}\.U_[a-z0-9]{8}$/", $per_run_id, 'assert per log run id 1st' ); @@ -403,7 +403,7 @@ final class CoreLibsLoggingLoggingTest extends TestCase $log->setLogUniqueId(true); $per_run_id_2nd = $log->getLogUniqueId(); $this->assertMatchesRegularExpression( - "/^\d{4}-\d{2}-\d{2}_\d{6}_U_[a-z0-9]{8}$/", + "/^\d{4}-\d{2}-\d{2}_\d{6}\.U_[a-z0-9]{8}$/", $per_run_id_2nd, 'assert per log run id 2nd' ); @@ -824,13 +824,13 @@ final class CoreLibsLoggingLoggingTest extends TestCase $this->assertTrue($log_ok, 'assert ::log (debug) OK'); $this->assertEquals( $log->getLogFile(), - $log->getLogFileId() . '_DEBUG.log' + $log->getLogFileId() . '.DEBUG.log' ); $log_ok = $log->log(Level::Info, 'INFO', group_id: 'GROUP_ID', prefix: 'PREFIX:'); $this->assertTrue($log_ok, 'assert ::log (info) OK'); $this->assertEquals( $log->getLogFile(), - $log->getLogFileId() . '_INFO.log' + $log->getLogFileId() . '.INFO.log' ); } From d7e64348088afeb2cd589189d647adfdc72c9868 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 17 Jan 2025 09:34:24 +0900 Subject: [PATCH 062/105] New DeprecatedHelper namespace For temporary wrapper functions for deprecated calls that need this PHP 8.4 fputcsv/fgetcsv/str_getcsv encoding default change deprecated warning Note this does not cover the SqlFileInfo class as this is not used in our code --- www/admin/class_test.deprecated.helper.php | 62 ++++++++++++ www/admin/class_test.php | 1 + www/admin/class_test.phpv.php | 2 - .../DeprecatedHelper/Deprecated84.php | 95 +++++++++++++++++++ 4 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 www/admin/class_test.deprecated.helper.php create mode 100644 www/lib/CoreLibs/DeprecatedHelper/Deprecated84.php diff --git a/www/admin/class_test.deprecated.helper.php b/www/admin/class_test.deprecated.helper.php new file mode 100644 index 00000000..2c858be9 --- /dev/null +++ b/www/admin/class_test.deprecated.helper.php @@ -0,0 +1,62 @@ + BASE . LOG, + 'log_file_id' => $LOG_FILE_ID, + 'log_per_date' => true, +]); +$_phpv = new CoreLibs\Check\PhpVersion(); +$phpv_class = 'CoreLibs\Check\PhpVersion'; + +$PAGE_NAME = 'TEST CLASS: PHP VERSION'; +print ""; +print "" . $PAGE_NAME . ""; +print ""; +print ''; +print '

' . $PAGE_NAME . '

'; + +// fputcsv +print "

\CoreLibs\DeprecatedHelper\Deprecated84::fputcsv()

"; +$test_csv = BASE . TMP . 'DeprecatedHelper.test.csv'; +print "File: $test_csv
"; + +$fp = fopen($test_csv, "w"); +if (!is_resource($fp)) { + die("Cannot open file: $test_csv"); +} +\CoreLibs\DeprecatedHelper\Deprecated84::fputcsv($fp, ["A", "B", "C"]); +fclose($fp); + +$fp = fopen($test_csv, "r"); +if (!is_resource($fp)) { + die("Cannot open file: $test_csv"); +} +while ($entry = \CoreLibs\DeprecatedHelper\Deprecated84::fgetcsv($fp)) { + print "fgetcsv:
" . print_r($entry, true) . "
"; +} +fclose($fp); + +$out = \CoreLibs\DeprecatedHelper\Deprecated84::str_getcsv("A,B,C"); +print "str_getcsv:
" . print_r($out, true) . "
"; + +print ""; + +// __END__ diff --git a/www/admin/class_test.php b/www/admin/class_test.php index a54b2bce..8772e07b 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -141,6 +141,7 @@ $test_files = [ 'class_test.error_msg.php' => 'Class Test: ERROR MSG', 'class_test.url-requests.curl.php' => 'Class Test: URL REQUESTS: CURL', 'subfolder/class_test.config.direct.php' => 'Class Test: CONFIG DIRECT SUB', + 'class_test.deprecated.helper.php' => 'Class Test: DEPRECATED HELPERS', ]; asort($test_files); diff --git a/www/admin/class_test.phpv.php b/www/admin/class_test.phpv.php index 9e0d077d..648dca85 100644 --- a/www/admin/class_test.phpv.php +++ b/www/admin/class_test.phpv.php @@ -28,8 +28,6 @@ $log = new CoreLibs\Logging\Logging([ $_phpv = new CoreLibs\Check\PhpVersion(); $phpv_class = 'CoreLibs\Check\PhpVersion'; -// define a list of from to color sets for conversion test - $PAGE_NAME = 'TEST CLASS: PHP VERSION'; print ""; print "" . $PAGE_NAME . ""; diff --git a/www/lib/CoreLibs/DeprecatedHelper/Deprecated84.php b/www/lib/CoreLibs/DeprecatedHelper/Deprecated84.php new file mode 100644 index 00000000..ce01faca --- /dev/null +++ b/www/lib/CoreLibs/DeprecatedHelper/Deprecated84.php @@ -0,0 +1,95 @@ + $fields + * @param string $separator + * @param string $enclosure + * @param string $escape + * @param string $eol + * @return int|false + * @throws InvalidArgumentException + */ + public static function fputcsv( + mixed $stream, + array $fields, + string $separator = ",", + string $enclosure = '"', + string $escape = '', // set to empty for future compatible + string $eol = PHP_EOL + ): int | false { + if (!is_resource($stream)) { + throw new \InvalidArgumentException("fputcsv stream parameter must be a resrouce"); + } + return fputcsv($stream, $fields, $separator, $enclosure, $escape, $eol); + } + + /** + * This is a wrapper for fgetcsv to fix deprecated warning for $escape parameter + * See: https://www.php.net/manual/en/function.fgetcsv.php + * escape parameter deprecation and recommend to set to "" for compatible with PHP 9.0 + * + * @param mixed $stream + * @param null|int $length + * @param string $separator + * @param string $enclosure + * @param string $escape + * @return array|false + * @throws InvalidArgumentException + */ + public static function fgetcsv( + mixed $stream, + ?int $length = null, + string $separator = ',', + string $enclosure = '"', + string $escape = '' // set to empty for future compatible + ): array | false { + if (!is_resource($stream)) { + throw new \InvalidArgumentException("fgetcsv stream parameter must be a resrouce"); + } + return fgetcsv($stream, $length, $separator, $enclosure, $escape); + } + + /** + * This is a wrapper for str_getcsv to fix deprecated warning for $escape parameter + * See: https://www.php.net/manual/en/function.str-getcsv.php + * escape parameter deprecation and recommend to set to "" for compatible with PHP 9.0 + * + * @param string $string + * @param string $separator + * @param string $enclosure + * @param string $escape + * @return array + */ + // phpcs:disable PSR1.Methods.CamelCapsMethodName + public static function str_getcsv( + string $string, + string $separator = ",", + string $enclosure = '"', + string $escape = '' // set to empty for future compatible + ): array { + return str_getcsv($string, $separator, $enclosure, $escape); + } + // phpcs:enable PSR1.Methods.CamelCapsMethodName +} + +// __END__ From 4e78b21c67b5280e2798aa60faa9488f397ac67b Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 17 Jan 2025 09:59:39 +0900 Subject: [PATCH 063/105] phpstan fix for fegetcsv param $length --- www/lib/CoreLibs/DeprecatedHelper/Deprecated84.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/lib/CoreLibs/DeprecatedHelper/Deprecated84.php b/www/lib/CoreLibs/DeprecatedHelper/Deprecated84.php index ce01faca..c9de92c7 100644 --- a/www/lib/CoreLibs/DeprecatedHelper/Deprecated84.php +++ b/www/lib/CoreLibs/DeprecatedHelper/Deprecated84.php @@ -49,7 +49,7 @@ class Deprecated84 * escape parameter deprecation and recommend to set to "" for compatible with PHP 9.0 * * @param mixed $stream - * @param null|int $length + * @param null|int<0,max> $length * @param string $separator * @param string $enclosure * @param string $escape From 757d7ae01d5e9915aa8472f4c9ce8121722ddb47 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 17 Jan 2025 12:48:46 +0900 Subject: [PATCH 064/105] ACL Login fixes for legacy id lookups add an edit access id lookup to cuid Fix unit_cuid not initialized, only old unit_id --- www/lib/CoreLibs/ACL/Login.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index a91e5c0d..86e44388 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -1532,7 +1532,7 @@ class Login $this->acl['page'] = $_SESSION['LOGIN_PAGES_ACL_LEVEL'][$this->page_name]; } - $this->acl['unit_id'] = null; + $this->acl['unit_cuid'] = null; $this->acl['unit_name'] = null; $this->acl['unit_uid'] = null; $this->acl['unit'] = []; @@ -3296,6 +3296,20 @@ HTML; return (string)$_SESSION['LOGIN_UNIT_ACL_LEVEL'][$id]['cuid']; } + /** + * This is a Legacy lookup from the edit access id to cuid for further lookups in the normal list + * + * @param string $cuid edit access cuid + * @return int|false false on not found or edit access id PK + */ + public function loginGetEditAccessIdFromCuid(string $cuid): int|false + { + if (!isset($_SESSION['LOGIN_UNIT_LEGACY'][$cuid])) { + return false; + } + return $_SESSION['LOGIN_UNIT_LEGACY'][$cuid]; + } + /** * Check if admin flag is set * From 0ff6294faaadda9b6a74602a124abe9e261b504d Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 17 Jan 2025 14:34:41 +0900 Subject: [PATCH 065/105] Fix ACL Login cuid <-> id pk lookups Used the wrong SESSION var for lookup --- www/admin/class_test.login.php | 12 +++++++++++- www/lib/CoreLibs/ACL/Login.php | 8 ++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/www/admin/class_test.login.php b/www/admin/class_test.login.php index 2049635e..31a7bfea 100644 --- a/www/admin/class_test.login.php +++ b/www/admin/class_test.login.php @@ -117,7 +117,7 @@ if (isset($login->loginGetAcl()['unit'])) { if ($login->loginCheckEditAccessCuid($edit_access_cuid)) { print "Set new:" . $edit_access_cuid . "
"; } else { - print "Load default unit id: " . $login->loginGetAcl()['unit_id'] . "
"; + print "Load default unit id: " . $login->loginGetAcl()['unit_cuid'] . "
"; } } else { print "Something went wrong with the login
"; @@ -140,4 +140,14 @@ $login->writeLog( write_type:'JSON' ); +echo "
"; +print "

Legacy Lookups

"; + +$edit_access_id = 1; +$edit_access_cuid = $login->loginGetEditAccessCuidFromId($edit_access_id); +$edit_access_id_rev = $login->loginGetEditAccessIdFromCuid($edit_access_cuid); +print "EA ID: " . $edit_access_id . "
"; +print "EA CUID: " . $edit_access_cuid . "
"; +print "REV EA CUID: " . $edit_access_id_rev . "
"; + print ""; diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 86e44388..fbf5870b 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -3290,10 +3290,10 @@ HTML; */ public function loginGetEditAccessCuidFromId(int $id): string|false { - if (!isset($_SESSION['LOGIN_UNIT_ACL_LEVEL'][$id])) { + if (!isset($_SESSION['LOGIN_UNIT_LEGACY'][$id])) { return false; } - return (string)$_SESSION['LOGIN_UNIT_ACL_LEVEL'][$id]['cuid']; + return (string)$_SESSION['LOGIN_UNIT_LEGACY'][$id]['cuid']; } /** @@ -3304,10 +3304,10 @@ HTML; */ public function loginGetEditAccessIdFromCuid(string $cuid): int|false { - if (!isset($_SESSION['LOGIN_UNIT_LEGACY'][$cuid])) { + if (!isset($_SESSION['LOGIN_UNIT'][$cuid])) { return false; } - return $_SESSION['LOGIN_UNIT_LEGACY'][$cuid]; + return $_SESSION['LOGIN_UNIT'][$cuid]['id']; } /** From a345d713064d5f9e40a27400c6dcd0d62af9f9d1 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 17 Jan 2025 14:43:13 +0900 Subject: [PATCH 066/105] De-depricate the ACL Login loginCheckEditAccessId method This is still used a lot, and there is no reason to deprecate it so early. First all the other logic should be brought in to make this an easy conversion. --- www/lib/CoreLibs/ACL/Login.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index fbf5870b..3eff9917 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -3217,7 +3217,7 @@ HTML; * @return int|null same edit access id if ok * or the default edit access id * if given one is not valid - * @deprecated Please switch to using edit access cuid check with ->loginCheckEditAccessValidCuid() + * @#deprecated Please switch to using edit access cuid check with ->loginCheckEditAccessValidCuid() */ public function loginCheckEditAccessId(?int $edit_access_id): ?int { From 8bde34ec7d2487db58d59f2b1f3e42cd100e9eec Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 17 Jan 2025 17:52:41 +0900 Subject: [PATCH 067/105] Fix bug in DB IO prepared statement with INSERT and auto RETURNING add INSERT will get a RETURNING added automatically if it has a primary key This was not checked when query was compared for prepared statements. Also added a prepared statement status checker --- 4dev/tests/DB/CoreLibsDBIOTest.php | 92 +++++++++++++++++++++- www/admin/class_test.db.php | 11 +++ www/admin/class_test.deprecated.helper.php | 45 +++++++++++ www/admin/class_test.login.php | 9 ++- www/lib/CoreLibs/DB/IO.php | 35 +++++++- 5 files changed, 186 insertions(+), 6 deletions(-) diff --git a/4dev/tests/DB/CoreLibsDBIOTest.php b/4dev/tests/DB/CoreLibsDBIOTest.php index ac6314d6..d85de03f 100644 --- a/4dev/tests/DB/CoreLibsDBIOTest.php +++ b/4dev/tests/DB/CoreLibsDBIOTest.php @@ -3692,7 +3692,7 @@ final class CoreLibsDBIOTest extends TestCase * * @return array */ - public function preparedProviderValue(): array + public function providerDbGetPrepareCursorValue(): array { // 1: query (can be empty for do not set) // 2: stm name @@ -3736,7 +3736,7 @@ final class CoreLibsDBIOTest extends TestCase * test return prepare cursor errors * * @covers ::dbGetPrepareCursorValue - * @dataProvider preparedProviderValue + * @dataProvider providerDbGetPrepareCursorValue * @testdox prepared query $stm_name with $key expect error id $error_id [$_dataName] * * @param string $query @@ -3769,6 +3769,94 @@ final class CoreLibsDBIOTest extends TestCase ); } + /** + * Undocumented function + * + * @return array + */ + public function providerDbPreparedCursorStatus(): array + { + return [ + 'empty statement pararm' => [ + 'query' => 'SELECT row_int, uid FROM table_with_primary_key', + 'stm_name' => 'test_stm_a', + 'check_stm_name' => '', + 'check_query' => '', + 'expected' => false + ], + 'different stm_name' => [ + 'query' => 'SELECT row_int, uid FROM table_with_primary_key', + 'stm_name' => 'test_stm_b', + 'check_stm_name' => 'other_name', + 'check_query' => '', + 'expected' => 0 + ], + 'same stm_name' => [ + 'query' => 'SELECT row_int, uid FROM table_with_primary_key', + 'stm_name' => 'test_stm_c', + 'check_stm_name' => 'test_stm_c', + 'check_query' => '', + 'expected' => 1 + ], + 'same stm_name and query' => [ + 'query' => 'SELECT row_int, uid FROM table_with_primary_key', + 'stm_name' => 'test_stm_d', + 'check_stm_name' => 'test_stm_d', + 'check_query' => 'SELECT row_int, uid FROM table_with_primary_key', + 'expected' => 2 + ], + 'same stm_name and different query' => [ + 'query' => 'SELECT row_int, uid FROM table_with_primary_key', + 'stm_name' => 'test_stm_e', + 'check_stm_name' => 'test_stm_e', + 'check_query' => 'SELECT row_int, uid, row_int FROM table_with_primary_key', + 'expected' => 1 + ], + 'insert query test' => [ + 'query' => 'INSERT INTO table_with_primary_key (row_int, uid) VALUES ($1, $2)', + 'stm_name' => 'test_stm_f', + 'check_stm_name' => 'test_stm_f', + 'check_query' => 'INSERT INTO table_with_primary_key (row_int, uid) VALUES ($1, $2)', + 'expected' => 2 + ] + ]; + } + + /** + * test cursor status for prepared statement + * + * @covers ::dbPreparedCursorStatus + * @dataProvider providerDbPreparedCursorStatus + * @testdox Check prepared $stm_name ($check_stm_name) status is $expected [$_dataName] + * + * @param string $query + * @param string $stm_name + * @param string $check_stm_name + * @param string $check_query + * @param bool|int $expected + * @return void + */ + public function testDbPreparedCursorStatus( + string $query, + string $stm_name, + string $check_stm_name, + string $check_query, + bool|int $expected + ): void { + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + $db->dbPrepare($stm_name, $query); + // $db->dbExecute($stm_name); + $this->assertEquals( + $expected, + $db->dbPreparedCursorStatus($check_stm_name, $check_query), + 'check prepared stement cursor status' + ); + unset($db); + } + // - schema set/get tests // dbGetSchema, dbSetSchema diff --git a/www/admin/class_test.db.php b/www/admin/class_test.db.php index eb9ba2e6..a5537600 100644 --- a/www/admin/class_test.db.php +++ b/www/admin/class_test.db.php @@ -707,6 +707,17 @@ if ( } else { print "[PGB] [3] pgb_sel_test_foo prepare OK
"; } +$stm_status = $db->dbPreparedCursorStatus(''); +print "[PGB] Empty statement name: " . $log->prAr($stm_status) . "
"; +$stm_status = $db->dbPreparedCursorStatus('pgb_sel_test_foobar'); +print "[PGB] Prepared name not match status: $stm_status
"; +$stm_status = $db->dbPreparedCursorStatus('pgb_sel_test_foo'); +print "[PGB] Prepared name match status: $stm_status
"; +$stm_status = $db->dbPreparedCursorStatus('pgb_sel_test_foo', $q_prep); +print "[PGB] prepared exists and query match status: $stm_status
"; +$stm_status = $db->dbPreparedCursorStatus('pgb_sel_test_foo', "SELECT * FROM test_foo"); +print "[PGB] prepared exists and query not match status: $stm_status
"; + $db_pgb->dbClose(); # db write class test diff --git a/www/admin/class_test.deprecated.helper.php b/www/admin/class_test.deprecated.helper.php index 2c858be9..b629ff05 100644 --- a/www/admin/class_test.deprecated.helper.php +++ b/www/admin/class_test.deprecated.helper.php @@ -57,6 +57,51 @@ fclose($fp); $out = \CoreLibs\DeprecatedHelper\Deprecated84::str_getcsv("A,B,C"); print "str_getcsv:
" . print_r($out, true) . "
"; +/** + * temporary different CSV function, because fgetcsv seems to be broken on some systems + * (does not read out japanese text) + * + * @param string $string full line for csv split + * @param string $encoding optional, if given, converts string to the internal encoding + * before we do anything + * @param string $delimiter sepperate character, default ',' + * @param string $enclosure string line marker, default '"' + * @param string $flag INTERN | EXTERN. if INTERN uses the PHP function, else uses explode + * @return array array with split data from input line + */ +function mtParseCSV( + string $string, + string $encoding = '', + string $delimiter = ',', + string $enclosure = '"', + string $flag = 'INTERN' +): array { + $lines = []; + if ($encoding) { + $string = \CoreLibs\Convert\Encoding::convertEncoding( + $string, + 'UTF-8', + $encoding + ); + } + if ($flag == 'INTERN') { + // split with PHP function + $lines = str_getcsv($string, $delimiter, $enclosure); + } else { + // split up with delimiter + $lines = explode(',', $string) ?: []; + } + // strip " from beginning and end of line + for ($i = 0; $i < count($lines); $i++) { + // remove line breaks + $lines[$i] = preg_replace("/\r\n?/", '', (string)$lines[$i]) ?? ''; + // lingering " at the beginning and end of the line + $lines[$i] = preg_replace("/^\"/", '', (string)$lines[$i]) ?? ''; + $lines[$i] = preg_replace("/\"$/", '', (string)$lines[$i]) ?? ''; + } + return $lines; +} + print ""; // __END__ diff --git a/www/admin/class_test.login.php b/www/admin/class_test.login.php index 31a7bfea..5b717b0b 100644 --- a/www/admin/class_test.login.php +++ b/www/admin/class_test.login.php @@ -145,9 +145,12 @@ print "

Legacy Lookups

"; $edit_access_id = 1; $edit_access_cuid = $login->loginGetEditAccessCuidFromId($edit_access_id); -$edit_access_id_rev = $login->loginGetEditAccessIdFromCuid($edit_access_cuid); +$edit_access_id_rev = null; +if (is_string($edit_access_cuid)) { + $edit_access_id_rev = $login->loginGetEditAccessIdFromCuid($edit_access_cuid); +} print "EA ID: " . $edit_access_id . "
"; -print "EA CUID: " . $edit_access_cuid . "
"; -print "REV EA CUID: " . $edit_access_id_rev . "
"; +print "EA CUID: " . $log->prAr($edit_access_cuid) . "
"; +print "REV EA CUID: " . $log->prAr($edit_access_id_rev) . "
"; print ""; diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index 4daad5b8..a0ae64de 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -3141,6 +3141,7 @@ class IO 'pk_name' => '', 'count' => 0, 'query' => '', + 'query_raw' => $query, 'result' => null, 'returning_id' => false, 'placeholder_converted' => [], @@ -3237,11 +3238,12 @@ class IO } } else { // if we try to use the same statement name for a differnt query, error abort - if ($this->prepare_cursor[$stm_name]['query'] != $query) { + if ($this->prepare_cursor[$stm_name]['query_raw'] != $query) { // thrown error $this->__dbError(26, false, context: [ 'statement_name' => $stm_name, 'prepared_query' => $this->prepare_cursor[$stm_name]['query'], + 'prepared_query_raw' => $this->prepare_cursor[$stm_name]['query_raw'], 'query' => $query, 'pk_name' => $pk_name, ]); @@ -4364,6 +4366,37 @@ class IO return $this->prepare_cursor[$stm_name][$key]; } + /** + * Checks if a prepared query eixsts + * + * @param string $stm_name Statement to check + * @param string $query [default=''] If set then query must also match + * @return false|int<0,2> False on missing stm_name + * 0: ok, 1: stm_name matchin, 2: stm_name and query matching + */ + public function dbPreparedCursorStatus(string $stm_name, string $query = ''): false|int + { + if (empty($stm_name)) { + $this->__dbError( + 101, + false, + 'No statement name given' + ); + return false; + } + // does not exist + $return_value = 0; + if (!empty($this->prepare_cursor[$stm_name]['query_raw'])) { + // statement name eixts + $return_value = 1; + if ($this->prepare_cursor[$stm_name]['query_raw'] == $query) { + // query also matches + $return_value = 2; + } + } + return $return_value; + } + // *************************** // ERROR AND WARNING DATA // *************************** From bcde36ac1794a8f22723802ec0fc4ba2ba0ffb20 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 20 Jan 2025 10:45:31 +0900 Subject: [PATCH 068/105] DB IO Cache reset should not be an error If the query is not found, do not throw an error, just show a warning --- www/lib/CoreLibs/DB/IO.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index a0ae64de..038f32d8 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -2946,7 +2946,7 @@ class IO $query_hash = $this->dbGetQueryHash($query, $params); // clears cache for this query if (empty($this->cursor_ext[$query_hash]['query'])) { - $this->__dbError(18, context: [ + $this->__dbWarning(18, context: [ 'query' => $query, 'params' => $params, 'hash' => $query_hash, From e7dd96b5d9c43c57285797b12b8e40e566070587 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 20 Jan 2025 20:27:03 +0900 Subject: [PATCH 069/105] Further fixes for PHP 8.4 --- .../AAASetupData/requests/http_requests.php | 4 +- .../CoreLibsUrlRequestsCurlTest.php | 87 +++++++++++++------ www/admin/UrlRequests.target.php | 4 +- www/configs/config.master.php | 4 +- www/lib/CoreLibs/Admin/EditBase.php | 2 +- www/lib/CoreLibs/DB/IO.php | 2 +- 6 files changed, 68 insertions(+), 35 deletions(-) diff --git a/4dev/tests/AAASetupData/requests/http_requests.php b/4dev/tests/AAASetupData/requests/http_requests.php index 912d715f..9523a60d 100644 --- a/4dev/tests/AAASetupData/requests/http_requests.php +++ b/4dev/tests/AAASetupData/requests/http_requests.php @@ -48,7 +48,7 @@ header("Content-Type: application/json; charset=UTF-8"); if (!empty($http_headers['HTTP_AUTHORIZATION']) && !empty($http_headers['HTTP_RUNAUTHTEST'])) { header("HTTP/1.1 401 Unauthorized"); print buildContent($http_headers, '{"code": 401, "content": {"Error": "Not Authorized"}}'); - exit; + exit(1); } // if server request type is get set file_get to null -> no body @@ -57,7 +57,7 @@ if ($_SERVER['REQUEST_METHOD'] == "GET") { } elseif (($file_get = file_get_contents('php://input')) === false) { header("HTTP/1.1 404 Not Found"); print buildContent($http_headers, '{"code": 404, "content": {"Error": "file_get_contents failed"}}'); - exit; + exit(1); } print buildContent($http_headers, $file_get); diff --git a/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php b/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php index 276b2ef6..77077b71 100644 --- a/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php +++ b/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php @@ -969,44 +969,77 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase "query" => ["foo-get" => "bar"] ]); $this->assertEquals("200", $response["code"], "multi call: get response code not matching"); - $this->assertEquals( - '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' - . '"HTTP_FIRST_CALL":"get","HTTP_ACCEPT":"*\/*",' - . '"HTTP_HOST":"soba.egplusww.jp"},' - . '"REQUEST_TYPE":"GET",' - . '"PARAMS":{"foo-get":"bar"},"BODY":null}', - $response['content'], - 'multi call: get content not matching' - ); + print "PHP: " . PHP_VERSION_ID . "\n"; + if (PHP_VERSION_ID >= 80400) { + $this->assertEquals( + '{"HEADERS":{"HTTP_HOST":"soba.egplusww.jp",' + . '"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_FIRST_CALL":"get",' + . '"HTTP_ACCEPT":"*\/*"},"REQUEST_TYPE":"GET","PARAMS":{"foo-get":"bar"},"BODY":null}', + $response['content'], + 'multi call: get content not matching' + ); + } else { + $this->assertEquals( + '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' + . '"HTTP_FIRST_CALL":"get","HTTP_ACCEPT":"*\/*",' + . '"HTTP_HOST":"soba.egplusww.jp"},' + . '"REQUEST_TYPE":"GET",' + . '"PARAMS":{"foo-get":"bar"},"BODY":null}', + $response['content'], + 'multi call: get content not matching' + ); + } // post $response = $curl->post($this->url_basic, [ "headers" => ["second-call" => "post"], "body" => ["foo-post" => "baz"] ]); $this->assertEquals("200", $response["code"], "multi call: post response code not matching"); - $this->assertEquals( - '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' - . '"HTTP_SECOND_CALL":"post","HTTP_ACCEPT":"*\/*",' - . '"HTTP_HOST":"soba.egplusww.jp"},' - . '"REQUEST_TYPE":"POST",' - . '"PARAMS":[],"BODY":{"foo-post":"baz"}}', - $response['content'], - 'multi call: post content not matching' - ); + if (PHP_VERSION_ID >= 80400) { + $this->assertEquals( + '{"HEADERS":{"HTTP_HOST":"soba.egplusww.jp",' + . '"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' + . '"HTTP_SECOND_CALL":"post","HTTP_ACCEPT":"*\/*"},' + . '"REQUEST_TYPE":"POST","PARAMS":[],"BODY":{"foo-post":"baz"}}', + $response['content'], + 'multi call: post content not matching' + ); + } else { + $this->assertEquals( + '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' + . '"HTTP_SECOND_CALL":"post","HTTP_ACCEPT":"*\/*",' + . '"HTTP_HOST":"soba.egplusww.jp"},' + . '"REQUEST_TYPE":"POST",' + . '"PARAMS":[],"BODY":{"foo-post":"baz"}}', + $response['content'], + 'multi call: post content not matching' + ); + } // delete $response = $curl->delete($this->url_basic, [ "headers" => ["third-call" => "delete"], ]); $this->assertEquals("200", $response["code"], "multi call: delete response code not matching"); - $this->assertEquals( - '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' - . '"HTTP_THIRD_CALL":"delete","HTTP_ACCEPT":"*\/*",' - . '"HTTP_HOST":"soba.egplusww.jp"},' - . '"REQUEST_TYPE":"DELETE",' - . '"PARAMS":[],"BODY":[]}', - $response['content'], - 'multi call: delete content not matching' - ); + if (PHP_VERSION_ID >= 80400) { + $this->assertEquals( + '{"HEADERS":{"HTTP_HOST":"soba.egplusww.jp",' + . '"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' + . '"HTTP_THIRD_CALL":"delete","HTTP_ACCEPT":"*\/*"},' + . '"REQUEST_TYPE":"DELETE","PARAMS":[],"BODY":[]}', + $response['content'], + 'multi call: delete content not matching' + ); + } else { + $this->assertEquals( + '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' + . '"HTTP_THIRD_CALL":"delete","HTTP_ACCEPT":"*\/*",' + . '"HTTP_HOST":"soba.egplusww.jp"},' + . '"REQUEST_TYPE":"DELETE",' + . '"PARAMS":[],"BODY":[]}', + $response['content'], + 'multi call: delete content not matching' + ); + } } // MARK: auth header set via config diff --git a/www/admin/UrlRequests.target.php b/www/admin/UrlRequests.target.php index 3d310ff4..d74e32e8 100644 --- a/www/admin/UrlRequests.target.php +++ b/www/admin/UrlRequests.target.php @@ -52,7 +52,7 @@ header("Content-Type: application/json; charset=UTF-8"); if (!empty($http_headers['HTTP_AUTHORIZATION']) && !empty($http_headers['HTTP_RUNAUTHTEST'])) { header("HTTP/1.1 401 Unauthorized"); print buildContent($http_headers, '{"code": 401, "content": {"Error": "Not Authorized"}}'); - exit; + exit(1); } // if server request type is get set file_get to null -> no body @@ -61,7 +61,7 @@ if ($_SERVER['REQUEST_METHOD'] == "GET") { } elseif (($file_get = file_get_contents('php://input')) === false) { header("HTTP/1.1 404 Not Found"); print buildContent($http_headers, '{"code": 404, "content": {"Error": "file_get_contents failed"}}'); - exit; + exit(1); } // str_replace('\"', '"', trim($file_get, '"')); diff --git a/www/configs/config.master.php b/www/configs/config.master.php index 9d5b9985..c30e86b0 100644 --- a/www/configs/config.master.php +++ b/www/configs/config.master.php @@ -184,7 +184,7 @@ define('HOST_NAME', $HOST_NAME); // BAIL ON MISSING MASTER SITE CONFIG if (!isset($SITE_CONFIG[HOST_NAME]['location'])) { echo 'Missing SITE_CONFIG entry for: "' . HOST_NAME . '". Contact Administrator'; - exit; + exit(1); } // BAIL ON MISSING DB CONFIG: // we have either no db selction for this host but have db config entries @@ -201,7 +201,7 @@ if ( ) ) { echo 'No matching DB config found for: "' . HOST_NAME . '". Contact Administrator'; - exit; + exit(1); } // set SSL on $is_secure = false; diff --git a/www/lib/CoreLibs/Admin/EditBase.php b/www/lib/CoreLibs/Admin/EditBase.php index 20312b52..754c33b5 100644 --- a/www/lib/CoreLibs/Admin/EditBase.php +++ b/www/lib/CoreLibs/Admin/EditBase.php @@ -76,7 +76,7 @@ class EditBase ); if ($this->form->mobile_phone) { echo "I am sorry, but this page cannot be viewed by a mobile phone"; - exit; + exit(1); } // $this->log->debug('POST', $this->log->prAr($_POST)); } diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index 038f32d8..85a4e897 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -500,7 +500,7 @@ class IO die(''); } // write to internal one, once OK - $this->db_functions = $db_functions; + $this->db_functions = $db_functions; /** @phan-suppress-current-line PhanPossiblyNullTypeMismatchProperty */ // connect to DB if (!$this->__connectToDB()) { From b493b3c4fdb14c2adcd541d10661ed388753fe34 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 20 Jan 2025 20:27:34 +0900 Subject: [PATCH 070/105] Remove debug message --- 4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php b/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php index 77077b71..e5a22024 100644 --- a/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php +++ b/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php @@ -969,7 +969,6 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase "query" => ["foo-get" => "bar"] ]); $this->assertEquals("200", $response["code"], "multi call: get response code not matching"); - print "PHP: " . PHP_VERSION_ID . "\n"; if (PHP_VERSION_ID >= 80400) { $this->assertEquals( '{"HEADERS":{"HTTP_HOST":"soba.egplusww.jp",' From 2c234ccef6cbb6a0496941ea4b13d6c93be65a5f Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 29 Jan 2025 09:57:58 +0900 Subject: [PATCH 071/105] On config errors do not exit but throw exception --- www/configs/config.master.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/www/configs/config.master.php b/www/configs/config.master.php index c30e86b0..da221c9b 100644 --- a/www/configs/config.master.php +++ b/www/configs/config.master.php @@ -183,8 +183,9 @@ if (file_exists(BASE . CONFIGS . 'config.path.php')) { define('HOST_NAME', $HOST_NAME); // BAIL ON MISSING MASTER SITE CONFIG if (!isset($SITE_CONFIG[HOST_NAME]['location'])) { - echo 'Missing SITE_CONFIG entry for: "' . HOST_NAME . '". Contact Administrator'; - exit(1); + throw new \InvalidArgumentException( + 'Missing SITE_CONFIG entry for: "' . HOST_NAME . '". Contact Administrator' + ); } // BAIL ON MISSING DB CONFIG: // we have either no db selction for this host but have db config entries @@ -200,8 +201,9 @@ if ( empty($DB_CONFIG[$SITE_CONFIG[HOST_NAME]['db_host']])) ) ) { - echo 'No matching DB config found for: "' . HOST_NAME . '". Contact Administrator'; - exit(1); + throw new \InvalidArgumentException( + 'No matching DB config found for: "' . HOST_NAME . '". Contact Administrator' + ); } // set SSL on $is_secure = false; From 44b825310ac0c1f983a55fe0d854ecae977f8b65 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 7 Feb 2025 19:06:35 +0900 Subject: [PATCH 072/105] Add ACL level number to unit detail --- www/lib/CoreLibs/ACL/Login.php | 1 + 1 file changed, 1 insertion(+) diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 3eff9917..46b622e9 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -1560,6 +1560,7 @@ class Login 'uid' => $unit['uid'], 'cuuid' => $unit['cuuid'], 'level' => $this->default_acl_list[$this->acl['unit'][$ea_cuid]]['name'] ?? -1, + 'level_number' => $this->acl['unit'][$ea_cuid], 'default' => $unit['default'], 'data' => $unit['data'], 'additional_acl' => $unit['additional_acl'] From 07fbd13213e10c9e03af60a9355a435f62e2d3c9 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 13 Feb 2025 18:24:30 +0900 Subject: [PATCH 073/105] Setup npm with eslint --- .eslintrc.js | 43 -- eslint.config.mjs | 59 ++ jsconfig.json | 2 + package-lock.json | 1567 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 17 + 5 files changed, 1645 insertions(+), 43 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 eslint.config.mjs create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 48fa262f..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,43 +0,0 @@ -module.exports = { - 'env': { - 'browser': true, - 'es6': true, - 'commonjs': true, - 'jquery': true - }, - 'extends': 'eslint:recommended', - 'parserOptions': { - 'ecmaVersion': 6 - }, - 'rules': { - 'indent': [ - 'error', - 'tab', - { - 'SwitchCase': 1 - } - ], - 'linebreak-style': [ - 'error', - 'unix' - ], - 'quotes': [ - 'error', - 'single' - ], - 'semi': [ - 'error', - 'always' - ], - 'no-console': 'off', - 'no-unused-vars': [ - 'error', { - 'vars': 'all', - 'args': 'after-used', - 'ignoreRestSiblings': false - } - ], - // Requires eslint >= v8.14.0 - 'no-constant-binary-expression': 'error' - } -}; diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..131b33ef --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,59 @@ +import globals from 'globals'; +import pluginJs from '@eslint/js'; + +/* +module.exports = { + 'env': { + 'browser': true, + 'es6': true, + 'commonjs': true, + 'jquery': true + }, + 'extends': 'eslint:recommended', + 'parserOptions': { + 'ecmaVersion': 6 + }, + // rules copied +}; +*/ + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + {languageOptions: { globals: globals.browser }}, + pluginJs.configs.recommended, + { + 'rules': { + 'indent': [ + 'error', + 'tab', + { + 'SwitchCase': 1 + } + ], + 'linebreak-style': [ + 'error', + 'unix' + ], + // 'quotes': [ + // 'error', + // 'single' + // ], + 'semi': [ + 'error', + 'always' + ], + 'no-console': 'off', + 'no-unused-vars': [ + 'error', { + 'vars': 'all', + 'args': 'after-used', + 'ignoreRestSiblings': false + } + ], + // Requires eslint >= v8.14.0 + 'no-constant-binary-expression': 'error' + } + } +]; + +// __END__ diff --git a/jsconfig.json b/jsconfig.json index 9a284483..9d08c675 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -1,9 +1,11 @@ +// https://www.typescriptlang.org/tsconfig/#compilerOptions { "compilerOptions": { "module": "ESNext", "moduleResolution": "Node", "target": "ES2020", "jsx": "react", + "checkJs": true, "allowImportingTsExtensions": true, "strictNullChecks": true, "strictFunctionTypes": true diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..5fdccbe6 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1567 @@ +{ + "name": "core-libraries", + "version": "9.26.8", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "core-libraries", + "version": "9.26.8", + "devDependencies": { + "@eslint/js": "^9.20.0", + "esbuild": "^0.25.0", + "eslint": "^9.20.1", + "globals": "^15.15.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz", + "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz", + "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz", + "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz", + "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz", + "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz", + "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz", + "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz", + "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz", + "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz", + "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz", + "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz", + "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz", + "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz", + "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz", + "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz", + "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz", + "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz", + "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz", + "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz", + "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz", + "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz", + "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz", + "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz", + "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz", + "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.11.0.tgz", + "integrity": "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.20.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz", + "integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.10.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz", + "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.0", + "@esbuild/android-arm": "0.25.0", + "@esbuild/android-arm64": "0.25.0", + "@esbuild/android-x64": "0.25.0", + "@esbuild/darwin-arm64": "0.25.0", + "@esbuild/darwin-x64": "0.25.0", + "@esbuild/freebsd-arm64": "0.25.0", + "@esbuild/freebsd-x64": "0.25.0", + "@esbuild/linux-arm": "0.25.0", + "@esbuild/linux-arm64": "0.25.0", + "@esbuild/linux-ia32": "0.25.0", + "@esbuild/linux-loong64": "0.25.0", + "@esbuild/linux-mips64el": "0.25.0", + "@esbuild/linux-ppc64": "0.25.0", + "@esbuild/linux-riscv64": "0.25.0", + "@esbuild/linux-s390x": "0.25.0", + "@esbuild/linux-x64": "0.25.0", + "@esbuild/netbsd-arm64": "0.25.0", + "@esbuild/netbsd-x64": "0.25.0", + "@esbuild/openbsd-arm64": "0.25.0", + "@esbuild/openbsd-x64": "0.25.0", + "@esbuild/sunos-x64": "0.25.0", + "@esbuild/win32-arm64": "0.25.0", + "@esbuild/win32-ia32": "0.25.0", + "@esbuild/win32-x64": "0.25.0" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.20.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz", + "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.11.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.20.0", + "@eslint/plugin-kit": "^0.2.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..af2d2ff1 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "core-libraries", + "version": "9.26.8", + "main": "", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Clemens Schwaighofer", + "license": "", + "description": "Core Libraries", + "devDependencies": { + "@eslint/js": "^9.20.0", + "esbuild": "^0.25.0", + "eslint": "^9.20.1", + "globals": "^15.15.0" + } +} From 5e21ead6fa6a66fc61aa3fdae5f99dd31e63f474 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 13 Feb 2025 18:24:50 +0900 Subject: [PATCH 074/105] change error catcher for javasript from log to error as output --- www/admin/layout/javascript/edit.jq.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/www/admin/layout/javascript/edit.jq.js b/www/admin/layout/javascript/edit.jq.js index 52da0f1c..cda95860 100644 --- a/www/admin/layout/javascript/edit.jq.js +++ b/www/admin/layout/javascript/edit.jq.js @@ -1,8 +1,6 @@ /* general edit javascript */ /* jquery version */ -/* jshint esversion: 11 */ - /* global i18n */ // debug set @@ -533,22 +531,20 @@ function errorCatch(err) if (err.stack) { // only FF if (err.lineNumber) { - console.log('ERROR[%s:%s] %s', err.name, err.lineNumber, err.message); + console.error('ERROR[%s:%s] ', err.name, err.lineNumber, err); } else if (err.line) { // only Safari - console.log('ERROR[%s:%s] %s', err.name, err.line, err.message); + console.error('ERROR[%s:%s] ', err.name, err.line, err); } else { - console.log('ERROR[%s] %s', err.name, err.message); + console.error('ERROR[%s] ', err.name, err); } - // stack trace - console.log('ERROR[stack] %s', err.stack); } else if (err.number) { // IE - console.log('ERROR[%s:%s] %s', err.name, err.number, err.message); - console.log('ERROR[description] %s', err.description); + console.error('ERROR[%s:%s] %s', err.name, err.number, err.message); + console.error('ERROR[description] %s', err.description); } else { // the rest - console.log('ERROR[%s] %s', err.name, err.message); + console.error('ERROR[%s] %s', err.name, err.message); } } From 6291ed88c0e12dbacf5c1ae5c435b1f4e8849718 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 13 Feb 2025 19:01:44 +0900 Subject: [PATCH 075/105] eslint config update --- eslint.config.mjs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 131b33ef..ff91f7eb 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,12 +3,7 @@ import pluginJs from '@eslint/js'; /* module.exports = { - 'env': { - 'browser': true, - 'es6': true, - 'commonjs': true, - 'jquery': true - }, + // in globals block 'extends': 'eslint:recommended', 'parserOptions': { 'ecmaVersion': 6 @@ -19,7 +14,12 @@ module.exports = { /** @type {import('eslint').Linter.Config[]} */ export default [ - {languageOptions: { globals: globals.browser }}, + {languageOptions: { + globals: { + ...globals.browser, + ...globals.jquery + } + }}, pluginJs.configs.recommended, { 'rules': { From 32dee1692e3d859620723a254efbee7a46605f93 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 17 Feb 2025 11:16:51 +0900 Subject: [PATCH 076/105] Fix DateTime days internal counter Fixed the bad coded include end date with using flags instead Allow exclude of start date Reverse counter fixed, and also includes weekend days Add reverse for weekend in date interval Login class: add numeric for ACL level DB IO: some minor code clean up for not needed var set check Some edit.jq.js clean ups and added - loadEl: load element by id and return element value or throw error if not found - goTo: scroll to an element with scroll into view call --- .../Combined/CoreLibsCombinedDateTimeTest.php | 183 ++++++++++++++---- www/admin/class_test.datetime.php | 53 +++-- www/admin/layout/javascript/edit.jq.js | 63 ++++-- www/lib/CoreLibs/ACL/Login.php | 2 +- www/lib/CoreLibs/Combined/DateTime.php | 52 +++-- www/lib/CoreLibs/DB/IO.php | 7 +- 6 files changed, 268 insertions(+), 92 deletions(-) diff --git a/4dev/tests/Combined/CoreLibsCombinedDateTimeTest.php b/4dev/tests/Combined/CoreLibsCombinedDateTimeTest.php index d7efa9b7..58040c49 100644 --- a/4dev/tests/Combined/CoreLibsCombinedDateTimeTest.php +++ b/4dev/tests/Combined/CoreLibsCombinedDateTimeTest.php @@ -926,48 +926,114 @@ final class CoreLibsCombinedDateTimeTest extends TestCase public function daysIntervalProvider(): array { return [ - 'valid interval /, not named array' => [ - '2020/1/1', - '2020/1/30', - false, - [29, 22, 8], + // normal and format tests + 'valid interval / not named array' => [ + 'input_a' => '2020/1/1', + 'input_b' => '2020/1/30', + 'return_named' => false, // return_named + 'include_end_date' => true, // include_end_date + 'exclude_start_date' => false, // exclude_start_date + 'expected' => [30, 22, 8, false], ], - 'valid interval /, named array' => [ - '2020/1/1', - '2020/1/30', - true, - ['overall' => 29, 'weekday' => 22, 'weekend' => 8], + 'valid interval / named array' => [ + 'input_a' => '2020/1/1', + 'input_b' => '2020/1/30', + 'return_named' => true, + 'include_end_date' => true, + 'exclude_start_date' => false, + 'expected' => ['overall' => 30, 'weekday' => 22, 'weekend' => 8, 'reverse' => false], ], - 'valid interval -' => [ - '2020-1-1', - '2020-1-30', - false, - [29, 22, 8], - ], - 'valid interval switched' => [ - '2020/1/30', - '2020/1/1', - false, - [28, 0, 0], + 'valid interval with "-"' => [ + 'input_a' => '2020-1-1', + 'input_b' => '2020-1-30', + 'return_named' => false, + 'include_end_date' => true, + 'exclude_start_date' => false, + 'expected' => [30, 22, 8, false], ], 'valid interval with time' => [ - '2020/1/1 12:12:12', - '2020/1/30 13:13:13', - false, - [28, 21, 8], + 'input_a' => '2020/1/1 12:12:12', + 'input_b' => '2020/1/30 13:13:13', + 'return_named' => false, + 'include_end_date' => true, + 'exclude_start_date' => false, + 'expected' => [30, 22, 8, false], ], + // invalid 'invalid dates' => [ - 'abc', - 'xyz', - false, - [0, 0, 0] + 'input_a' => 'abc', + 'input_b' => 'xyz', + 'return_named' => false, + 'include_end_date' => true, + 'exclude_start_date' => false, + 'expected' => [0, 0, 0, false] ], - // this test will take a long imte + // this test will take a long time 'out of bound dates' => [ - '1900-1-1', - '9999-12-31', - false, - [2958463,2113189,845274], + 'input_a' => '1900-1-1', + 'input_b' => '9999-12-31', + 'return_named' => false, + 'include_end_date' => true, + 'exclude_start_date' => false, + 'expected' => [2958463, 2113189, 845274, false], + ], + // tests for include/exclude + 'exclude end date' => [ + 'input_b' => '2020/1/1', + 'input_a' => '2020/1/30', + 'return_named' => false, + 'include_end_date' => false, + 'exclude_start_date' => false, + 'expected' => [29, 21, 8, false], + ], + 'exclude start date' => [ + 'input_b' => '2020/1/1', + 'input_a' => '2020/1/30', + 'return_named' => false, + 'include_end_date' => true, + 'exclude_start_date' => true, + 'expected' => [29, 21, 8, false], + ], + 'exclude start and end date' => [ + 'input_b' => '2020/1/1', + 'input_a' => '2020/1/30', + 'return_named' => false, + 'include_end_date' => false, + 'exclude_start_date' => true, + 'expected' => [28, 20, 8, false], + ], + // reverse + 'reverse: valid interval' => [ + 'input_a' => '2020/1/30', + 'input_b' => '2020/1/1', + 'return_named' => false, + 'include_end_date' => true, + 'exclude_start_date' => false, + 'expected' => [30, 22, 8, true], + ], + 'reverse: exclude end date' => [ + 'input_a' => '2020/1/30', + 'input_b' => '2020/1/1', + 'return_named' => false, + 'include_end_date' => false, + 'exclude_start_date' => false, + 'expected' => [29, 21, 8, true], + ], + 'reverse: exclude start date' => [ + 'input_a' => '2020/1/30', + 'input_b' => '2020/1/1', + 'return_named' => false, + 'include_end_date' => true, + 'exclude_start_date' => true, + 'expected' => [29, 21, 8, true], + ], + 'reverse: exclude start and end date' => [ + 'input_a' => '2020/1/30', + 'input_b' => '2020/1/1', + 'return_named' => false, + 'include_end_date' => false, + 'exclude_start_date' => true, + 'expected' => [28, 20, 8, true], ], ]; } @@ -982,19 +1048,27 @@ final class CoreLibsCombinedDateTimeTest extends TestCase * * @param string $input_a * @param string $input_b - * @param bool $flag - * @param array $expected + * @param bool $return_named + * @param array $expected * @return void */ public function testCalcDaysInterval( string $input_a, string $input_b, - bool $flag, + bool $return_named, + bool $include_end_date, + bool $exclude_start_date, $expected ): void { $this->assertEquals( $expected, - \CoreLibs\Combined\DateTime::calcDaysInterval($input_a, $input_b, $flag) + \CoreLibs\Combined\DateTime::calcDaysInterval( + $input_a, + $input_b, + return_named:$return_named, + include_end_date:$include_end_date, + exclude_start_date:$exclude_start_date + ) ); } @@ -1187,7 +1261,38 @@ final class CoreLibsCombinedDateTimeTest extends TestCase '2023-07-03', '2023-07-27', true - ] + ], + // reverse + 'reverse: no weekend' => [ + '2023-07-04', + '2023-07-03', + false + ], + 'reverse: start weekend sat' => [ + '2023-07-04', + '2023-07-01', + true + ], + 'reverse: start weekend sun' => [ + '2023-07-04', + '2023-07-02', + true + ], + 'reverse: end weekend sat' => [ + '2023-07-08', + '2023-07-03', + true + ], + 'reverse: end weekend sun' => [ + '2023-07-09', + '2023-07-03', + true + ], + 'reverse: long period > 6 days' => [ + '2023-07-27', + '2023-07-03', + true + ], ]; } diff --git a/www/admin/class_test.datetime.php b/www/admin/class_test.datetime.php index e0374599..45bca5e8 100644 --- a/www/admin/class_test.datetime.php +++ b/www/admin/class_test.datetime.php @@ -268,7 +268,9 @@ foreach ($compare_datetimes as $compare_datetime) { print "COMPAREDATE: $compare_datetime[0] = $compare_datetime[1]: " . (string)DateTime::compareDateTime($compare_datetime[0], $compare_datetime[1]) . "
"; } + print "
"; +print "

calcDaysInterval

"; $compare_dates = [ [ '2021-05-01', '2021-05-10', ], [ '2021-05-10', '2021-05-01', ], @@ -279,9 +281,21 @@ foreach ($compare_dates as $compare_date) { print "CALCDAYSINTERVAL: $compare_date[0] = $compare_date[1]: " . DgS::printAr(DateTime::calcDaysInterval($compare_date[0], $compare_date[1])) . "
"; print "CALCDAYSINTERVAL(named): $compare_date[0] = $compare_date[1]: " - . DgS::printAr(DateTime::calcDaysInterval($compare_date[0], $compare_date[1], true)) . "
"; + . DgS::printAr(DateTime::calcDaysInterval($compare_date[0], $compare_date[1], return_named:true)) . "
"; + print "CALCDAYSINTERVAL(EXCLUDE END): $compare_date[0] = $compare_date[1]: " + . Dgs::printAr(DateTime::calcDaysInterval($compare_date[0], $compare_date[1], include_end_date:false)); + print "CALCDAYSINTERVAL(EXCLUDE START): $compare_date[0] = $compare_date[1]: " + . Dgs::printAr(DateTime::calcDaysInterval($compare_date[0], $compare_date[1], exclude_start_date:true)); + print "CALCDAYSINTERVAL(EXCLUDE END, EXCLUDE START): $compare_date[0] = $compare_date[1]: " + . Dgs::printAr(DateTime::calcDaysInterval( + $compare_date[0], + $compare_date[1], + include_end_date:false, + exclude_start_date:true + )); } print "
"; +print "

setWeekdayNameFromIsoDow

"; // test date conversion $dow = 2; print "DOW[$dow]: " . DateTime::setWeekdayNameFromIsoDow($dow) . "
"; @@ -297,26 +311,25 @@ $date = '2022-70-242'; print "DATE-dow[$date];invalid: " . DateTime::setWeekdayNameFromDate($date) . "
"; print "DATE-dow[$date],long;invalid: " . DateTime::setWeekdayNameFromDate($date, true) . "
"; print "DOW-date[$date];invalid: " . DateTime::setWeekdayNumberFromDate($date) . "
"; -print "
"; -// check date range includes a weekend -// does not: -$start_date = '2023-07-03'; -$end_date = '2023-07-05'; -print "Has Weekend: " . $start_date . " ~ " . $end_date . ": " - . Dgs::prBl(DateTime::dateRangeHasWeekend($start_date, $end_date)) . "
"; -$start_date = '2023-07-03'; -$end_date = '2023-07-10'; -print "Has Weekend: " . $start_date . " ~ " . $end_date . ": " - . Dgs::prBl(DateTime::dateRangeHasWeekend($start_date, $end_date)) . "
"; -$start_date = '2023-07-03'; -$end_date = '2023-07-31'; -print "Has Weekend: " . $start_date . " ~ " . $end_date . ": " - . Dgs::prBl(DateTime::dateRangeHasWeekend($start_date, $end_date)) . "
"; -$start_date = '2023-07-01'; -$end_date = '2023-07-03'; -print "Has Weekend: " . $start_date . " ~ " . $end_date . ": " - . Dgs::prBl(DateTime::dateRangeHasWeekend($start_date, $end_date)) . "
"; +print "
"; +print "

dateRangeHasWeekend

"; +// check date range includes a weekend +$has_weekend_list = [ + ['2023-07-03', '2023-07-05'], + ['2023-07-03', '2023-07-10'], + ['2023-07-03', '2023-07-31'], + ['2023-07-01', '2023-07-03'], + ['2023-07-01', '2023-07-01'], + ['2023-07-01', '2023-07-02'], + ['2023-06-30', '2023-07-01'], + ['2023-06-30', '2023-06-30'], + ['2023-07-01', '2023-06-30'], +]; +foreach ($has_weekend_list as $days) { + print "Has Weekend: " . $days[0] . " ~ " . $days[1] . ": " + . Dgs::prBl(DateTime::dateRangeHasWeekend($days[0], $days[1])) . "
"; +} print ""; diff --git a/www/admin/layout/javascript/edit.jq.js b/www/admin/layout/javascript/edit.jq.js index cda95860..6c2605ed 100644 --- a/www/admin/layout/javascript/edit.jq.js +++ b/www/admin/layout/javascript/edit.jq.js @@ -16,6 +16,21 @@ if (!DEBUG) { var GL_OB_S = 100; var GL_OB_BASE = 100; +/** + * Gets html element or throws an error + * @param {string} el_id Element ID to get + * @returns {HTMLElement} + * @throws Error + */ +function loadEl(el_id) +{ + let el = document.getElementById(el_id); + if (el === null) { + throw new Error('Cannot find: ' + el_id); + } + return el; +} + /** * opens a popup window with winName and given features (string) * @param {String} theURL the url @@ -154,6 +169,18 @@ function goToPos(element, offset = 0, duration = 500, base = 'body,html') // esl } } +/** + * go to element, scroll + * non jquery + * @param {string} target +*/ +function goTo(target) // eslint-disable-line no-unused-vars +{ + loadEl(target).scrollIntoView({ + behavior: 'smooth' + }); +} + /** * uses the i18n object created in the translation template * that is filled from gettext in PHP @@ -400,9 +427,9 @@ function keyInObject(key, object) /** * returns matching key of value - * @param {Object} obj object to search value in - * @param {Mixed} value any value (String, Number, etc) - * @return {String} the key found for the first matching value + * @param {Object} object object to search value in + * @param {Mixed} value any value (String, Number, etc) + * @return {String} the key found for the first matching value */ function getKeyByValue(object, value) // eslint-disable-line no-unused-vars { @@ -414,9 +441,9 @@ function getKeyByValue(object, value) // eslint-disable-line no-unused-vars /** * returns true if value is found in object with a key - * @param {Object} obj object to search value in - * @param {Mixed} value any value (String, Number, etc) - * @return {Boolean} true on value found, false on not found + * @param {Object} object object to search value in + * @param {Mixed} value any value (String, Number, etc) + * @return {Boolean} true on value found, false on not found */ function valueInObject(object, value) // eslint-disable-line no-unused-vars { @@ -796,7 +823,7 @@ function showOverlayBoxLayers(el_id) // eslint-disable-line no-unused-vars * else just set zIndex to the new GL_OB_S value * @param {String} el_id Target to hide layer */ -function hideOverlayBoxLayers(el_id) +function hideOverlayBoxLayers(el_id='') { // console.log('HIDE overlaybox: %s', GL_OB_S); // remove on layer @@ -1109,7 +1136,9 @@ function phfa(list) // eslint-disable-line no-unused-vars function html_options(name, data, selected = '', options_only = false, return_string = false, sort = '') // eslint-disable-line no-unused-vars { // wrapper to new call - return html_options_block(name, data, selected, false, options_only, return_string, sort); + return html_options_block( + name, data, selected, 0, options_only, return_string, sort + ); } /** @@ -1131,8 +1160,9 @@ function html_options(name, data, selected = '', options_only = false, return_st * @param {String} [onchange=''] onchange trigger call, default unset * @return {String} html with build options block */ -function html_options_block(name, data, selected = '', multiple = 0, options_only = false, return_string = false, sort = '', onchange = '') -{ +function html_options_block( + name, data, selected = '', multiple = 0, options_only = false, return_string = false, sort = '', onchange = '' +) { var content = []; var element_select; var select_options = {}; @@ -1169,7 +1199,8 @@ function html_options_block(name, data, selected = '', multiple = 0, options_onl // basic options init options = { 'label': value, - 'value': key + 'value': key, + 'selected': '' }; // add selected if matching if (multiple == 0 && !Array.isArray(selected) && selected == key) { @@ -1180,7 +1211,7 @@ function html_options_block(name, data, selected = '', multiple = 0, options_onl options.selected = ''; } // create the element option - element_option = cel('option', '', value, '', options); + element_option = cel('option', '', value, [], options); // attach it to the select element ael(element_select, element_option); } @@ -1232,7 +1263,7 @@ function html_options_refill(name, data, sort = '') // eslint-disable-line no-un [].forEach.call(document.querySelectorAll('#' + name + ' :checked'), function(elm) { option_selected = elm.value; }); - document.getElementById(name).innerHTML = ''; + loadEl(name).innerHTML = ''; for (const key of data_list) { value = data[key]; // console.log('add [%s] options: key: %s, value: %s', name, key, value); @@ -1243,7 +1274,7 @@ function html_options_refill(name, data, sort = '') // eslint-disable-line no-un if (key == option_selected) { element_option.selected = true; } - document.getElementById(name).appendChild(element_option); + loadEl(name).appendChild(element_option); } } } @@ -1307,7 +1338,7 @@ function parseQueryString(query = '', return_key = '') // eslint-disable-line no * all parameters are returned * @param {String} [query=''] different query string to parse, if not * set (default) the current window href is used - * @param {Bool} [single=false] if set to true then only the first found + * @param {Boolean} [single=false] if set to true then only the first found * will be returned * @return {Object|Array|String} if search is empty, object, if search is set * and only one entry, then string, else array @@ -1319,7 +1350,7 @@ function getQueryStringParam(search = '', query = '', single = false) // eslint- query = window.location.href; } const url = new URL(query); - let param = ''; + let param = null; if (search) { let _params = url.searchParams.getAll(search); if (_params.length == 1 || single === true) { diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 46b622e9..890119ff 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -2539,7 +2539,7 @@ HTML; $this->login_user_id, -1, $login_user_id_changed - ); + ) ?? ''; // flag unclean input data if ($login_user_id_changed > 0) { $this->login_user_id_unclear = true; diff --git a/www/lib/CoreLibs/Combined/DateTime.php b/www/lib/CoreLibs/Combined/DateTime.php index d46619c5..d123c53a 100644 --- a/www/lib/CoreLibs/Combined/DateTime.php +++ b/www/lib/CoreLibs/Combined/DateTime.php @@ -639,16 +639,26 @@ class DateTime * * @param string $start_date valid start date (y/m/d) * @param string $end_date valid end date (y/m/d) - * @param bool $return_named return array type, false (default), true for named - * @return array 0/overall, 1/weekday, 2/weekend + * @param bool $return_named [default=false] return array type, false (default), true for named + * @param bool $include_end_date [default=true] include end date in calc + * @param bool $exclude_start_date [default=false] include end date in calc + * @return array{0:int,1:int,2:int,3:bool}|array{overall:int,weekday:int,weekend:int,reverse:bool} + * 0/overall, 1/weekday, 2/weekend, 3/reverse */ public static function calcDaysInterval( string $start_date, string $end_date, - bool $return_named = false + bool $return_named = false, + bool $include_end_date = true, + bool $exclude_start_date = false ): array { // pos 0 all, pos 1 weekday, pos 2 weekend - $days = []; + $days = [ + 0 => 0, + 1 => 0, + 2 => 0, + 3 => false, + ]; // if anything invalid, return 0,0,0 try { $start = new \DateTime($start_date); @@ -659,19 +669,30 @@ class DateTime 'overall' => 0, 'weekday' => 0, 'weekend' => 0, + 'reverse' => false ]; } else { - return [0, 0, 0]; + return $days; } } // so we include the last day too, we need to add +1 second in the time - $end->setTime(0, 0, 1); - // if end date before start date, only this will be filled - $days[0] = $end->diff($start)->days; - $days[1] = 0; - $days[2] = 0; + // if start is before end, switch dates and flag + $days[3] = false; + if ($start > $end) { + $new_start = $end; + $end = $start; + $start = $new_start; + $days[3] = true; + } // get period for weekends/weekdays - $period = new \DatePeriod($start, new \DateInterval('P1D'), $end); + $options = 0; + if ($include_end_date) { + $options |= \DatePeriod::INCLUDE_END_DATE; + } + if ($exclude_start_date) { + $options |= \DatePeriod::EXCLUDE_START_DATE; + } + $period = new \DatePeriod($start, new \DateInterval('P1D'), $end, $options); foreach ($period as $dt) { $curr = $dt->format('D'); if ($curr == 'Sat' || $curr == 'Sun') { @@ -679,12 +700,14 @@ class DateTime } else { $days[1]++; } + $days[0]++; } if ($return_named === true) { return [ 'overall' => $days[0], 'weekday' => $days[1], 'weekend' => $days[2], + 'reverse' => $days[3], ]; } else { return $days; @@ -705,6 +728,13 @@ class DateTime ): bool { $dd_start = new \DateTime($start_date); $dd_end = new \DateTime($end_date); + // flip if start is after end + if ($dd_start > $dd_end) { + $new_start = $dd_end; + $dd_end = $dd_start; + $dd_start = $new_start; + } + // if start > end, flip if ( // starts with a weekend $dd_start->format('N') >= 6 || diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index 85a4e897..47206323 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -1413,10 +1413,7 @@ class IO $this->pk_name_table[$table] ? $this->pk_name_table[$table] : 'NULL'; } - if ( - !preg_match(self::REGEX_RETURNING, $this->query) && - $this->pk_name && $this->pk_name != 'NULL' - ) { + if (!preg_match(self::REGEX_RETURNING, $this->query) && $this->pk_name != 'NULL') { // check if this query has a ; at the end and remove it $__query = preg_replace("/(;\s*)$/", '', $this->query); // must be query, if preg replace failed, use query as before @@ -1426,7 +1423,7 @@ class IO } elseif ( preg_match(self::REGEX_RETURNING, $this->query, $matches) ) { - if ($this->pk_name && $this->pk_name != 'NULL') { + if ($this->pk_name != 'NULL') { // add the primary key if it is not in the returning set if (!preg_match("/$this->pk_name/", $matches[1])) { $this->query .= " , " . $this->pk_name; From 7b96c1f9ca2a841e37f996cec2b36aa1bf8ffb2c Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 17 Feb 2025 12:55:20 +0900 Subject: [PATCH 077/105] Remove old eslint config, replaced with mjs one --- eslint.config.js | 141 ----------------------------------------------- 1 file changed, 141 deletions(-) delete mode 100644 eslint.config.js diff --git a/eslint.config.js b/eslint.config.js deleted file mode 100644 index 5d9aa4da..00000000 --- a/eslint.config.js +++ /dev/null @@ -1,141 +0,0 @@ -// // import globals from "globals"; -// import js from '@eslint/js'; - -module.exports = { - // 'env': { - // 'browser': true, - // 'es6': true, - // 'commonjs': true, - // 'jquery': true - // }, - // 'extends': 'eslint:recommended', - // 'parserOptions': { - // 'ecmaVersion': 11 - // }, - // ...eslint.configs.recommended, - 'rules': { - 'indent': [ - 'error', - 'tab', { - 'SwitchCase': 1 - } - ], - 'linebreak-style': [ - 'error', - 'unix' - ], - 'quotes': [ - 'error', - 'single', - { 'avoidEscape': true, 'allowTemplateLiterals': true } - ], - 'semi': [ - 'error', - 'always' - ], - 'no-console': 'off', - 'no-unused-vars': [ - 'error', { - 'vars': 'all', - 'args': 'after-used', - 'ignoreRestSiblings': false - } - ], - // Requires eslint >= v8.14.0 - 'no-constant-binary-expression': 'error' - } -}; - -// export default [ -// ...js.configs.recommended, -// { -// files: ['**/*.js'], -// 'rules': { -// 'indent': [ -// 'error', -// 'tab', { -// 'SwitchCase': 1 -// } -// ], -// 'linebreak-style': [ -// 'error', -// 'unix' -// ], -// 'quotes': [ -// 'error', -// 'single', -// { 'avoidEscape': true, 'allowTemplateLiterals': true } -// ], -// 'semi': [ -// 'error', -// 'always' -// ], -// 'no-console': 'off', -// 'no-unused-vars': [ -// 'error', { -// 'vars': 'all', -// 'args': 'after-used', -// 'ignoreRestSiblings': false -// } -// ], -// // Requires eslint >= v8.14.0 -// 'no-constant-binary-expression': 'error' -// }, -// languageOptions: { -// globals: { -// ...globals.browser, -// ...globals.commonjs, -// ...globals.jquery, -// // myCustomGlobal: "readonly" -// 'ecmaVersion': 11 -// } -// } -// } -// ]; - -// import globals from "globals"; -// import js from '@eslint/js'; - -// export default [ -// js.configs.recommended, -// { -// files: ['**/*.js'], -// rules: { -// 'indent': [ -// 'error', -// 'tab', { -// 'SwitchCase': 1 -// } -// ], -// 'linebreak-style': [ -// 'error', -// 'unix' -// ], -// 'quotes': [ -// 'error', -// 'single', -// { 'avoidEscape': true, 'allowTemplateLiterals': true } -// ], -// 'semi': [ -// 'error', -// 'always' -// ], -// 'no-console': 'off', -// 'no-unused-vars': [ -// 'error', { -// 'vars': 'all', -// 'args': 'after-used', -// 'ignoreRestSiblings': false -// } -// ], -// // Requires eslint >= v8.14.0 -// 'no-constant-binary-expression': 'error' -// }/* , -// languageOptions: { -// ecmaVersion: 11, -// // globals: { -// // ...globals.browser, -// // }, -// } */ -// } -// ]; From fc7b7053558de43d4058c62513d5df9cb8ee9ebc Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 28 Feb 2025 10:14:11 +0900 Subject: [PATCH 078/105] config.master.php file update - remove not used code - reorder defines for possible clean up targets - TARGET and HOST_NAME are set early HOST NAME is set right at the top TARGET is set after site configs is read - add more $_ENV reads DEFAULT_ACL_LEVEL LOCALE (encoding is read from locale which should be in the format of nn_CT.ENCODING, eg en_US.UTF-8), falls back to UTF-8 ADMIN.STYLESHEET ADMIN.JAVASCRIPT --- .gitignore | 2 + 4dev/tests/ACL/CoreLibsACLLoginTest.php | 1 - www/configs/.target.example | 3 + www/configs/config.master.php | 106 +++++++----------------- www/configs/config.other.php | 6 ++ www/configs/config.path.php | 5 ++ www/configs/config.php | 5 ++ www/lib/CoreLibs/ACL/Login.php | 11 +-- www/lib/CoreLibs/Basic.php | 6 +- www/lib/CoreLibs/Language/GetLocale.php | 2 - 10 files changed, 54 insertions(+), 93 deletions(-) create mode 100644 www/configs/.target.example diff --git a/.gitignore b/.gitignore index dd606ef4..35a8acbc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ vendor/ tools/ www/composer.lock www/vendor +**/.env +**/.target diff --git a/4dev/tests/ACL/CoreLibsACLLoginTest.php b/4dev/tests/ACL/CoreLibsACLLoginTest.php index e6abe067..1062bb60 100644 --- a/4dev/tests/ACL/CoreLibsACLLoginTest.php +++ b/4dev/tests/ACL/CoreLibsACLLoginTest.php @@ -152,7 +152,6 @@ final class CoreLibsACLLoginTest extends TestCase // TARGET define('TARGET', 'test'); // LOGIN DB SCHEMA - // define('LOGIN_DB_SCHEMA', ''); // SHOULD SET // DEFAULT_ACL_LEVEL (d80) diff --git a/www/configs/.target.example b/www/configs/.target.example new file mode 100644 index 00000000..62f4277b --- /dev/null +++ b/www/configs/.target.example @@ -0,0 +1,3 @@ +# target can be live, stage, test, dev +# this overrides the SITE set "location" entry +TARGET= diff --git a/www/configs/config.master.php b/www/configs/config.master.php index da221c9b..9d5dbfa4 100644 --- a/www/configs/config.master.php +++ b/www/configs/config.master.php @@ -78,42 +78,11 @@ define('TEMPLATES_C', 'templates_c' . DIRECTORY_SEPARATOR); // template base define('TEMPLATES', 'templates' . DIRECTORY_SEPARATOR); -/************* HASH / ACL DEFAULT / ERROR SETTINGS / SMARTY *************/ +/************* HASH / ACL DEFAULT *************/ // default hash type define('DEFAULT_HASH', 'sha256'); // default acl level -define('DEFAULT_ACL_LEVEL', 80); -// SSL host name -// define('SSL_HOST', $_ENV['SSL_HOST'] ?? ''); -// error page strictness, Default is 3 -// 1: only show error page as the last mesure if really no mid & aid can be loaded and found at all -// 2: if template not found, do not search, show error template -// 3: if default template is not found, show error template, do not fall back to default tree -// 4: very strict, even on normal fixable errors through error -// define('ERROR_STRICT', 3); -// allow page caching in general, set to 'false' if you do debugging or development! -// define('ALLOW_SMARTY_CACHE', false); -// cache life time, in second', default here is 2 days (172800s) -// -1 is never expire cache -// define('SMARTY_CACHE_LIFETIME', -1); - -/************* LOGOUT ********************/ -// logout target -define('LOGOUT_TARGET', ''); - -/************* AJAX / ACCESS *************/ -// ajax request type -define('AJAX_REQUEST_TYPE', 'POST'); -// what AJAX type to use -define('USE_PROTOTYPE', false); -define('USE_SCRIPTACULOUS', false); -define('USE_JQUERY', true); - -/************* LAYOUT WIDTHS *************/ -define('PAGE_WIDTH', '100%'); -define('CONTENT_WIDTH', '100%'); -// the default template name -define('MASTER_TEMPLATE_NAME', 'main_body.tpl'); +define('DEFAULT_ACL_LEVEL', $ENV['DEFAULT_ACL_LEVEL'] ?? 80); /************* OVERALL CONTROL NAMES *************/ // BELOW has HAS to be changed @@ -136,24 +105,15 @@ define('COMPILE_ID', 'COMPILE_' . BASE_NAME . '_' . SERVER_NAME_HASH); /************* LANGUAGE / ENCODING *******/ // default lang + encoding -define('DEFAULT_LOCALE', 'en_US.UTF-8'); +define('DEFAULT_LOCALE', $_ENV['LOCALE'] ?? 'en_US.UTF-8'); // default web page encoding setting -define('DEFAULT_ENCODING', 'UTF-8'); +define('DEFAULT_ENCODING', array_pad(explode('.', DEFAULT_LOCALE, 2), 2, 'UTF-8')); -/************* QUEUE TABLE *************/ -// if we have a dev/live system -// set_live is a per page/per item -// live_queue is a global queue system -// define('QUEUE', 'live_queue'); - -/************* DB PATHS (PostgreSQL) *****************/ -// schema names, can also be defined per -define('PUBLIC_SCHEMA', 'public'); -define('DEV_SCHEMA', 'public'); -define('TEST_SCHEMA', 'public'); -define('LIVE_SCHEMA', 'public'); -define('GLOBAL_DB_SCHEMA', ''); -define('LOGIN_DB_SCHEMA', ''); +/************* HOST NAME *****************/ +// get the name without the port +list($HOST_NAME) = array_pad(explode(':', $_SERVER['HTTP_HOST'], 2), 2, null); +// set HOST name +define('HOST_NAME', $HOST_NAME); /************* CORE HOST SETTINGS *****************/ if (file_exists(BASE . CONFIGS . 'config.host.php')) { @@ -162,6 +122,14 @@ if (file_exists(BASE . CONFIGS . 'config.host.php')) { if (!isset($SITE_CONFIG)) { $SITE_CONFIG = []; } +// BAIL ON MISSING MASTER SITE CONFIG +if (!isset($SITE_CONFIG[HOST_NAME]['location'])) { + throw new \InvalidArgumentException( + 'Missing SITE_CONFIG entry for: "' . HOST_NAME . '". Contact Administrator' + ); +} +// set target first +define('TARGET', $_ENV['TARGET'] ?? $SITE_CONFIG[HOST_NAME]['location'] ?? 'test'); /************* DB ACCESS *****************/ if (file_exists(BASE . CONFIGS . 'config.db.php')) { require BASE . CONFIGS . 'config.db.php'; @@ -175,18 +143,6 @@ if (file_exists(BASE . CONFIGS . 'config.path.php')) { } /************* MASTER INIT *****************/ -// live frontend pages -// ** missing live domains ** -// get the name without the port -[$HOST_NAME] = array_pad(explode(':', $_SERVER['HTTP_HOST'], 2), 2, null); -// set HOST name -define('HOST_NAME', $HOST_NAME); -// BAIL ON MISSING MASTER SITE CONFIG -if (!isset($SITE_CONFIG[HOST_NAME]['location'])) { - throw new \InvalidArgumentException( - 'Missing SITE_CONFIG entry for: "' . HOST_NAME . '". Contact Administrator' - ); -} // BAIL ON MISSING DB CONFIG: // we have either no db selction for this host but have db config entries // or we have a db selection but no db config as array or empty @@ -237,35 +193,31 @@ define('DB_CONFIG', $DB_CONFIG[DB_CONFIG_NAME] ?? [ ]); // because we can't change constant, but we want to for db debug flag $GLOBALS['DB_CONFIG_SET'] = DB_CONFIG; -// define('DB_CONFIG_TARGET', SITE_CONFIG[$HOST_NAME]['db_host_target']); -// define('DB_CONFIG_OTHER', SITE_CONFIG[$HOST_NAME]['db_host_other']); -// override for login and global schemas -// where the edit* tables are -// define('LOGIN_DB_SCHEMA', PUBLIC_SCHEMA); -// where global tables are that are used by all schemas (eg queue tables for online, etc) -// define('GLOBAL_DB_SCHEMA', PUBLIC_SCHEMA); // debug settings, site lang, etc -define('TARGET', $SITE_CONFIG[HOST_NAME]['location'] ?? 'test'); define('DEBUG_LEVEL', $SITE_CONFIG[HOST_NAME]['debug_level'] ?? 'debug'); define('SITE_LOCALE', $SITE_CONFIG[HOST_NAME]['site_locale'] ?? DEFAULT_LOCALE); define('SITE_DOMAIN', str_replace(DIRECTORY_SEPARATOR, '', CONTENT_PATH)); define('SITE_ENCODING', $SITE_CONFIG[HOST_NAME]['site_encoding'] ?? DEFAULT_ENCODING); define('LOGIN_ENABLED', $SITE_CONFIG[HOST_NAME]['login_enabled'] ?? false); define('AUTH', $SITE_CONFIG[HOST_NAME]['auth'] ?? false); -// paths -// define('CSV_PATH', $PATHS[TARGET]['csv_path'] ?? ''); -// define('EXPORT_SCRIPT', $PATHS[TARGET]['perl_bin'] ?? ''); -// define('REDIRECT_URL', $PATHS[TARGET]['redirect_url'] ?? ''); +// NOTE: everything below is smarty related and should be removed from here /************* GENERAL PAGE TITLE ********/ define('G_TITLE', $_ENV['G_TITLE'] ?? ''); - +/************* LAYOUT WIDTHS *************/ +define('PAGE_WIDTH', $_ENV['SMARTY.PAGE_WIDTH'] ?? '100%'); +define('CONTENT_WIDTH', $_ENV['SMARTY.CONTENT_WIDTH'] ?? '100%'); +// the default template name +define('MASTER_TEMPLATE_NAME', $_ENV['MASTER_TEMPLATE_NAME'] ?? 'main_body.tpl'); +/************* JS LIBRARIES *************/ +define('USE_PROTOTYPE', false); +define('USE_SCRIPTACULOUS', false); +define('USE_JQUERY', true); /************ STYLE SHEETS / JS **********/ -define('ADMIN_STYLESHEET', 'edit.css'); -define('ADMIN_JAVASCRIPT', 'edit.js'); +define('ADMIN_STYLESHEET', $_ENV['ADMIN.STYLESHEET'] ?? 'edit.css'); +define('ADMIN_JAVASCRIPT', $_ENV['ADMIN.JAVASCRIPT'] ?? 'edit.js'); define('STYLESHEET', $_ENV['STYLESHEET'] ?? 'frontend.css'); define('JAVASCRIPT', $_ENV['JAVASCRIPT'] ?? 'frontend.js'); - // anything optional /************* INTERNAL ******************/ // any other global definitons in the config.other.php diff --git a/www/configs/config.other.php b/www/configs/config.other.php index 3a02357d..f9f0320f 100644 --- a/www/configs/config.other.php +++ b/www/configs/config.other.php @@ -15,6 +15,12 @@ define('EDIT_BASE_STYLESHEET', 'edit.css'); // define('SOME_ID', ); +/************* QUEUE TABLE *************/ +// if we have a dev/live system +// set_live is a per page/per item +// live_queue is a global queue system +// define('QUEUE', 'live_queue'); + /************* CONVERT *******************/ // this only needed if the external thumbnail create is used $paths = [ diff --git a/www/configs/config.path.php b/www/configs/config.path.php index 69a78d00..c3f6cb84 100644 --- a/www/configs/config.path.php +++ b/www/configs/config.path.php @@ -35,4 +35,9 @@ define('CONTENT_PATH', $folder . DIRECTORY_SEPARATOR); ], ];*/ +// paths +// define('CSV_PATH', $PATHS[TARGET]['csv_path'] ?? ''); +// define('EXPORT_SCRIPT', $PATHS[TARGET]['perl_bin'] ?? ''); +// define('REDIRECT_URL', $PATHS[TARGET]['redirect_url'] ?? ''); + // __END__ diff --git a/www/configs/config.php b/www/configs/config.php index 9f884b73..9358099f 100644 --- a/www/configs/config.php +++ b/www/configs/config.php @@ -53,6 +53,11 @@ for ( \gullevek\dotEnv\DotEnv::readEnvFile( $__DIR__PATH . $CONFIG_PATH_PREFIX . CONFIG_PATH ); + // load target file if it exists + \gullevek\dotEnv\DotEnv::readEnvFile( + $__DIR__PATH . $CONFIG_PATH_PREFIX . CONFIG_PATH, + '.target' + ); // load master config file that loads all other config files require $__DIR__PATH . $CONFIG_PATH_PREFIX . CONFIG_PATH . 'config.master.php'; break; diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 890119ff..205bd49f 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -423,14 +423,9 @@ class Login // LOGOUT TARGET if (!isset($options['logout_target'])) { - if (defined('LOGOUT_TARGET')) { - trigger_error( - 'loginMainCall: LOGOUT_TARGET should not be used', - E_USER_DEPRECATED - ); - $options['logout_target'] = LOGOUT_TARGET; - $this->logout_target = $options['logout_target']; - } + // defaults to '' + $options['logout_target'] = ''; + $this->logout_target = $options['logout_target']; } // *** PASSWORD SETTINGS diff --git a/www/lib/CoreLibs/Basic.php b/www/lib/CoreLibs/Basic.php index db57672a..c259bd79 100644 --- a/www/lib/CoreLibs/Basic.php +++ b/www/lib/CoreLibs/Basic.php @@ -103,11 +103,7 @@ class Basic 'VIDEOS', 'DOCUMENTS', 'PDFS', 'BINARIES', 'ICONS', 'UPLOADS', 'CSV', 'JS', 'CSS', 'TABLE_ARRAYS', 'SMARTY', 'LANG', 'CACHE', 'TMP', 'LOG', 'TEMPLATES', 'TEMPLATES_C', 'DEFAULT_LANG', 'DEFAULT_ENCODING', 'DEFAULT_HASH', - 'DEFAULT_ACL_LEVEL', 'LOGOUT_TARGET', 'PASSWORD_CHANGE', 'AJAX_REQUEST_TYPE', - 'USE_PROTOTYPE', 'USE_SCRIPTACULOUS', 'USE_JQUERY', 'PAGE_WIDTH', - 'MASTER_TEMPLATE_NAME', 'PUBLIC_SCHEMA', 'TEST_SCHEMA', 'DEV_SCHEMA', - 'LIVE_SCHEMA', 'DB_CONFIG_NAME', 'DB_CONFIG', 'TARGET', 'DEBUG', - 'SHOW_ALL_ERRORS' + 'DB_CONFIG_NAME', 'DB_CONFIG', 'TARGET' ] as $constant ) { if (!defined($constant)) { diff --git a/www/lib/CoreLibs/Language/GetLocale.php b/www/lib/CoreLibs/Language/GetLocale.php index d0e6f9e1..bedcea05 100644 --- a/www/lib/CoreLibs/Language/GetLocale.php +++ b/www/lib/CoreLibs/Language/GetLocale.php @@ -50,7 +50,6 @@ class GetLocale $locale = defined('SITE_LOCALE') && !empty(SITE_LOCALE) ? SITE_LOCALE : // else parse from default, if not 'en' - /** @phpstan-ignore-next-line DEFAULT_LOCALE could be empty */ (defined('DEFAULT_LOCALE') && !empty(DEFAULT_LOCALE) ? DEFAULT_LOCALE : 'en'); } @@ -97,7 +96,6 @@ class GetLocale $encoding = defined('SITE_ENCODING') && !empty(SITE_ENCODING) ? SITE_ENCODING : // or default encoding, if not 'UTF-8' - /** @phpstan-ignore-next-line DEFAULT_LOCALE could be empty */ (defined('DEFAULT_ENCODING') && !empty(DEFAULT_ENCODING) ? DEFAULT_ENCODING : 'UTF-8'); } From 816bb7c9ee15ae49dcada20bf1f0b8034c839521 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 28 Feb 2025 10:19:36 +0900 Subject: [PATCH 079/105] Allow encoding ovrride for htmlentities --- www/lib/CoreLibs/Convert/Html.php | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/www/lib/CoreLibs/Convert/Html.php b/www/lib/CoreLibs/Convert/Html.php index 2094dc55..ae057662 100644 --- a/www/lib/CoreLibs/Convert/Html.php +++ b/www/lib/CoreLibs/Convert/Html.php @@ -10,9 +10,16 @@ namespace CoreLibs\Convert; class Html { + /** @var int */ public const SELECTED = 0; + /** @var int */ public const CHECKED = 1; + // TODO: check for not valid htmlentites encoding + // as of PHP 8.4: https://www.php.net/manual/en/function.htmlentities.php + /** @#var array */ + // public const VALID_HTMLENT_ENCODINGS = []; + /** * full wrapper for html entities * @@ -22,14 +29,19 @@ class Html * encodes in UTF-8 * does not double encode * - * @param mixed $string string to html encode - * @param int $flags [default: ENT_QUOTES | ENT_HTML5] + * @param mixed $string string to html encode + * @param int $flags [default=ENT_QUOTES | ENT_HTML5] + * @param string $encoding [default=UTF-8] * @return mixed if string, encoded, else as is (eg null) */ - public static function htmlent(mixed $string, int $flags = ENT_QUOTES | ENT_HTML5): mixed - { + public static function htmlent( + mixed $string, + int $flags = ENT_QUOTES | ENT_HTML5, + string $encoding = 'UTF-8' + ): mixed { if (is_string($string)) { - return htmlentities($string, $flags, 'UTF-8', false); + // if not a valid encoding this will throw a warning and use UTF-8 + return htmlentities($string, $flags, $encoding, false); } return $string; } @@ -37,7 +49,7 @@ class Html /** * strips out all line breaks or replaced with given string * @param string $string string - * @param string $replace replace character, default ' ' + * @param string $replace [default=' '] replace character * @return string cleaned string without any line breaks */ public static function removeLB(string $string, string $replace = ' '): string From f3bd09529a4f7050bef7c8eb9f261e1d18a69edd Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 28 Feb 2025 10:29:04 +0900 Subject: [PATCH 080/105] phpstan fixes --- phpstan-bootstrap.php | 1 + www/lib/CoreLibs/Language/GetLocale.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/phpstan-bootstrap.php b/phpstan-bootstrap.php index 7011a7dd..77105949 100755 --- a/phpstan-bootstrap.php +++ b/phpstan-bootstrap.php @@ -10,5 +10,6 @@ $_SERVER['HTTP_HOST'] = 'soba.tokyo.tequila.jp'; define('BASE_NAME', ''); define('SITE_DOMAIN', ''); define('HOST_NAME', 'soba.tokyo.tequila.jp'); +define('DEFAULT_ENCODING', 'en_US.UTF-8'); // __END__ diff --git a/www/lib/CoreLibs/Language/GetLocale.php b/www/lib/CoreLibs/Language/GetLocale.php index bedcea05..2953621b 100644 --- a/www/lib/CoreLibs/Language/GetLocale.php +++ b/www/lib/CoreLibs/Language/GetLocale.php @@ -96,7 +96,7 @@ class GetLocale $encoding = defined('SITE_ENCODING') && !empty(SITE_ENCODING) ? SITE_ENCODING : // or default encoding, if not 'UTF-8' - (defined('DEFAULT_ENCODING') && !empty(DEFAULT_ENCODING) ? + (defined('DEFAULT_ENCODING') ? DEFAULT_ENCODING : 'UTF-8'); } } From 558694aa6c29a88ea1aa785b4efd7b63e1651050 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 28 Feb 2025 10:32:43 +0900 Subject: [PATCH 081/105] Fix DEFAULT_ENCODING that it is string --- www/configs/config.master.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/configs/config.master.php b/www/configs/config.master.php index 9d5dbfa4..70c52463 100644 --- a/www/configs/config.master.php +++ b/www/configs/config.master.php @@ -107,7 +107,7 @@ define('COMPILE_ID', 'COMPILE_' . BASE_NAME . '_' . SERVER_NAME_HASH); // default lang + encoding define('DEFAULT_LOCALE', $_ENV['LOCALE'] ?? 'en_US.UTF-8'); // default web page encoding setting -define('DEFAULT_ENCODING', array_pad(explode('.', DEFAULT_LOCALE, 2), 2, 'UTF-8')); +define('DEFAULT_ENCODING', (string)array_pad(explode('.', DEFAULT_LOCALE, 2), 2, 'UTF-8')[1]); /************* HOST NAME *****************/ // get the name without the port From bbcc642fdeaa89a8ee026194fdff22bbd100568f Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 7 Mar 2025 15:09:47 +0900 Subject: [PATCH 082/105] All "edit.js" development has moved to a new repository "Code-Blocks.javascript-utils" --- www/admin/layout/javascript/edit.jq.js | 126 +- .../javascript/translateTest-ja_JP.UTF-8.js | 5 + www/admin/layout/javascript/utils.js | 1540 +++++++++++++++++ www/admin/layout/javascript/utils.min.js | 3 + www/admin/layout/javascript/utils.min.js.map | 7 + www/admin/test.javascript.html | 37 + 6 files changed, 1680 insertions(+), 38 deletions(-) create mode 100644 www/admin/layout/javascript/translateTest-ja_JP.UTF-8.js create mode 100644 www/admin/layout/javascript/utils.js create mode 100644 www/admin/layout/javascript/utils.min.js create mode 100644 www/admin/layout/javascript/utils.min.js.map create mode 100644 www/admin/test.javascript.html diff --git a/www/admin/layout/javascript/edit.jq.js b/www/admin/layout/javascript/edit.jq.js index 6c2605ed..4423fb96 100644 --- a/www/admin/layout/javascript/edit.jq.js +++ b/www/admin/layout/javascript/edit.jq.js @@ -206,29 +206,57 @@ function __(string) if (!String.prototype.format) { String.prototype.format = function() { - var args = arguments; - return this.replace(/{(\d+)}/g, function(match, number) - { - return typeof args[number] != 'undefined' ? - args[number] : - match - ; - }); + console.error('[DEPRECATED] use formatString'); + return formatString(this, arguments); }; } +/** + * simple sprintf formater for replace + * usage: "{0} is cool, {1} is not".format("Alpha", "Beta"); + * First, checks if it isn't implemented yet. + * @param {String} string String with {..} entries + * @param {...any} args List of replacement + * @returns {String} Escaped string + */ +function formatString(string, ...args) +{ + return string.replace(/{(\d+)}/g, function(match, number) + { + return typeof args[number] != 'undefined' ? + args[number] : + match + ; + }); +} + /** * round to digits (float) - * @param {Float} Number.prototype.round Float type number to round + * @param {Number} Number.prototype.round Float type number to round * @param {Number} prec Precision to round to * @return {Float} Rounded number */ if (Number.prototype.round) { Number.prototype.round = function (prec) { - return Math.round(this * Math.pow(10, prec)) / Math.pow(10, prec); + console.error('[DEPRECATED] use roundPrecision'); + return roundPrecision(this, prec); }; } +/** + * round to digits (float) + * @param {Number} number Float type number to round + * @param {Number} precision Precision to round to + * @return {Number} Rounded number + */ +function roundPrecision(number, precision) +{ + if (!isNaN(number) || !isNaN(precision)) { + return number; + } + return Math.round(number * Math.pow(10, precision)) / Math.pow(10, precision); +} + /** * formats flat number 123456 to 123,456 * @param {Number} x number to be formated @@ -253,48 +281,70 @@ function convertLBtoBR(string) // eslint-disable-line no-unused-vars /** * escape HTML string - * @param {String} !String.prototype.escapeHTML HTML data string to be escaped - * @return {String} escaped string + * @param {String} String.prototype.escapeHTML HTML data string to be escaped + * @return {String} escaped string */ if (!String.prototype.escapeHTML) { String.prototype.escapeHTML = function() { - return this.replace(/[&<>"'/]/g, function (s) { - var entityMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - '\'': ''', - '/': '/' - }; - - return entityMap[s]; - }); + console.error('[DEPRECATED] use escapeHtml'); + return escapeHtml(this); }; } /** * unescape a HTML encoded string - * @param {String} !String.prototype.unescapeHTML data with escaped entries - * @return {String} HTML formated string + * @param {String} String.prototype.unescapeHTML data with escaped entries + * @return {String} HTML formated string */ if (!String.prototype.unescapeHTML) { String.prototype.unescapeHTML = function() { - return this.replace(/&[#\w]+;/g, function (s) { - var entityMap = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - ''': '\'', - '/': '/' - }; - - return entityMap[s]; - }); + console.error('[DEPRECATED] use unescapeHtml'); + return unescapeHtml(this); }; } +/** + * Escapes HTML in string + * @param {String} string Text to escape HTML in + * @returns {String} + */ +function escapeHtml(string) +{ + return string.replace(/[&<>"'/]/g, function (s) { + var entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '\'': ''', + '/': '/' + }; + + return entityMap[s]; + }); +} + +/** + * Unescape a HTML encoded string + * @param {String} string Text to unescape HTML in + * @returns {String} + */ +function unescapeHtml(string) +{ + return string.replace(/&[#\w]+;/g, function (s) { + var entityMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + ''': '\'', + '/': '/' + }; + + return entityMap[s]; + }); +} + /** * returns current timestamp (unix timestamp) * @return {Number} timestamp (in milliseconds) diff --git a/www/admin/layout/javascript/translateTest-ja_JP.UTF-8.js b/www/admin/layout/javascript/translateTest-ja_JP.UTF-8.js new file mode 100644 index 00000000..6e221691 --- /dev/null +++ b/www/admin/layout/javascript/translateTest-ja_JP.UTF-8.js @@ -0,0 +1,5 @@ +var i18n = { + "Original": "Translated" +}; + +// __END__ diff --git a/www/admin/layout/javascript/utils.js b/www/admin/layout/javascript/utils.js new file mode 100644 index 00000000..749e5d77 --- /dev/null +++ b/www/admin/layout/javascript/utils.js @@ -0,0 +1,1540 @@ +// www/admin/layout/js-dev/utils/JavaScriptHelpers.mjs +function errorCatch(err) { + if (err.stack) { + if (err.lineNumber) { + console.error("ERROR[%s:%s] ", err.name, err.lineNumber, err); + } else if (err.line) { + console.error("ERROR[%s:%s] ", err.name, err.line, err); + } else { + console.error("ERROR[%s] ", err.name, err); + } + } else if (err.number) { + console.error("ERROR[%s:%s] %s", err.name, err.number, err.message); + console.error("ERROR[description] %s", err.description); + } else { + console.error("ERROR[%s] %s", err.name, err.message); + } +} +function isFunction(name) { + if (typeof window[name] !== "undefined" && typeof window[name] === "function") { + return true; + } else { + return false; + } +} +function executeFunctionByName(functionName, context) { + var args = Array.prototype.slice.call(arguments, 2); + var namespaces = functionName.split("."); + var func = namespaces.pop(); + if (func == void 0) { + throw new Error("Cannot get function from namespaces: " + functionName); + } + for (var i = 0; i < namespaces.length; i++) { + context = context[namespaces[i]]; + } + return context[func].apply(context, args); +} +function isObject(val) { + if (val === null) { + return false; + } + return typeof val === "function" || typeof val === "object"; +} +function getObjectCount(object) { + return Object.keys(object).length; +} +function keyInObject(key, object) { + return Object.prototype.hasOwnProperty.call(object, key) ? true : false; +} +function getKeyByValue(object, value) { + return Object.keys(object).find((key) => object[key] === value) ?? ""; +} +function valueInObject(object, value) { + return Object.keys(object).find((key) => object[key] === value) ? true : false; +} +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; +} + +// www/admin/layout/js-dev/utils/DomHelpers.mjs +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); + if (__winName == null) { + return; + } + __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"); + let ta_value = ta.getAttribute("value"); + let theRows = []; + if (ta_value != null) { + theRows = ta_value.split("\n"); + } + var numNewRows = 0; + for (var i = 0; i < theRows.length; i++) { + if (theRows[i].length + 2 > maxChars) { + numNewRows += Math.ceil((theRows[i].length + 2) / maxChars); + } + } + ta.setAttribute("row", (numNewRows + theRows.length).toString()); +} +function exists(id) { + return $("#" + id).length > 0 ? true : false; +} + +// www/admin/layout/js-dev/utils/HtmlElementCreator.mjs +var HtmlElementCreator = class { + /** + * reates object for DOM element creation flow + * @param {String} tag must set tag (div, span, etc) + * @param {String} [id=''] optional set for id, if input, select will be used for name + * @param {String} [content=''] text content inside, is skipped if sub elements exist + * @param {Array} [css=[]] array for css tags + * @param {Object} [options={}] anything else (value, placeholder, OnClick, style) + * @return {Object} created element as an object + */ + cel(tag, id = "", content = "", css = [], options = {}) { + return { + tag, + id, + name: options.name, + // override name if set [name gets ignored in tree build anyway] + content, + css, + options, + sub: [] + }; + } + /** + * attach a cel created object to another to create a basic DOM tree + * @param {Object} base object where to attach/search + * @param {Object} attach the object to be attached + * @param {String} [id=''] optional id, if given search in base for this id and attach there + * @return {Object} "none", technically there is no return needed as it is global attach + */ + 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 < base.sub.length; i++) { + this.ael(base.sub[i], attach, id); + } + } + } + } else { + base.sub.push(deepCopyFunction(attach)); + } + return base; + } + /** + * directly attach n elements to one master base element + * this type does not support attach with optional id + * @param {Object} base object to where we attach the elements + * @param {...Object} attach attach 1..n: attach directly to the base element those attachments + * @return {Object} "none", technically there is no return needed, global attach + */ + aelx(base, ...attach) { + for (var i = 0; i < attach.length; i++) { + base.sub.push(deepCopyFunction(attach[i])); + } + return base; + } + /** + * same as aelx, but instead of using objects as parameters + * get an array of objects to attach + * @param {Object} base object to where we attach the elements + * @param {Array} attach array of objects to attach + * @return {Object} "none", technically there is no return needed, global attach + */ + aelxar(base, attach) { + for (var i = 0; i < attach.length; i++) { + base.sub.push(deepCopyFunction(attach[i])); + } + return base; + } + /** + * resets the sub elements of the base element given + * @param {Object} base cel created element + * @return {Object} returns reset base element + */ + rel(base) { + base.sub = []; + return base; + } + /** + * searches and removes style from css array + * @param {Object} _element element to work one + * @param {String} css style sheet to remove (name) + * @return {Object} returns full element + */ + rcssel(_element, css) { + var css_index = _element.css.indexOf(css); + if (css_index > -1) { + _element.css.splice(css_index, 1); + } + return _element; + } + /** + * adds a new style sheet to the element given + * @param {Object} _element element to work on + * @param {String} css style sheet to add (name) + * @return {Object} returns full element + */ + acssel(_element, css) { + var css_index = _element.css.indexOf(css); + if (css_index == -1) { + _element.css.push(css); + } + return _element; + } + /** + * removes one css and adds another + * is a wrapper around rcssel/acssel + * @param {Object} _element element to work on + * @param {String} rcss style to remove (name) + * @param {String} acss style to add (name) + * @return {Object} returns full element + */ + scssel(_element, rcss, acss) { + this.rcssel(_element, rcss); + this.acssel(_element, acss); + } + /** + * parses the object tree created with cel/ael and converts it into an HTML string + * that can be inserted into the page + * @param {Object} tree object tree with dom element declarations + * @return {String} HTML string that can be used as innerHTML + */ + phfo(tree) { + let name_elements = [ + "button", + "fieldset", + "form", + "iframe", + "input", + "map", + "meta", + "object", + "output", + "param", + "select", + "textarea" + ]; + let skip_options = [ + "id", + "name", + "class" + ]; + let no_close = [ + "input", + "br", + "img", + "hr", + "area", + "col", + "keygen", + "wbr", + "track", + "source", + "param", + "command", + // only in header + "base", + "meta", + "link", + "embed" + ]; + var content = []; + var line = "<" + tree.tag; + var i; + if (tree.id) { + line += ' id="' + tree.id + '"'; + if (name_elements.includes(tree.tag)) { + line += ' name="' + (tree.name ? tree.name : tree.id) + '"'; + } + } + if (isObject(tree.css) && tree.css.length > 0) { + line += ' class="'; + for (i = 0; i < tree.css.length; i++) { + line += tree.css[i] + " "; + } + line = line.slice(0, -1); + line += '"'; + } + if (isObject(tree.options)) { + for (const [key, item] of Object.entries(tree.options)) { + if (!skip_options.includes(key)) { + line += " " + key + '="' + item + '"'; + } + } + } + line += ">"; + content.push(line); + if (isObject(tree.sub) && tree.sub.length > 0) { + if (tree.content) { + content.push(tree.content); + } + for (i = 0; i < tree.sub.length; i++) { + content.push(this.phfo(tree.sub[i])); + } + } else if (tree.content) { + content.push(tree.content); + } + if (!no_close.includes(tree.tag)) { + content.push(""); + } + return content.join(""); + } + /** + * Create HTML elements from array list + * as a flat element without master object file + * Is like tree.sub call + * @param {Array} list Array of cel created objects + * @return {String} HTML String + */ + phfa(list) { + var content = []; + for (var i = 0; i < list.length; i++) { + content.push(this.phfo(list[i])); + } + return content.join(""); + } +}; + +// www/admin/layout/js-dev/utils/HtmlHelpers.mjs +var dom = new HtmlElementCreator(); +function escapeHtml(string) { + return string.replace(/[&<>"'/]/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 = false, return_string = false, 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 = false, return_string = false, sort = "", onchange = "") { + var content = []; + var element_select; + var select_options = {}; + var element_option; + var data_list = []; + var value; + var options = {}; + if (multiple > 0) { + select_options.multiple = ""; + if (multiple > 1) { + select_options.size = multiple; + } + } + if (onchange) { + select_options.OnChange = onchange; + } + element_select = dom.cel("select", name, "", [], select_options); + if (sort == "keys") { + data_list = Object.keys(data).sort(); + } else if (sort == "values") { + data_list = Object.keys(data).sort((a, b) => ("" + data[a]).localeCompare(data[b])); + } else { + data_list = Object.keys(data); + } + for (const key of data_list) { + value = data[key]; + options = { + "label": value, + "value": key, + "selected": "" + }; + if (multiple == 0 && !Array.isArray(selected) && selected == key) { + options.selected = ""; + } + if (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) { + content.push(dom.phfo(element_select)); + return content.join(""); + } else { + return element_select; + } + } else { + if (return_string) { + for (var i = 0; i < element_select.sub.length; i++) { + content.push(dom.phfo(element_select.sub[i])); + } + return content.join(""); + } else { + return element_select.sub; + } + } +} +function html_options_refill(name, data, sort = "") { + var element_option; + var option_selected; + var data_list = []; + var value; + if (document.getElementById(name)) { + if (sort == "keys") { + data_list = Object.keys(data).sort(); + } else if (sort == "values") { + data_list = Object.keys(data).sort((a, b) => ("" + data[a]).localeCompare(data[b])); + } else { + data_list = Object.keys(data); + } + [].forEach.call(document.querySelectorAll("#" + name + " :checked"), function(elm) { + option_selected = elm.value; + }); + loadEl(name).innerHTML = ""; + for (const key of data_list) { + value = data[key]; + element_option = document.createElement("option"); + element_option.label = value; + element_option.value = key; + element_option.innerHTML = value; + if (key == option_selected) { + element_option.selected = true; + } + loadEl(name).appendChild(element_option); + } + } +} + +// www/admin/layout/js-dev/utils/MathHelpers.mjs +function dec2hex(dec) { + return ("0" + dec.toString(16)).substring(-2); +} +function getRandomIntInclusive(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1) + min); +} +function roundPrecision(number, precision) { + if (!isNaN(number) || !isNaN(precision)) { + return number; + } + return Math.round(number * Math.pow(10, precision)) / Math.pow(10, precision); +} + +// www/admin/layout/js-dev/utils/StringHelpers.mjs +function formatString(string, ...args) { + return string.replace(/{(\d+)}/g, function(match, number) { + return typeof args[number] != "undefined" ? args[number] : match; + }); +} +function numberWithCommas(number) { + var parts = number.toString().split("."); + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); + return parts.join("."); +} +function convertLBtoBR(string) { + return string.replace(/(?:\r\n|\r|\n)/g, "
"); +} + +// www/admin/layout/js-dev/utils/DateTimeHelpers.mjs +function getTimestamp() { + var date = /* @__PURE__ */ new Date(); + return date.getTime(); +} + +// www/admin/layout/js-dev/utils/UniqIdGenerators.mjs +function generateId(len) { + var arr = new Uint8Array((len || 40) / 2); + (window.crypto || // @ts-ignore + window.msCrypto).getRandomValues(arr); + return Array.from(arr, self.dec2hex).join(""); +} +function randomIdF() { + return Math.random().toString(36).substring(2); +} + +// www/admin/layout/js-dev/utils/ResizingAndMove.mjs +function getWindowSize() { + var width, height; + width = window.innerWidth || (window.document.documentElement.clientWidth || window.document.body.clientWidth); + height = window.innerHeight || (window.document.documentElement.clientHeight || window.document.body.clientHeight); + return { + width, + height + }; +} +function getScrollOffset() { + var left, top; + left = window.pageXOffset || (window.document.documentElement.scrollLeft || window.document.body.scrollLeft); + top = window.pageYOffset || (window.document.documentElement.scrollTop || window.document.body.scrollTop); + return { + left, + top + }; +} +function getScrollOffsetOpener() { + var left, top; + left = opener.window.pageXOffset || (opener.document.documentElement.scrollLeft || opener.document.body.scrollLeft); + top = opener.window.pageYOffset || (opener.document.documentElement.scrollTop || opener.document.body.scrollTop); + return { + left, + top + }; +} +function setCenter(id, left, top) { + var dimensions = { + height: $("#" + id).height() ?? 0, + width: $("#" + id).width() ?? 0 + }; + var type = $("#" + id).css("position"); + var viewport = this.getWindowSize(); + var offset = this.getScrollOffset(); + if (left) { + $("#" + id).css({ + left: viewport.width / 2 - dimensions.width / 2 + offset.left + "px" + }); + } + if (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 == void 0) { + return; + } + if ($("#" + element).length) { + $(base).animate({ + scrollTop: element_offset.top - offset + }, duration); + } + } catch (err) { + errorCatch(err); + } +} +function goTo(target) { + loadEl(target).scrollIntoView({ + behavior: "smooth" + }); +} + +// www/admin/layout/js-dev/utils/FormatBytes.mjs +function formatBytes(bytes) { + var i = -1; + 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 (isNaN(bytes)) { + return bytes.toString(); + } + var i = Math.floor(Math.log(bytes) / Math.log(1024)); + var sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + return ((bytes / Math.pow(1024, i)).toFixed(2) + " " + sizes[i]).toString(); +} +function stringByteFormat(bytes) { + if (!(typeof bytes === "string" || bytes instanceof String)) { + return bytes.toString(); + } + let valid_units = "bkmgtpezy"; + let regex = /([\d.,]*)\s?(eb|pb|tb|gb|mb|kb|e|p|t|g|m|k|b)$/i; + let matches = bytes.match(regex); + if (matches !== null) { + let m1 = parseFloat(matches[1].replace(/[^0-9.]/, "")); + let m2 = matches[2].replace(/[^bkmgtpezy]/i, "").charAt(0).toLowerCase(); + if (m2) { + bytes = m1 * Math.pow(1024, valid_units.indexOf(m2)); + } + } + return bytes; +} + +// www/admin/layout/js-dev/utils/UrlParser.mjs +function parseQueryString(query = "", return_key = "") { + if (!query) { + query = window.location.search.substring(1); + } + var vars = query.split("&"); + var query_string = {}; + for (var i = 0; i < vars.length; i++) { + var pair = vars[i].split("="); + var key = decodeURIComponent(pair[0]); + var value = decodeURIComponent(pair[1]); + if (!key || value === "undefined") { + continue; + } + if (typeof query_string[key] === "undefined") { + query_string[key] = decodeURIComponent(value); + } else if (typeof query_string[key] === "string") { + var arr = [query_string[key], decodeURIComponent(value)]; + query_string[key] = arr; + } else { + query_string[key].push(decodeURIComponent(value)); + } + } + if (return_key) { + if (keyInObject(return_key, query_string)) { + return query_string[return_key]; + } else { + return ""; + } + } else { + return query_string; + } +} +function getQueryStringParam(search = "", query = "", single = false) { + if (!query) { + query = window.location.href; + } + const url = new URL(query); + let param = null; + if (search) { + let _params = url.searchParams.getAll(search); + if (_params.length == 1 || single === true) { + param = _params[0]; + } else if (_params.length > 1) { + param = _params; + } + } else { + param = {}; + for (const [key] of url.searchParams.entries()) { + if (typeof param[key] === "undefined") { + let _params = url.searchParams.getAll(key); + param[key] = _params.length < 2 || single === true ? _params[0] : _params; + } + } + } + return param; +} + +// www/admin/layout/js-dev/utils/l10nTranslation.mjs +var l10nTranslation = class { + #i18n = {}; + constructor(i18n2) { + this.#i18n = i18n2; + } + /** + * uses the i18n object created in the translation template + * that is filled from gettext in PHP + * @param {String} string text to translate + * @return {String} translated text (based on PHP selected language) + */ + __(string) { + if (typeof this.#i18n !== "undefined" && isObject(this.#i18n) && this.#i18n[string]) { + return this.#i18n[string]; + } else { + return string; + } + } +}; + +// www/admin/layout/js-dev/utils/LoginMenu.mjs +var hec = new HtmlElementCreator(); +var l10n = new l10nTranslation(i18n ?? {}); +function loginLogout() { + const form = document.createElement("form"); + form.method = "post"; + const hiddenField = document.createElement("input"); + hiddenField.type = "hidden"; + hiddenField.name = "login_logout"; + hiddenField.value = "Logout"; + form.appendChild(hiddenField); + document.body.appendChild(form); + form.submit(); +} +function createLoginRow(login_string, header_id = "mainHeader") { + if (exists(header_id)) { + if (!exists("loginRow")) { + $("#" + header_id).html(hec.phfo(hec.cel("div", "loginRow", "", ["loginRow", "flx-spbt"]))); + } + $("#loginRow").html(hec.phfo(hec.cel("div", "loginRow-name", login_string))); + $("#loginRow").append(hec.phfo(hec.cel("div", "loginRow-info", ""))); + $("#loginRow").append(hec.phfo( + hec.aelx( + // outer div + hec.cel("div", "loginRow-logout"), + // inner element + hec.cel("input", "logout", "", [], { + value: l10n.__("Logout"), + type: "button", + onClick: "loginLogout()" + }) + ) + )); + } +} +function createNavMenu(nav_menu, header_id = "mainHeader") { + if (isObject(nav_menu) && getObjectCount(nav_menu) > 1) { + if (!exists("menuRow")) { + $("#" + header_id).html(hec.phfo(hec.cel("div", "menuRow", "", ["menuRow", "flx-s"]))); + } + var content = []; + $.each(nav_menu, function(key, item) { + if (key != 0) { + content.push(hec.phfo(hec.cel("div", "", "·", ["pd-2"]))); + } + if (item.enabled) { + if (window.location.href.indexOf(item.url) != -1) { + item.selected = 1; + } + content.push(hec.phfo( + hec.aelx( + hec.cel("div"), + hec.cel("a", "", item.name, ["pd-2"].concat(item.selected ? "highlight" : ""), { + href: item.url + }) + ) + )); + } + }); + $("#menuRow").html(content.join("")); + } else { + $("#menuRow").hide(); + } +} + +// www/admin/layout/js-dev/utils/ActionIndicatorOverlayBox.mjs +function actionIndicator(loc, overlay = true) { + if ($("#indicator").is(":visible")) { + this.actionIndicatorHide(loc, overlay); + } else { + this.actionIndicatorShow(loc, overlay); + } +} +function actionIndicatorShow(loc, overlay = true) { + if (!$("#indicator").is(":visible")) { + if (!$("#indicator").hasClass("progress")) { + $("#indicator").addClass("progress"); + } + setCenter("indicator", true, true); + $("#indicator").show(); + } + if (overlay === true) { + this.overlayBoxShow(); + } +} +function actionIndicatorHide(loc, overlay = true) { + $("#indicator").hide(); + if (overlay === true) { + overlayBoxHide(); + } +} +function overlayBoxShow() { + if ($("#overlayBox").is(":visible")) { + $("#overlayBox").css("zIndex", "100"); + } else { + $("#overlayBox").show(); + $("#overlayBox").css("zIndex", "98"); + } +} +function overlayBoxHide() { + if (parseInt($("#overlayBox").css("zIndex")) >= 100) { + $("#overlayBox").css("zIndex", "98"); + } else { + $("#overlayBox").hide(); + } +} +function setOverlayBox() { + if (!$("#overlayBox").is(":visible")) { + $("#overlayBox").show(); + } +} +function hideOverlayBox() { + if ($("#overlayBox").is(":visible")) { + $("#overlayBox").hide(); + } +} +function ClearCall() { + $("#actionBox").html(""); + $("#actionBox").hide(); + $("#overlayBox").hide(); +} +var ActionIndicatorOverlayBox = class { + // open overlay boxes counter for z-index + #GL_OB_S = 100; + #GL_OB_BASE = 100; + /** + * show action indicator + * - checks if not existing and add + * - only shows if not visible (else ignore) + * - overlaybox check is called and shown on a fixzed + * zIndex of 1000 + * - indicator is page centered + * @param {String} loc ID string, only used for console log + */ + showActionIndicator(loc) { + if ($("#indicator").length == 0) { + var el = document.createElement("div"); + el.className = "progress hide"; + el.id = "indicator"; + $("body").append(el); + } else if (!$("#indicator").hasClass("progress")) { + $("#indicator").addClass("progress").hide(); + } + if (!$("#indicator").is(":visible")) { + this.checkOverlayExists(); + if (!$("#overlayBox").is(":visible")) { + $("#overlayBox").show(); + } + $("#overlayBox").css("zIndex", 1e3); + $("#indicator").show(); + setCenter("indicator", true, true); + } + } + /** + * hide action indicator, if it is visiable + * If the global variable GL_OB_S is > GL_OB_BASE then + * the overlayBox is not hidden but the zIndex + * is set to this value + * @param {String} loc ID string, only used for console log + */ + hideActionIndicator(loc) { + if ($("#indicator").is(":visible")) { + $("#indicator").hide(); + if (this.#GL_OB_S > this.#GL_OB_BASE) { + $("#overlayBox").css("zIndex", this.#GL_OB_S); + } else { + $("#overlayBox").hide(); + $("#overlayBox").css("zIndex", this.#GL_OB_BASE); + } + } + } + /** + * checks if overlayBox exists, if not it is + * added as hidden item at the body end + */ + checkOverlayExists() { + if ($("#overlayBox").length == 0) { + var el = document.createElement("div"); + el.className = "overlayBoxElement hide"; + el.id = "overlayBox"; + $("body").append(el); + } + } + /** + * show overlay box + * if not visible show and set zIndex to 10 (GL_OB_BASE) + * if visible, add +1 to the GL_OB_S variable and + * up zIndex by this value + */ + showOverlayBoxLayers(el_id) { + if (!$("#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); + if (el_id) { + if ($("#" + el_id).length > 0) { + $("#" + el_id).css("zIndex", this.#GL_OB_S + 1); + $("#" + el_id).show(); + } + } + } + /** + * hide overlay box + * lower GL_OB_S value by -1 + * if we are 10 (GL_OB_BASE) or below hide the overlayIndex + * and set zIndex and GL_OB_S to 0 + * else just set zIndex to the new GL_OB_S value + * @param {String} el_id Target to hide layer + */ + hideOverlayBoxLayers(el_id = "") { + this.#GL_OB_S--; + if (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); + } else { + $("#overlayBox").css("zIndex", this.#GL_OB_S); + } + if (el_id) { + $("#" + el_id).hide(); + $("#" + el_id).css("zIndex", 0); + } + } + /** + * only for single action box + */ + clearCallActionBox() { + $("#actionBox").html(""); + $("#actionBox").hide(); + this.hideOverlayBoxLayers(); + } +}; + +// www/admin/layout/js-dev/utils/ActionBox.mjs +var ActionBox = class { + // open overlay boxes counter for z-index + zIndex = { + base: 100, + max: 110, + indicator: 0, + boxes: {}, + active: [], + top: "" + }; + // general action box storage + action_box_storage = {}; + // set to 10 min (*60 for seconds, *1000 for microseconds) + action_box_cache_timeout = 10 * 60 * 1e3; + hec; + l10n; + /** + * action box creator + * @param {Object} hec HtmlElementCreator + * @param {Object} l10n l10nTranslation + */ + constructor(hec3, l10n3) { + this.hec = hec3; + this.l10n = l10n3; + } + /** + * Show an action box + * @param {string} [target_id='actionBox'] where to attach content to, if not exists, create new + * @param {string} [content=''] content to add to the box + * @param {array} [action_box_css=[]] additional css elements for the action box + * @param {number} [override=0] override size adjust + * @param {number} [content_override=0] override content size adjust + */ + 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); + } + /** + * Fill action box with content, create it if it does not existgs + * @param {string} [target_id='actionBox'] where to attach content to, if not exists, create new + * @param {string} [content=''] content to add to the box + * @param {array} [action_box_css=[]] additional css elements for the action box + */ + fillActionBox(target_id = "actionBox", content = "", action_box_css = []) { + if (!exists(target_id)) { + $("#mainContainer").after( + this.hec.phfo(this.hec.cel("div", target_id, "", ["actionBoxElement", "hide"].concat(action_box_css))) + ); + } + $("#" + target_id).html(content); + } + /** + * Adjust the size of the action box + * @param {string} [target_id='actionBox'] which actionBox to work on + * @param {number} [override=0] override size adjust + * @param {number} [content_override=0] override content size adjust + */ + adjustActionBox(target_id = "actionBox", override = 0, content_override = 0) { + this.adjustActionBoxHeight(target_id, override, content_override); + setCenter(target_id, true, true); + } + /** + * hide any open action boxes and hide overlay + */ + hideAllActionBoxes() { + $('#actionBox, div[id^="actionBox-"].actionBoxElement').hide(); + $("#overlayBox").hide(); + } + /** + * hide action box, but do not clear content + * DEPRECATED + * @param {string} [target_id='actionBox'] + */ + hideActionBox(target_id = "actionBox") { + this.closeActionBoxFloat(target_id, false); + } + /** + * Just show and adjust the box + * DEPRECAED + * @param {string} [target_id='actionBox'] which actionBox to work on + * @param {number} [override=0] override size adjust + * @param {number} [content_override=0] override content size adjust + * @param {Boolean} [hide_all=false] if set to true, hide all other action boxes + */ + showActionBox(target_id = "actionBox", override = 0, content_override = 0, hide_all = true) { + this.showActionBoxFloat(target_id, override, content_override, hide_all); + } + /** + * close an action box with default clear content + * for just hide use hideActionBox + * DEPRECATED + * @param {String} [target_id='actionBox'] which action box to close, default is set + * @param {Boolean} [clean=true] if set to false will not remove html content, just hide + */ + closeActionBox(target_id = "actionBox", clean = true) { + this.closeActionBoxFloat(target_id, clean); + } + /** + * TODO: better stacked action box: OPEN + * @param {string} [target_id='actionBox'] which actionBox to work on + * @param {number} [override=0] override size adjust + * @param {number} [content_override=0] override content size adjust + * @param {boolean} [hide_all=false] if set to true, hide all other action boxes + */ + showActionBoxFloat(target_id = "actionBox", override = 0, content_override = 0, hide_all = false) { + if (hide_all === true) { + this.hideAllActionBoxes(); + } + if (!exists("overlayBox")) { + $("body").prepend(this.hec.phfo(this.hec.cel("div", "overlayBox", "", ["overlayBoxElement"]))); + $("#overlayBox").css("zIndex", this.zIndex.base); + } + $("#overlayBox").show(); + if (!keyInObject(target_id, this.zIndex.boxes)) { + this.zIndex.boxes[target_id] = this.zIndex.max; + this.zIndex.max += 10; + } else if (this.zIndex.boxes[target_id] + 10 < this.zIndex.max) { + this.zIndex.boxes[target_id] = this.zIndex.max; + this.zIndex.max += 10; + } + if (!this.zIndex.indicator) { + $("#overlayBox").css("zIndex", this.zIndex.boxes[target_id] - 1); + } + $("#" + target_id).css("zIndex", this.zIndex.boxes[target_id]).show(); + if (this.zIndex.active.indexOf(target_id) == -1) { + this.zIndex.active.push(target_id); + } + this.zIndex.top = target_id; + this.adjustActionBox(target_id, override, content_override); + } + /** + * TODO: better stacked action box: CLOSE + * @param {String} [target_id='actionBox'] which action box to close, default is set + * @param {Boolean} [clean=true] if set to false will not remove html content, just hide + */ + closeActionBoxFloat(target_id = "actionBox", clean = true) { + if (!exists(target_id)) { + return; + } + if (keyInObject(target_id, this.action_box_storage) && clean === true) { + this.action_box_storage[target_id] = {}; + } + if (clean === true) { + $("#" + target_id).html(""); + } + $("#" + target_id).hide(); + let idx = this.zIndex.active.indexOf(target_id); + this.zIndex.active.splice(idx, 1); + let visible_zIndexes = $('#actionBox:visible, div[id^="actionBox-"].actionBoxElement:visible').map((i, el) => ({ + id: el.id, + zIndex: $("#" + el.id).css("zIndex") + })).get(); + if (visible_zIndexes.length > 0) { + let max_zIndex = 0; + let max_el_id = ""; + for (let zIndex_el of visible_zIndexes) { + if (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(); + } + } + /** + * create a new action box and fill it with basic elements + * @param {String} [target_id='actionBox'] + * @param {String} [title=''] + * @param {Object} [contents={}] + * @param {Object} [headers={}] + * @param {Boolean} [show_close=true] + * @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)) { + this.action_box_storage[target_id] = {}; + } + let header_css = []; + if (keyInObject("header_css", settings)) { + header_css = settings.header_css; + } + let action_box_css = []; + if (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 === true ? [ + // title + this.hec.cel("div", "", title, ["fs-b", "w-80"]), + // close button + 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"]) + ] + ) + )); + if (getObjectCount(headers) > 0) { + if (keyInObject("raw_string", headers)) { + elements.push(headers.raw_string); + } else { + elements.push(this.hec.phfo(headers)); + } + } + if (getObjectCount(contents) > 0) { + if (keyInObject("raw_string", contents)) { + elements.push(contents.raw_string); + } else { + elements.push(this.hec.phfo(contents)); + } + } else { + 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 === true ? [ + // dummy spacer + this.hec.cel("div", "", "", ["fs-b", "w-80"]), + // close button + 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); + } + /** + * adjusts the action box height based on content and window height of browser + * TODO: border on outside/and other margin things need to be added in overall adjustment + * @param {String} [target_id='actionBox'] target id, if not set, fall back to default + * @param {Number} [override=0] override value to add to the actionBox height + * @param {Number} [content_override=0] override the value from _content block if it exists + */ + adjustActionBoxHeight(target_id = "actionBox", override = 0, content_override = 0) { + var new_height = 0; + var dim = {}; + var abc_dim = {}; + var content_id = ""; + if (isNaN(override)) { + override = 0; + } + if (isNaN(content_override)) { + content_override = 0; + } + switch (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": "" + }); + }); + if (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; + } + if (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; + } + if (exists(content_id + "_content")) { + if (content_override > 0) { + console.log("Target: %s, Action box Content Override: %s", target_id, content_override); + new_height += content_override; + } else { + 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; + } + } + if (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) { + if (exists(content_id + "_content")) { + if (!$("#" + 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 { + if (exists(content_id + "_content")) { + if ($("#" + 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"); + } +}; + +// www/admin/layout/js-dev/utilsAll.mjs +var aiob = new ActionIndicatorOverlayBox(); +var hec2 = new HtmlElementCreator(); +var l10n2 = new l10nTranslation(i18n ?? {}); +var ab = new ActionBox(hec2, l10n2); +if (!String.prototype.format) { + String.prototype.format = function() { + console.error("[DEPRECATED] use StringHelpers.formatString"); + return formatString(this, arguments); + }; +} +if (Number.prototype.round) { + Number.prototype.round = function(prec) { + console.error("[DEPRECATED] use MathHelpers.roundPrecision"); + return roundPrecision(this, prec); + }; +} +if (!String.prototype.escapeHTML) { + String.prototype.escapeHTML = function() { + console.error("[DEPRECATED] use HtmlHelpers.escapeHtml"); + return escapeHtml(this); + }; +} +if (!String.prototype.unescapeHTML) { + String.prototype.unescapeHTML = function() { + console.error("[DEPRECATED] use HtmlHelpers.unescapeHtml"); + return 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 l10n2.__(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 = true) { + actionIndicator(loc, overlay); +} +function actionIndicatorShow2(loc, overlay = true) { + actionIndicatorShow(loc, overlay); +} +function actionIndicatorHide2(loc, overlay = true) { + 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 hec2.cel(tag, id, content, css, options); +} +function ael(base, attach, id = "") { + return hec2.ael(base, attach, id); +} +function aelx(base, ...attach) { + return hec2.aelx(base, attach); +} +function aelxar(base, attach) { + return hec2.aelxar(base, attach); +} +function rel(base) { + return hec2.rel(base); +} +function rcssel(_element, css) { + return hec2.rcssel(_element, css); +} +function acssel(_element, css) { + return hec2.acssel(_element, css); +} +function scssel(_element, rcss, acss) { + hec2.scssel(_element, rcss, acss); +} +function phfo(tree) { + return hec2.phfo(tree); +} +function phfa(list) { + return hec2.phfa(list); +} +function html_options2(name, data, selected = "", options_only = false, return_string = false, sort = "") { + return html_options(name, data, selected, options_only, return_string, sort); +} +function html_options_block2(name, data, selected = "", multiple = 0, options_only = false, return_string = false, 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 = false) { + return getQueryStringParam(search, query, single); +} +function loginLogout2() { + loginLogout(); +} +function createLoginRow2(login_string, header_id = "mainHeader") { + createLoginRow(login_string, header_id); +} +function createNavMenu2(nav_menu, header_id = "mainHeader") { + 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 = true) { + ab.showActionBox(target_id, override, content_override, hide_all); +} +function closeActionBox(target_id = "actionBox", clean = true) { + ab.closeActionBox(target_id, clean); +} +function showActionBoxFloat(target_id = "actionBox", override = 0, content_override = 0, hide_all = false) { + ab.showActionBoxFloat(target_id, override, content_override, hide_all); +} +function closeActionBoxFloat(target_id = "actionBox", clean = true) { + ab.closeActionBoxFloat(target_id, clean); +} +function createActionBox(target_id = "actionBox", title = "", contents = {}, headers = {}, settings = {}, show_close = true) { + 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); +} diff --git a/www/admin/layout/javascript/utils.min.js b/www/admin/layout/javascript/utils.min.js new file mode 100644 index 00000000..3b9a3af5 --- /dev/null +++ b/www/admin/layout/javascript/utils.min.js @@ -0,0 +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!!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){this.rcssel(_element,rcss),this.acssel(_element,acss)}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("0"+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;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(isNaN(bytes))return bytes.toString();var i=Math.floor(Math.log(bytes)/Math.log(1024)),sizes=["B","KB","MB","GB","TB","PB","EB","ZB","YB"];return((bytes/Math.pow(1024,i)).toFixed(2)+" "+sizes[i]).toString()}function stringByteFormat(bytes){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 bytes}function parseQueryString(query="",return_key=""){query||(query=window.location.search.substring(1));for(var vars=query.split("&"),query_string={},i=0;i"u")query_string[key]=decodeURIComponent(value);else if(typeof query_string[key]=="string"){var arr=[query_string[key],decodeURIComponent(value)];query_string[key]=arr}else query_string[key].push(decodeURIComponent(value))}return return_key?keyInObject(return_key,query_string)?query_string[return_key]:"":query_string}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}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 hec=new HtmlElementCreator,l10n=new l10nTranslation(i18n??{});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 createLoginRow(login_string,header_id="mainHeader"){exists(header_id)&&(exists("loginRow")||$("#"+header_id).html(hec.phfo(hec.cel("div","loginRow","",["loginRow","flx-spbt"]))),$("#loginRow").html(hec.phfo(hec.cel("div","loginRow-name",login_string))),$("#loginRow").append(hec.phfo(hec.cel("div","loginRow-info",""))),$("#loginRow").append(hec.phfo(hec.aelx(hec.cel("div","loginRow-logout"),hec.cel("input","logout","",[],{value:l10n.__("Logout"),type:"button",onClick:"loginLogout()"})))))}function createNavMenu(nav_menu,header_id="mainHeader"){if(isObject(nav_menu)&&getObjectCount(nav_menu)>1){exists("menuRow")||$("#"+header_id).html(hec.phfo(hec.cel("div","menuRow","",["menuRow","flx-s"])));var content=[];$.each(nav_menu,function(key,item){key!=0&&content.push(hec.phfo(hec.cel("div","","·",["pd-2"]))),item.enabled&&(window.location.href.indexOf(item.url)!=-1&&(item.selected=1),content.push(hec.phfo(hec.aelx(hec.cel("div"),hec.cel("a","",item.name,["pd-2"].concat(item.selected?"highlight":""),{href:item.url})))))}),$("#menuRow").html(content.join(""))}else $("#menuRow").hide()}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 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(hec3,l10n3){this.hec=hec3,this.l10n=l10n3}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 aiob=new ActionIndicatorOverlayBox,hec2=new HtmlElementCreator,l10n2=new l10nTranslation(i18n??{}),ab=new ActionBox(hec2,l10n2);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 l10n2.__(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 hec2.cel(tag,id,content,css,options)}function ael(base,attach,id=""){return hec2.ael(base,attach,id)}function aelx(base,...attach){return hec2.aelx(base,attach)}function aelxar(base,attach){return hec2.aelxar(base,attach)}function rel(base){return hec2.rel(base)}function rcssel(_element,css){return hec2.rcssel(_element,css)}function acssel(_element,css){return hec2.acssel(_element,css)}function scssel(_element,rcss,acss){hec2.scssel(_element,rcss,acss)}function phfo(tree){return hec2.phfo(tree)}function phfa(list){return hec2.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 createLoginRow2(login_string,header_id="mainHeader"){createLoginRow(login_string,header_id)}function createNavMenu2(nav_menu,header_id="mainHeader"){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=utilsAll.min.js.map diff --git a/www/admin/layout/javascript/utils.min.js.map b/www/admin/layout/javascript/utils.min.js.map new file mode 100644 index 00000000..33738d7e --- /dev/null +++ b/www/admin/layout/javascript/utils.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../js-dev/utils/JavaScriptHelpers.mjs", "../js-dev/utils/DomHelpers.mjs", "../js-dev/utils/HtmlElementCreator.mjs", "../js-dev/utils/HtmlHelpers.mjs", "../js-dev/utils/MathHelpers.mjs", "../js-dev/utils/StringHelpers.mjs", "../js-dev/utils/DateTimeHelpers.mjs", "../js-dev/utils/UniqIdGenerators.mjs", "../js-dev/utils/ResizingAndMove.mjs", "../js-dev/utils/FormatBytes.mjs", "../js-dev/utils/UrlParser.mjs", "../js-dev/utils/l10nTranslation.mjs", "../js-dev/utils/LoginMenu.mjs", "../js-dev/utils/ActionIndicatorOverlayBox.mjs", "../js-dev/utils/ActionBox.mjs", "../js-dev/utilsAll.mjs"], + "sourcesContent": ["/*\nDescription: JavaScript Helpers\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport {\n\terrorCatch, isFunction, executeFunctionByName, isObject, getObjectCount,\n\tkeyInObject, getKeyByValue,valueInObject, deepCopyFunction\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\n */\nfunction getObjectCount(object)\n{\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 */\nfunction keyInObject(key, object)\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 */\nfunction valueInObject(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\tname: options.name, // override name if set [name gets ignored in tree build anyway]\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\t// base.sub.push(Object.assign({}, attach));\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\t// base.sub.push(Object.assign({}, attach));\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\t// base.sub.push(Object.assign({}, attach[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\t// base.sub.push(Object.assign({}, attach[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}\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\n */\nfunction dec2hex(dec)\n{\n\treturn ('0' + 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 maximumg 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: \"{0} is cool, {1} is not\".format(\"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 characters\n * works on all browsers\n * after many runs it will create duplicates\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} bytes bytes in int\n * @return {String} string in GB/MB/KB\n */\nfunction formatBytes(bytes)\n{\n\tvar i = -1;\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} bytes bytes in int\n * @return {String} string in GB/MB/KB\n */\nfunction formatBytesLong(bytes)\n{\n\tif (isNaN(bytes)) {\n\t\treturn bytes.toString();\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 (\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 * @return {String|Number} A byte number, or original string as is\n */\nfunction stringByteFormat(bytes)\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\treturn bytes;\n}\n\n// __END__\n", "/*\nDescription: HTML Helpers\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { parseQueryString, getQueryStringParam };\nimport { keyInObject } from './JavaScriptHelpers.mjs';\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 */\nfunction parseQueryString(query = '', return_key = '')\n{\n\tif (!query) {\n\t\tquery = window.location.search.substring(1);\n\t}\n\tvar vars = query.split('&');\n\tvar query_string = {};\n\tfor (var i = 0; i < vars.length; i++) {\n\t\tvar pair = vars[i].split('=');\n\t\tvar key = decodeURIComponent(pair[0]);\n\t\tvar value = decodeURIComponent(pair[1]);\n\t\t// skip over run if there is nothing\n\t\tif (!key || value === 'undefined') {\n\t\t\tcontinue;\n\t\t}\n\t\t// If first entry with this name\n\t\tif (typeof query_string[key] === 'undefined') {\n\t\t\tquery_string[key] = decodeURIComponent(value);\n\t\t\t// If second entry with this name\n\t\t} else if (typeof query_string[key] === 'string') {\n\t\t\tvar arr = [query_string[key], decodeURIComponent(value)];\n\t\t\tquery_string[key] = arr;\n\t\t\t// If third or later entry with this name\n\t\t} else {\n\t\t\tquery_string[key].push(decodeURIComponent(value));\n\t\t}\n\t}\n\tif (return_key) {\n\t\tif (keyInObject(return_key, query_string)) {\n\t\t\treturn query_string[return_key];\n\t\t} else {\n\t\t\treturn '';\n\t\t}\n\t} else {\n\t\treturn query_string;\n\t}\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: 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: Login access and menu\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { loginLogout, createLoginRow, createNavMenu };\nimport { isObject, getObjectCount } from './JavaScriptHelpers.mjs';\nimport { exists } from './DomHelpers.mjs';\nimport { HtmlElementCreator } from './HtmlElementCreator.mjs';\nimport { l10nTranslation } from './l10nTranslation.mjs';\nlet hec = new HtmlElementCreator();\nlet l10n = new l10nTranslation(i18n ?? {});\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/**\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 */\nfunction createLoginRow(login_string, header_id = 'mainHeader')\n{\n\t// if header does not exist, we do nothing\n\tif (exists(header_id)) {\n\t\t// that row must exist already, if not it must be the first in the \"mainHeader\"\n\t\tif (!exists('loginRow')) {\n\t\t\t$('#' + header_id).html(hec.phfo(hec.cel('div', 'loginRow', '', ['loginRow', 'flx-spbt'])));\n\t\t}\n\t\t// clear out just in case for first entry\n\t\t// fill with div name & login/logout button\n\t\t$('#loginRow').html(hec.phfo(hec.cel('div', 'loginRow-name', login_string)));\n\t\t$('#loginRow').append(hec.phfo(hec.cel('div', 'loginRow-info', '')));\n\t\t$('#loginRow').append(hec.phfo(\n\t\t\thec.aelx(\n\t\t\t\t// outer div\n\t\t\t\thec.cel('div', 'loginRow-logout'),\n\t\t\t\t// inner element\n\t\t\t\thec.cel('input', 'logout', '', [], {\n\t\t\t\t\tvalue: l10n.__('Logout'),\n\t\t\t\t\ttype: 'button',\n\t\t\t\t\tonClick: 'loginLogout()'\n\t\t\t\t})\n\t\t\t)\n\t\t));\n\t}\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 */\nfunction createNavMenu(nav_menu, header_id = 'mainHeader')\n{\n\t// must be an object\n\tif (isObject(nav_menu) && getObjectCount(nav_menu) > 1) {\n\t\t// do we have more than one entry, if not, do not show (single page)\n\t\tif (!exists('menuRow')) {\n\t\t\t$('#' + header_id).html(hec.phfo(hec.cel('div', 'menuRow', '', ['menuRow', 'flx-s'])));\n\t\t}\n\t\tvar content = [];\n\t\t$.each(nav_menu, function(key, item) {\n\t\t\t// key is number\n\t\t\t// item is object with entries\n\t\t\tif (key != 0) {\n\t\t\t\tcontent.push(hec.phfo(hec.cel('div', '', '·', ['pd-2'])));\n\t\t\t}\n\t\t\t// ignore item.popup for now\n\t\t\tif (item.enabled) {\n\t\t\t\t// set selected based on window.location.href as the php set will not work\n\t\t\t\tif (window.location.href.indexOf(item.url) != -1) {\n\t\t\t\t\titem.selected = 1;\n\t\t\t\t}\n\t\t\t\t// create the entry\n\t\t\t\tcontent.push(hec.phfo(\n\t\t\t\t\thec.aelx(\n\t\t\t\t\t\thec.cel('div'),\n\t\t\t\t\t\thec.cel('a', '', item.name, ['pd-2'].concat(item.selected ? 'highlight': ''), {\n\t\t\t\t\t\t\thref: item.url\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});\n\t\t$('#menuRow').html(content.join(''));\n\t} else {\n\t\t$('#menuRow').hide();\n\t}\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\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: 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", "/*\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\tcreateLoginRow as _createLoginRow,\n\tcreateNavMenu as _createNavMenu\n} from './utils/LoginMenu.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';\n\nlet aiob = new ActionIndicatorOverlayBox();\nlet hec = new HtmlElementCreator();\n// @ts-ignore\n// eslint-disable-next-line no-undef\nlet l10n = new l10nTranslation(i18n ?? {});\nlet ab = new ActionBox(hec, l10n);\n\n/* export {\n\tescapeHtml,\n\troundPrecision,\n\tformatString,\n\tunescapeHtml,\n\tloadEl,\n\tpop,\n\texpandTA,\n\tgetWindowSize,\n\tgetScrollOffset,\n\tgetScrollOffsetOpener,\n\tsetCenter,\n\tgoToPos,\n\tgoTo,\n\t__,\n\tnumberWithCommas,\n\tconvertLBtoBR,\n\tgetTimestamp,\n\tdec2hex,\n\tgenerateId,\n\trandomIdF,\n\tgetRandomIntInclusive,\n\tisFunction,\n\texecuteFunctionByName,\n\tisObject,\n\tgetObjectCount,\n\tkeyInObject,\n\tgetKeyByValue,\n\tvalueInObject,\n\tdeepCopyFunction,\n\texists,\n\tformatBytes,\n\tformatBytesLong,\n\tstringByteFormat,\n\terrorCatch,\n\tactionIndicator,\n\tactionIndicatorShow,\n\tactionIndicatorHide,\n\toverlayBoxShow,\n\toverlayBoxHide,\n\tsetOverlayBox,\n\thideOverlayBox,\n\tClearCall,\n\tshowActionIndicator,\n\thideActionIndicator,\n\tcheckOverlayExists,\n\tshowOverlayBoxLayers,\n\thideOverlayBoxLayers,\n\tclearCallActionBox,\n\tcel,\n\tael,\n\taelx,\n\taelxar,\n\trel,\n\trcssel,\n\tacssel,\n\tscssel,\n\tphfo,\n\tphfa,\n\thtml_options,\n\thtml_options_block,\n\thtml_options_refill,\n\tparseQueryString,\n\tgetQueryStringParam,\n\tloginLogout,\n\tcreateLoginRow,\n\tcreateNavMenu,\n\tshowFillActionBox,\n\tfillActionBox,\n\tadjustActionBox,\n\thideAllActionBoxes,\n\thideActionBox,\n\tshowActionBox,\n\tcloseActionBox,\n\tshowActionBoxFloat,\n\tcloseActionBoxFloat,\n\tcreateActionBox,\n\tadjustActionBoxHeight,\n}; */\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\t_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\t_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", "createLoginRow", "createNavMenu"] +} diff --git a/www/admin/test.javascript.html b/www/admin/test.javascript.html new file mode 100644 index 00000000..e76020f7 --- /dev/null +++ b/www/admin/test.javascript.html @@ -0,0 +1,37 @@ + + + JavaScript Test + + + + + +
+

JavaScript tests

+
+
+
+ + From e71df90144e280dc589b3d4a3149cd628117eb62 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 10 Mar 2025 11:00:02 +0900 Subject: [PATCH 083/105] Fully deprecate prototype edit.js, add deprecation warnings to edit.jq.js and add new utils Note that all the utils.js are build in an external repository and just copied here --- .../tests/Convert/CoreLibsConvertByteTest.php | 2 +- www/admin/layout/javascript/edit.jq.js | 78 ++++- www/admin/layout/javascript/edit.pt.js | 12 +- www/admin/layout/javascript/utils.js | 280 +++++++++++------- www/admin/layout/javascript/utils.min.js | 6 +- www/admin/layout/javascript/utils.min.js.map | 8 +- 6 files changed, 259 insertions(+), 127 deletions(-) diff --git a/4dev/tests/Convert/CoreLibsConvertByteTest.php b/4dev/tests/Convert/CoreLibsConvertByteTest.php index 95838889..0cd1682f 100644 --- a/4dev/tests/Convert/CoreLibsConvertByteTest.php +++ b/4dev/tests/Convert/CoreLibsConvertByteTest.php @@ -40,7 +40,7 @@ final class CoreLibsConvertByteTest extends TestCase 4 => '1.00 KB', 5 => '1.02KiB', ], - 'invalud string number' => [ + 'invalid string number' => [ 0 => '1024 MB', 1 => '1024 MB', 2 => '1024 MB', diff --git a/www/admin/layout/javascript/edit.jq.js b/www/admin/layout/javascript/edit.jq.js index 4423fb96..dba5f852 100644 --- a/www/admin/layout/javascript/edit.jq.js +++ b/www/admin/layout/javascript/edit.jq.js @@ -1,5 +1,9 @@ -/* general edit javascript */ -/* jquery version */ +/* +general edit javascript +jquery version +*/ + +/** @deprecated Do not use this anymore, use utils.js or utils.min.js */ /* global i18n */ @@ -21,6 +25,7 @@ var GL_OB_BASE = 100; * @param {string} el_id Element ID to get * @returns {HTMLElement} * @throws Error + * @deprecated use utils.js */ function loadEl(el_id) { @@ -36,6 +41,7 @@ function loadEl(el_id) * @param {String} theURL the url * @param {String} winName window name * @param {Object} features popup features + * @deprecated use utils.js */ function pop(theURL, winName, features) // eslint-disable-line no-unused-vars { @@ -46,6 +52,7 @@ function pop(theURL, winName, features) // eslint-disable-line no-unused-vars /** * automatically resize a text area based on the amount of lines in it * @param {string} ta_id element id + * @deprecated use utils.js */ function expandTA(ta_id) // eslint-disable-line no-unused-vars { @@ -71,6 +78,7 @@ function expandTA(ta_id) // eslint-disable-line no-unused-vars /** * wrapper to get the real window size for the current browser window * @return {Object} object with width/height + * @deprecated use utils.js */ function getWindowSize() { @@ -86,6 +94,7 @@ function getWindowSize() /** * wrapper to get the correct scroll offset * @return {Object} object with x/y px + * @deprecated use utils.js */ function getScrollOffset() { @@ -101,6 +110,7 @@ function getScrollOffset() /** * wrapper to get the correct scroll offset for opener page (from popup) * @return {Object} object with x/y px + * @deprecated use utils.js */ function getScrollOffsetOpener() // eslint-disable-line no-unused-vars { @@ -118,6 +128,7 @@ function getScrollOffsetOpener() // eslint-disable-line no-unused-vars * @param {String} id element to center * @param {Boolean} left if true centers to the middle from the left * @param {Boolean} top if true centers to the middle from the top + * @deprecated use utils.js */ function setCenter(id, left, top) { @@ -155,6 +166,7 @@ function setCenter(id, left, top) * @param {Number} [offset=0] offset from top, default is 0 (px) * @param {Number} [duration=500] animation time, default 500ms * @param {String} [base='body,html'] base element for offset scroll + * @deprecated use utils.js */ function goToPos(element, offset = 0, duration = 500, base = 'body,html') // eslint-disable-line no-unused-vars { @@ -173,6 +185,7 @@ function goToPos(element, offset = 0, duration = 500, base = 'body,html') // esl * go to element, scroll * non jquery * @param {string} target + * @deprecated use utils.js */ function goTo(target) // eslint-disable-line no-unused-vars { @@ -186,6 +199,7 @@ function goTo(target) // eslint-disable-line no-unused-vars * that is filled from gettext in PHP * @param {String} string text to translate * @return {String} translated text (based on PHP selected language) + * @deprecated use utils.js */ function __(string) { @@ -202,6 +216,7 @@ function __(string) * First, checks if it isn't implemented yet. * @param {String} String.prototype.format string with elements to be replaced * @return {String} Formated string + * @deprecated use utils.js */ if (!String.prototype.format) { String.prototype.format = function() @@ -218,6 +233,7 @@ if (!String.prototype.format) { * @param {String} string String with {..} entries * @param {...any} args List of replacement * @returns {String} Escaped string + * @deprecated use utils.js */ function formatString(string, ...args) { @@ -235,6 +251,7 @@ function formatString(string, ...args) * @param {Number} Number.prototype.round Float type number to round * @param {Number} prec Precision to round to * @return {Float} Rounded number + * @deprecated use utils.js */ if (Number.prototype.round) { Number.prototype.round = function (prec) { @@ -248,6 +265,7 @@ if (Number.prototype.round) { * @param {Number} number Float type number to round * @param {Number} precision Precision to round to * @return {Number} Rounded number + * @deprecated use utils.js */ function roundPrecision(number, precision) { @@ -261,6 +279,7 @@ function roundPrecision(number, precision) * formats flat number 123456 to 123,456 * @param {Number} x number to be formated * @return {String} formatted with , in thousands + * @deprecated use utils.js */ function numberWithCommas(x) // eslint-disable-line no-unused-vars { @@ -273,6 +292,7 @@ function numberWithCommas(x) // eslint-disable-line no-unused-vars * converts line breaks to br * @param {String} string any string * @return {String} string with
+ * @deprecated use utils.js */ function convertLBtoBR(string) // eslint-disable-line no-unused-vars { @@ -283,6 +303,7 @@ function convertLBtoBR(string) // eslint-disable-line no-unused-vars * escape HTML string * @param {String} String.prototype.escapeHTML HTML data string to be escaped * @return {String} escaped string + * @deprecated use utils.js */ if (!String.prototype.escapeHTML) { String.prototype.escapeHTML = function() { @@ -295,6 +316,7 @@ if (!String.prototype.escapeHTML) { * unescape a HTML encoded string * @param {String} String.prototype.unescapeHTML data with escaped entries * @return {String} HTML formated string + * @deprecated use utils.js */ if (!String.prototype.unescapeHTML) { String.prototype.unescapeHTML = function() { @@ -307,6 +329,7 @@ if (!String.prototype.unescapeHTML) { * Escapes HTML in string * @param {String} string Text to escape HTML in * @returns {String} + * @deprecated use utils.js */ function escapeHtml(string) { @@ -328,6 +351,7 @@ function escapeHtml(string) * Unescape a HTML encoded string * @param {String} string Text to unescape HTML in * @returns {String} + * @deprecated use utils.js */ function unescapeHtml(string) { @@ -348,6 +372,7 @@ function unescapeHtml(string) /** * returns current timestamp (unix timestamp) * @return {Number} timestamp (in milliseconds) + * @deprecated use utils.js */ function getTimestamp() // eslint-disable-line no-unused-vars { @@ -360,6 +385,7 @@ function getTimestamp() // eslint-disable-line no-unused-vars * i.e. 0-255 -> '00'-'ff' * @param {Number} dec decimal string * @return {String} hex encdoded number + * @deprecated use utils.js */ function dec2hex(dec) { @@ -371,6 +397,7 @@ function dec2hex(dec) * only works on mondern browsers * @param {Number} len length of unique id string * @return {String} random string in length of len + * @deprecated use utils.js */ function generateId(len) // eslint-disable-line no-unused-vars { @@ -384,6 +411,7 @@ function generateId(len) // eslint-disable-line no-unused-vars * works on all browsers * after many runs it will create duplicates * @return {String} not true random string + * @deprecated use utils.js */ function randomIdF() // eslint-disable-line no-unused-vars { @@ -397,6 +425,7 @@ function randomIdF() // eslint-disable-line no-unused-vars * @param {Number} min minimum int number inclusive * @param {Number} max maximumg int number inclusive * @return {Number} Random number + * @deprecated use utils.js */ function getRandomIntInclusive(min, max) // eslint-disable-line no-unused-vars { @@ -410,6 +439,7 @@ function getRandomIntInclusive(min, max) // eslint-disable-line no-unused-vars * check if name is a function * @param {string} name Name of function to check if exists * @return {Boolean} true/false + * @deprecated use utils.js */ function isFunction(name) // eslint-disable-line no-unused-vars { @@ -429,6 +459,7 @@ function isFunction(name) // eslint-disable-line no-unused-vars * @param {mixed} context context (window or first namespace) * hidden next are all the arguments * @return {mixed} Return values from functon + * @deprecated use utils.js */ function executeFunctionByName(functionName, context /*, args */) // eslint-disable-line no-unused-vars { @@ -445,6 +476,7 @@ function executeFunctionByName(functionName, context /*, args */) // eslint-disa * checks if a variable is an object * @param {Mixed} val possible object * @return {Boolean} true/false if it is an object or not + * @deprecated use utils.js */ function isObject(val) { @@ -458,6 +490,7 @@ function isObject(val) * get the length of an object (entries) * @param {Object} object object to check * @return {Number} number of entry + * @deprecated use utils.js */ function getObjectCount(object) { @@ -469,6 +502,7 @@ function getObjectCount(object) * @param {String} key key name * @param {Object} object object to search key in * @return {Boolean} true/false if key exists in object + * @deprecated use utils.js */ function keyInObject(key, object) { @@ -480,6 +514,7 @@ function keyInObject(key, object) * @param {Object} object object to search value in * @param {Mixed} value any value (String, Number, etc) * @return {String} the key found for the first matching value + * @deprecated use utils.js */ function getKeyByValue(object, value) // eslint-disable-line no-unused-vars { @@ -494,6 +529,7 @@ function getKeyByValue(object, value) // eslint-disable-line no-unused-vars * @param {Object} object object to search value in * @param {Mixed} value any value (String, Number, etc) * @return {Boolean} true on value found, false on not found + * @deprecated use utils.js */ function valueInObject(object, value) // eslint-disable-line no-unused-vars { @@ -509,6 +545,7 @@ function valueInObject(object, value) // eslint-disable-line no-unused-vars * or if JSON.parse(JSON.stringify(obj)) is failing * @param {Object} inObject Object to copy * @return {Object} Copied Object + * @deprecated use utils.js */ function deepCopyFunction(inObject) { @@ -532,6 +569,7 @@ function deepCopyFunction(inObject) * checks if a DOM element actually exists * @param {String} id Element id to check for * @return {Boolean} true if element exists, false on failure + * @deprecated use utils.js */ function exists(id) { @@ -543,6 +581,7 @@ function exists(id) * currently precision is fixed, if dynamic needs check for max/min precision * @param {Number} bytes bytes in int * @return {String} string in GB/MB/KB + * @deprecated use utils.js */ function formatBytes(bytes) // eslint-disable-line no-unused-vars { @@ -559,6 +598,7 @@ function formatBytes(bytes) // eslint-disable-line no-unused-vars * like formatBytes, but returns bytes for <1KB and not 0.n KB * @param {Number} bytes bytes in int * @return {String} string in GB/MB/KB + * @deprecated use utils.js */ function formatBytesLong(bytes) // eslint-disable-line no-unused-vars { @@ -571,6 +611,7 @@ function formatBytesLong(bytes) // eslint-disable-line no-unused-vars * Convert a string with B/K/M/etc into a byte number * @param {String|Number} bytes Any string with B/K/M/etc * @return {String|Number} A byte number, or original string as is + * @deprecated use utils.js */ function stringByteFormat(bytes) // eslint-disable-line no-unused-vars { @@ -601,6 +642,7 @@ function stringByteFormat(bytes) // eslint-disable-line no-unused-vars /** * prints out error messages based on data available from the browser * @param {Object} err error from try/catch block + * @deprecated use utils.js */ function errorCatch(err) { @@ -644,6 +686,7 @@ function errorCatch(err) * @param {String} loc location name for action indicator * default empty. for console.log * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block + * @deprecated use utils.js */ function actionIndicator(loc, overlay = true) // eslint-disable-line no-unused-vars { @@ -660,6 +703,7 @@ function actionIndicator(loc, overlay = true) // eslint-disable-line no-unused-v * @param {String} loc location name for action indicator * default empty. for console.log * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block + * @deprecated use utils.js */ function actionIndicatorShow(loc, overlay = true) { @@ -682,6 +726,7 @@ function actionIndicatorShow(loc, overlay = true) * @param {String} loc location name for action indicator * default empty. for console.log * @param {Boolean} [overlay=true] override the auto hide/show over the overlay div block + * @deprecated use utils.js */ function actionIndicatorHide(loc, overlay = true) { @@ -694,6 +739,7 @@ function actionIndicatorHide(loc, overlay = true) /** * shows the overlay box or if already visible, bumps the zIndex to 100 + * @deprecated use utils.js */ function overlayBoxShow() { @@ -708,6 +754,7 @@ function overlayBoxShow() /** * hides the overlay box or if zIndex is 100 bumps it down to previous level + * @deprecated use utils.js */ function overlayBoxHide() { @@ -721,6 +768,7 @@ function overlayBoxHide() /** * position the overlay block box and shows it + * @deprecated use utils.js */ function setOverlayBox() // eslint-disable-line no-unused-vars { @@ -731,6 +779,7 @@ function setOverlayBox() // eslint-disable-line no-unused-vars /** * opposite of set, always hides overlay box + * @deprecated use utils.js */ function hideOverlayBox() // eslint-disable-line no-unused-vars { @@ -741,6 +790,7 @@ function hideOverlayBox() // eslint-disable-line no-unused-vars /** * the abort call, clears the action box and hides it and the overlay box + * @deprecated use utils.js */ function ClearCall() // eslint-disable-line no-unused-vars { @@ -762,6 +812,7 @@ function ClearCall() // eslint-disable-line no-unused-vars * zIndex of 1000 * - indicator is page centered * @param {String} loc ID string, only used for console log + * @deprecated use utils.js */ function showActionIndicator(loc) // eslint-disable-line no-unused-vars { @@ -800,6 +851,7 @@ function showActionIndicator(loc) // eslint-disable-line no-unused-vars * the overlayBox is not hidden but the zIndex * is set to this value * @param {String} loc ID string, only used for console log + * @deprecated use utils.js */ function hideActionIndicator(loc) // eslint-disable-line no-unused-vars { @@ -823,6 +875,7 @@ function hideActionIndicator(loc) // eslint-disable-line no-unused-vars /** * checks if overlayBox exists, if not it is * added as hidden item at the body end + * @deprecated use utils.js */ function checkOverlayExists() { @@ -840,6 +893,7 @@ function checkOverlayExists() * if not visible show and set zIndex to 10 (GL_OB_BASE) * if visible, add +1 to the GL_OB_S variable and * up zIndex by this value + * @deprecated use utils.js */ function showOverlayBoxLayers(el_id) // eslint-disable-line no-unused-vars { @@ -872,6 +926,7 @@ function showOverlayBoxLayers(el_id) // eslint-disable-line no-unused-vars * and set zIndex and GL_OB_S to 0 * else just set zIndex to the new GL_OB_S value * @param {String} el_id Target to hide layer + * @deprecated use utils.js */ function hideOverlayBoxLayers(el_id='') { @@ -897,6 +952,7 @@ function hideOverlayBoxLayers(el_id='') /** * only for single action box + * @deprecated use utils.js */ function clearCallActionBox() // eslint-disable-line no-unused-vars { @@ -914,6 +970,7 @@ function clearCallActionBox() // eslint-disable-line no-unused-vars * @param {Array} [css=[]] array for css tags * @param {Object} [options={}] anything else (value, placeholder, OnClick, style) * @return {Object} created element as an object + * @deprecated use utils.js */ function cel(tag, id = '', content = '', css = [], options = {}) { @@ -934,6 +991,7 @@ function cel(tag, id = '', content = '', css = [], options = {}) * @param {Object} attach the object to be attached * @param {String} [id=''] optional id, if given search in base for this id and attach there * @return {Object} "none", technically there is no return needed as it is global attach + * @deprecated use utils.js */ function ael(base, attach, id = '') { @@ -964,6 +1022,7 @@ function ael(base, attach, id = '') * @param {Object} base object to where we attach the elements * @param {...Object} attach attach 1..n: attach directly to the base element those attachments * @return {Object} "none", technically there is no return needed, global attach + * @deprecated use utils.js */ function aelx(base, ...attach) { @@ -980,6 +1039,7 @@ function aelx(base, ...attach) * @param {Object} base object to where we attach the elements * @param {Array} attach array of objects to attach * @return {Object} "none", technically there is no return needed, global attach + * @deprecated use utils.js */ function aelxar(base, attach) // eslint-disable-line no-unused-vars { @@ -994,6 +1054,7 @@ function aelxar(base, attach) // eslint-disable-line no-unused-vars * resets the sub elements of the base element given * @param {Object} base cel created element * @return {Object} returns reset base element + * @deprecated use utils.js */ function rel(base) // eslint-disable-line no-unused-vars { @@ -1006,6 +1067,7 @@ function rel(base) // eslint-disable-line no-unused-vars * @param {Object} _element element to work one * @param {String} css style sheet to remove (name) * @return {Object} returns full element + * @deprecated use utils.js */ function rcssel(_element, css) { @@ -1021,6 +1083,7 @@ function rcssel(_element, css) * @param {Object} _element element to work on * @param {String} css style sheet to add (name) * @return {Object} returns full element + * @deprecated use utils.js */ function acssel(_element, css) { @@ -1038,6 +1101,7 @@ function acssel(_element, css) * @param {String} rcss style to remove (name) * @param {String} acss style to add (name) * @return {Object} returns full element + * @deprecated use utils.js */ function scssel(_element, rcss, acss) // eslint-disable-line no-unused-vars { @@ -1050,6 +1114,7 @@ function scssel(_element, rcss, acss) // eslint-disable-line no-unused-vars * that can be inserted into the page * @param {Object} tree object tree with dom element declarations * @return {String} HTML string that can be used as innerHTML + * @deprecated use utils.js */ function phfo(tree) { @@ -1156,6 +1221,7 @@ function phfo(tree) * Is like tree.sub call * @param {Array} list Array of cel created objects * @return {String} HTML String + * @deprecated use utils.js */ function phfa(list) // eslint-disable-line no-unused-vars { @@ -1182,6 +1248,7 @@ function phfa(list) // eslint-disable-line no-unused-vars * @param {String} [sort=''] if empty as is, else allowed 'keys', * 'values' all others are ignored * @return {String} html with build options block + * @deprecated use utils.js */ function html_options(name, data, selected = '', options_only = false, return_string = false, sort = '') // eslint-disable-line no-unused-vars { @@ -1209,6 +1276,7 @@ function html_options(name, data, selected = '', options_only = false, return_st * 'values' all others are ignored * @param {String} [onchange=''] onchange trigger call, default unset * @return {String} html with build options block + * @deprecated use utils.js */ function html_options_block( name, data, selected = '', multiple = 0, options_only = false, return_string = false, sort = '', onchange = '' @@ -1291,6 +1359,7 @@ function html_options_block( * @param {String} name name/id * @param {Object} data array of options * @param {String} [sort=''] if empty as is, else allowed 'keys', 'values' + * @deprecated use utils.js * all others are ignored */ function html_options_refill(name, data, sort = '') // eslint-disable-line no-unused-vars @@ -1339,6 +1408,7 @@ function html_options_refill(name, data, sort = '') // eslint-disable-line no-un * @param {String} [return_key=''] if set only returns this key entry * or empty for none * @return {Object|String} parameter entry list + * @deprecated use utils.js */ function parseQueryString(query = '', return_key = '') // eslint-disable-line no-unused-vars { @@ -1393,6 +1463,7 @@ function parseQueryString(query = '', return_key = '') // eslint-disable-line no * @return {Object|Array|String} if search is empty, object, if search is set * and only one entry, then string, else array * unless single is true + * @deprecated use utils.js */ function getQueryStringParam(search = '', query = '', single = false) // eslint-disable-line no-unused-vars { @@ -1430,6 +1501,7 @@ function getQueryStringParam(search = '', query = '', single = false) // eslint- // *** MASTER logout call /** * submits basic data for form logout + * @deprecated use utils.js */ function loginLogout() // eslint-disable-line no-unused-vars { @@ -1450,6 +1522,7 @@ function loginLogout() // eslint-disable-line no-unused-vars * @param {String} [header_id='mainHeader'] the target for the main element block * if not set mainHeader is assumed * this is the target div for the "loginRow" + * @deprecated use utils.js */ function createLoginRow(login_string, header_id = 'mainHeader') // eslint-disable-line no-unused-vars { @@ -1485,6 +1558,7 @@ function createLoginRow(login_string, header_id = 'mainHeader') // eslint-disabl * @param {String} [header_id='mainHeader'] the target for the main element block * if not set mainHeader is assumed * this is the target div for the "menuRow" + * @deprecated use utils.js */ function createNavMenu(nav_menu, header_id = 'mainHeader') // eslint-disable-line no-unused-vars { diff --git a/www/admin/layout/javascript/edit.pt.js b/www/admin/layout/javascript/edit.pt.js index c338a1d8..35dfed5c 100644 --- a/www/admin/layout/javascript/edit.pt.js +++ b/www/admin/layout/javascript/edit.pt.js @@ -1,5 +1,11 @@ -/* general edit javascript */ -/* prototype version */ +/* +general edit javascript +prototype version +*/ + +/** @deprecated Do not use this anymore, use utils.js */ + +throw new Error("Prototype Support is deprected, please switch to jquery and utils.js/utils.min.js"); /* jshint esversion: 6 */ @@ -25,7 +31,7 @@ function pop(theURL, winName, features) { /** * automatically resize a text area based on the amount of lines in it - * @param {[string} ta_id element id + * @param {string} ta_id element id */ function expandTA(ta_id) { var ta; diff --git a/www/admin/layout/javascript/utils.js b/www/admin/layout/javascript/utils.js index 749e5d77..b581ff32 100644 --- a/www/admin/layout/javascript/utils.js +++ b/www/admin/layout/javascript/utils.js @@ -1,4 +1,4 @@ -// www/admin/layout/js-dev/utils/JavaScriptHelpers.mjs +// src/utils/JavaScriptHelpers.mjs function errorCatch(err) { if (err.stack) { if (err.lineNumber) { @@ -41,15 +41,24 @@ function isObject(val) { return typeof val === "function" || typeof val === "object"; } function getObjectCount(object) { + if (!isObject(object)) { + return -1; + } return Object.keys(object).length; } function keyInObject(key, object) { + return objectKeyExists(object, key); +} +function objectKeyExists(object, key) { return Object.prototype.hasOwnProperty.call(object, key) ? true : false; } function getKeyByValue(object, value) { return Object.keys(object).find((key) => object[key] === value) ?? ""; } function valueInObject(object, value) { + return objectValueExists(object, value); +} +function objectValueExists(object, value) { return Object.keys(object).find((key) => object[key] === value) ? true : false; } function deepCopyFunction(inObject) { @@ -65,7 +74,7 @@ function deepCopyFunction(inObject) { return outObject; } -// www/admin/layout/js-dev/utils/DomHelpers.mjs +// src/utils/DomHelpers.mjs function loadEl(el_id) { let el = document.getElementById(el_id); if (el === null) { @@ -103,7 +112,7 @@ function exists(id) { return $("#" + id).length > 0 ? true : false; } -// www/admin/layout/js-dev/utils/HtmlElementCreator.mjs +// src/utils/HtmlElementCreator.mjs var HtmlElementCreator = class { /** * reates object for DOM element creation flow @@ -324,7 +333,7 @@ var HtmlElementCreator = class { } }; -// www/admin/layout/js-dev/utils/HtmlHelpers.mjs +// src/utils/HtmlHelpers.mjs var dom = new HtmlElementCreator(); function escapeHtml(string) { return string.replace(/[&<>"'/]/g, function(s) { @@ -453,9 +462,9 @@ function html_options_refill(name, data, sort = "") { } } -// www/admin/layout/js-dev/utils/MathHelpers.mjs +// src/utils/MathHelpers.mjs function dec2hex(dec) { - return ("0" + dec.toString(16)).substring(-2); + return ("0x" + dec.toString(16)).substring(-2); } function getRandomIntInclusive(min, max) { min = Math.ceil(min); @@ -463,13 +472,13 @@ function getRandomIntInclusive(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } function roundPrecision(number, precision) { - if (!isNaN(number) || !isNaN(precision)) { + if (isNaN(number) || isNaN(precision)) { return number; } return Math.round(number * Math.pow(10, precision)) / Math.pow(10, precision); } -// www/admin/layout/js-dev/utils/StringHelpers.mjs +// src/utils/StringHelpers.mjs function formatString(string, ...args) { return string.replace(/{(\d+)}/g, function(match, number) { return typeof args[number] != "undefined" ? args[number] : match; @@ -484,13 +493,13 @@ function convertLBtoBR(string) { return string.replace(/(?:\r\n|\r|\n)/g, "
"); } -// www/admin/layout/js-dev/utils/DateTimeHelpers.mjs +// src/utils/DateTimeHelpers.mjs function getTimestamp() { var date = /* @__PURE__ */ new Date(); return date.getTime(); } -// www/admin/layout/js-dev/utils/UniqIdGenerators.mjs +// src/utils/UniqIdGenerators.mjs function generateId(len) { var arr = new Uint8Array((len || 40) / 2); (window.crypto || // @ts-ignore @@ -501,7 +510,7 @@ function randomIdF() { return Math.random().toString(36).substring(2); } -// www/admin/layout/js-dev/utils/ResizingAndMove.mjs +// src/utils/ResizingAndMove.mjs function getWindowSize() { var width, height; width = window.innerWidth || (window.document.documentElement.clientWidth || window.document.body.clientWidth); @@ -570,9 +579,12 @@ function goTo(target) { }); } -// www/admin/layout/js-dev/utils/FormatBytes.mjs +// src/utils/FormatBytes.mjs function formatBytes(bytes) { var i = -1; + if (typeof bytes === "bigint") { + bytes = Number(bytes); + } do { bytes = bytes / 1024; i++; @@ -580,14 +592,22 @@ function formatBytes(bytes) { 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); + } if (isNaN(bytes)) { return bytes.toString(); } + let negative = false; + if (bytes < 0) { + negative = true; + bytes *= -1; + } var i = Math.floor(Math.log(bytes) / Math.log(1024)); var sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; - return ((bytes / Math.pow(1024, i)).toFixed(2) + " " + sizes[i]).toString(); + return (negative ? "-" : "") + ((bytes / Math.pow(1024, i)).toFixed(2) + " " + sizes[i]).toString(); } -function stringByteFormat(bytes) { +function stringByteFormat(bytes, raw = false) { if (!(typeof bytes === "string" || bytes instanceof String)) { return bytes.toString(); } @@ -601,10 +621,13 @@ function stringByteFormat(bytes) { bytes = m1 * Math.pow(1024, valid_units.indexOf(m2)); } } - return bytes; + if (raw) { + return bytes; + } + return Math.round(bytes); } -// www/admin/layout/js-dev/utils/UrlParser.mjs +// src/utils/UrlParser.mjs function parseQueryString(query = "", return_key = "") { if (!query) { query = window.location.search.substring(1); @@ -662,30 +685,7 @@ function getQueryStringParam(search = "", query = "", single = false) { return param; } -// www/admin/layout/js-dev/utils/l10nTranslation.mjs -var l10nTranslation = class { - #i18n = {}; - constructor(i18n2) { - this.#i18n = i18n2; - } - /** - * uses the i18n object created in the translation template - * that is filled from gettext in PHP - * @param {String} string text to translate - * @return {String} translated text (based on PHP selected language) - */ - __(string) { - if (typeof this.#i18n !== "undefined" && isObject(this.#i18n) && this.#i18n[string]) { - return this.#i18n[string]; - } else { - return string; - } - } -}; - -// www/admin/layout/js-dev/utils/LoginMenu.mjs -var hec = new HtmlElementCreator(); -var l10n = new l10nTranslation(i18n ?? {}); +// src/utils/LoginLogout.mjs function loginLogout() { const form = document.createElement("form"); form.method = "post"; @@ -697,58 +697,8 @@ function loginLogout() { document.body.appendChild(form); form.submit(); } -function createLoginRow(login_string, header_id = "mainHeader") { - if (exists(header_id)) { - if (!exists("loginRow")) { - $("#" + header_id).html(hec.phfo(hec.cel("div", "loginRow", "", ["loginRow", "flx-spbt"]))); - } - $("#loginRow").html(hec.phfo(hec.cel("div", "loginRow-name", login_string))); - $("#loginRow").append(hec.phfo(hec.cel("div", "loginRow-info", ""))); - $("#loginRow").append(hec.phfo( - hec.aelx( - // outer div - hec.cel("div", "loginRow-logout"), - // inner element - hec.cel("input", "logout", "", [], { - value: l10n.__("Logout"), - type: "button", - onClick: "loginLogout()" - }) - ) - )); - } -} -function createNavMenu(nav_menu, header_id = "mainHeader") { - if (isObject(nav_menu) && getObjectCount(nav_menu) > 1) { - if (!exists("menuRow")) { - $("#" + header_id).html(hec.phfo(hec.cel("div", "menuRow", "", ["menuRow", "flx-s"]))); - } - var content = []; - $.each(nav_menu, function(key, item) { - if (key != 0) { - content.push(hec.phfo(hec.cel("div", "", "·", ["pd-2"]))); - } - if (item.enabled) { - if (window.location.href.indexOf(item.url) != -1) { - item.selected = 1; - } - content.push(hec.phfo( - hec.aelx( - hec.cel("div"), - hec.cel("a", "", item.name, ["pd-2"].concat(item.selected ? "highlight" : ""), { - href: item.url - }) - ) - )); - } - }); - $("#menuRow").html(content.join("")); - } else { - $("#menuRow").hide(); - } -} -// www/admin/layout/js-dev/utils/ActionIndicatorOverlayBox.mjs +// src/utils/ActionIndicatorOverlayBox.mjs function actionIndicator(loc, overlay = true) { if ($("#indicator").is(":visible")) { this.actionIndicatorHide(loc, overlay); @@ -919,7 +869,28 @@ var ActionIndicatorOverlayBox = class { } }; -// www/admin/layout/js-dev/utils/ActionBox.mjs +// src/utils/l10nTranslation.mjs +var l10nTranslation = class { + #i18n = {}; + constructor(i18n2) { + this.#i18n = i18n2; + } + /** + * uses the i18n object created in the translation template + * that is filled from gettext in PHP + * @param {String} string text to translate + * @return {String} translated text (based on PHP selected language) + */ + __(string) { + if (typeof this.#i18n !== "undefined" && isObject(this.#i18n) && this.#i18n[string]) { + return this.#i18n[string]; + } else { + return string; + } + } +}; + +// src/utils/ActionBox.mjs var ActionBox = class { // open overlay boxes counter for z-index zIndex = { @@ -941,9 +912,9 @@ var ActionBox = class { * @param {Object} hec HtmlElementCreator * @param {Object} l10n l10nTranslation */ - constructor(hec3, l10n3) { - this.hec = hec3; - this.l10n = l10n3; + constructor(hec2, l10n2) { + this.hec = hec2; + this.l10n = l10n2; } /** * Show an action box @@ -1269,11 +1240,92 @@ var ActionBox = class { } }; -// www/admin/layout/js-dev/utilsAll.mjs +// src/utils/LoginNavMenu.mjs +var LoginNavMenu = class { + hec; + l10n; + /** + * action box creator + * @param {Object} hec HtmlElementCreator + * @param {Object} l10n l10nTranslation + */ + constructor(hec2, l10n2) { + this.hec = hec2; + this.l10n = l10n2; + } + /** + * create login string and logout button elements + * @param {String} login_string the login string to show on the left + * @param {String} [header_id='mainHeader'] the target for the main element block + * if not set mainHeader is assumed + * this is the target div for the "loginRow" + */ + createLoginRow(login_string, header_id = "mainHeader") { + if (exists(header_id)) { + if (!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( + // outer div + this.hec.cel("div", "loginRow-logout"), + // inner element + this.hec.cel("input", "logout", "", [], { + value: this.l10n.__("Logout"), + type: "button", + onClick: "loginLogout()" + }) + ) + )); + } + } + /** + * create the top nav menu that switches physical between pages + * (edit access data based) + * @param {Object} nav_menu the built nav menu with highlight info + * @param {String} [header_id='mainHeader'] the target for the main element block + * if not set mainHeader is assumed + * this is the target div for the "menuRow" + */ + createNavMenu(nav_menu, header_id = "mainHeader") { + if (isObject(nav_menu) && getObjectCount(nav_menu) > 1) { + if (!exists("menuRow")) { + $("#" + header_id).html(this.hec.phfo(this.hec.cel("div", "menuRow", "", ["menuRow", "flx-s"]))); + } + var content = []; + $.each(nav_menu, function(key, item) { + if (key != 0) { + content.push(this.hec.phfo(this.hec.cel("div", "", "·", ["pd-2"]))); + } + if (item.enabled) { + if (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(); + } + } +}; + +// src/utils.mjs var aiob = new ActionIndicatorOverlayBox(); -var hec2 = new HtmlElementCreator(); -var l10n2 = new l10nTranslation(i18n ?? {}); -var ab = new ActionBox(hec2, l10n2); +var hec = new HtmlElementCreator(); +var l10n = new l10nTranslation(typeof i18n === "undefined" ? {} : i18n); +var ab = new ActionBox(hec, l10n); +var lnm = new LoginNavMenu(hec, l10n); if (!String.prototype.format) { String.prototype.format = function() { console.error("[DEPRECATED] use StringHelpers.formatString"); @@ -1338,7 +1390,7 @@ function goTo2(target) { goTo(target); } function __(string) { - return l10n2.__(string); + return l10n.__(string); } function numberWithCommas2(x) { return numberWithCommas(x); @@ -1443,34 +1495,34 @@ function clearCallActionBox() { aiob.clearCallActionBox(); } function cel(tag, id = "", content = "", css = [], options = {}) { - return hec2.cel(tag, id, content, css, options); + return hec.cel(tag, id, content, css, options); } function ael(base, attach, id = "") { - return hec2.ael(base, attach, id); + return hec.ael(base, attach, id); } function aelx(base, ...attach) { - return hec2.aelx(base, attach); + return hec.aelx(base, attach); } function aelxar(base, attach) { - return hec2.aelxar(base, attach); + return hec.aelxar(base, attach); } function rel(base) { - return hec2.rel(base); + return hec.rel(base); } function rcssel(_element, css) { - return hec2.rcssel(_element, css); + return hec.rcssel(_element, css); } function acssel(_element, css) { - return hec2.acssel(_element, css); + return hec.acssel(_element, css); } function scssel(_element, rcss, acss) { - hec2.scssel(_element, rcss, acss); + hec.scssel(_element, rcss, acss); } function phfo(tree) { - return hec2.phfo(tree); + return hec.phfo(tree); } function phfa(list) { - return hec2.phfa(list); + return hec.phfa(list); } function html_options2(name, data, selected = "", options_only = false, return_string = false, sort = "") { return html_options(name, data, selected, options_only, return_string, sort); @@ -1499,11 +1551,11 @@ function getQueryStringParam2(search = "", query = "", single = false) { function loginLogout2() { loginLogout(); } -function createLoginRow2(login_string, header_id = "mainHeader") { - createLoginRow(login_string, header_id); +function createLoginRow(login_string, header_id = "mainHeader") { + lnm.createLoginRow(login_string, header_id); } -function createNavMenu2(nav_menu, header_id = "mainHeader") { - createNavMenu(nav_menu, 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); diff --git a/www/admin/layout/javascript/utils.min.js b/www/admin/layout/javascript/utils.min.js index 3b9a3af5..3185f018 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!!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){this.rcssel(_element,rcss),this.acssel(_element,acss)}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("0"+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;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(isNaN(bytes))return bytes.toString();var i=Math.floor(Math.log(bytes)/Math.log(1024)),sizes=["B","KB","MB","GB","TB","PB","EB","ZB","YB"];return((bytes/Math.pow(1024,i)).toFixed(2)+" "+sizes[i]).toString()}function stringByteFormat(bytes){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 bytes}function parseQueryString(query="",return_key=""){query||(query=window.location.search.substring(1));for(var vars=query.split("&"),query_string={},i=0;i"u")query_string[key]=decodeURIComponent(value);else if(typeof query_string[key]=="string"){var arr=[query_string[key],decodeURIComponent(value)];query_string[key]=arr}else query_string[key].push(decodeURIComponent(value))}return return_key?keyInObject(return_key,query_string)?query_string[return_key]:"":query_string}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}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 hec=new HtmlElementCreator,l10n=new l10nTranslation(i18n??{});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 createLoginRow(login_string,header_id="mainHeader"){exists(header_id)&&(exists("loginRow")||$("#"+header_id).html(hec.phfo(hec.cel("div","loginRow","",["loginRow","flx-spbt"]))),$("#loginRow").html(hec.phfo(hec.cel("div","loginRow-name",login_string))),$("#loginRow").append(hec.phfo(hec.cel("div","loginRow-info",""))),$("#loginRow").append(hec.phfo(hec.aelx(hec.cel("div","loginRow-logout"),hec.cel("input","logout","",[],{value:l10n.__("Logout"),type:"button",onClick:"loginLogout()"})))))}function createNavMenu(nav_menu,header_id="mainHeader"){if(isObject(nav_menu)&&getObjectCount(nav_menu)>1){exists("menuRow")||$("#"+header_id).html(hec.phfo(hec.cel("div","menuRow","",["menuRow","flx-s"])));var content=[];$.each(nav_menu,function(key,item){key!=0&&content.push(hec.phfo(hec.cel("div","","·",["pd-2"]))),item.enabled&&(window.location.href.indexOf(item.url)!=-1&&(item.selected=1),content.push(hec.phfo(hec.aelx(hec.cel("div"),hec.cel("a","",item.name,["pd-2"].concat(item.selected?"highlight":""),{href:item.url})))))}),$("#menuRow").html(content.join(""))}else $("#menuRow").hide()}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 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(hec3,l10n3){this.hec=hec3,this.l10n=l10n3}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 aiob=new ActionIndicatorOverlayBox,hec2=new HtmlElementCreator,l10n2=new l10nTranslation(i18n??{}),ab=new ActionBox(hec2,l10n2);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 l10n2.__(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 hec2.cel(tag,id,content,css,options)}function ael(base,attach,id=""){return hec2.ael(base,attach,id)}function aelx(base,...attach){return hec2.aelx(base,attach)}function aelxar(base,attach){return hec2.aelxar(base,attach)}function rel(base){return hec2.rel(base)}function rcssel(_element,css){return hec2.rcssel(_element,css)}function acssel(_element,css){return hec2.acssel(_element,css)}function scssel(_element,rcss,acss){hec2.scssel(_element,rcss,acss)}function phfo(tree){return hec2.phfo(tree)}function phfa(list){return hec2.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 createLoginRow2(login_string,header_id="mainHeader"){createLoginRow(login_string,header_id)}function createNavMenu2(nav_menu,header_id="mainHeader"){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=utilsAll.min.js.map +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){this.rcssel(_element,rcss),this.acssel(_element,acss)}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;typeof bytes=="bigint"&&(bytes=Number(bytes));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=""){query||(query=window.location.search.substring(1));for(var vars=query.split("&"),query_string={},i=0;i"u")query_string[key]=decodeURIComponent(value);else if(typeof query_string[key]=="string"){var arr=[query_string[key],decodeURIComponent(value)];query_string[key]=arr}else query_string[key].push(decodeURIComponent(value))}return return_key?keyInObject(return_key,query_string)?query_string[return_key]:"":query_string}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)} +//# 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 33738d7e..cacaf832 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": ["../js-dev/utils/JavaScriptHelpers.mjs", "../js-dev/utils/DomHelpers.mjs", "../js-dev/utils/HtmlElementCreator.mjs", "../js-dev/utils/HtmlHelpers.mjs", "../js-dev/utils/MathHelpers.mjs", "../js-dev/utils/StringHelpers.mjs", "../js-dev/utils/DateTimeHelpers.mjs", "../js-dev/utils/UniqIdGenerators.mjs", "../js-dev/utils/ResizingAndMove.mjs", "../js-dev/utils/FormatBytes.mjs", "../js-dev/utils/UrlParser.mjs", "../js-dev/utils/l10nTranslation.mjs", "../js-dev/utils/LoginMenu.mjs", "../js-dev/utils/ActionIndicatorOverlayBox.mjs", "../js-dev/utils/ActionBox.mjs", "../js-dev/utilsAll.mjs"], - "sourcesContent": ["/*\nDescription: JavaScript Helpers\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport {\n\terrorCatch, isFunction, executeFunctionByName, isObject, getObjectCount,\n\tkeyInObject, getKeyByValue,valueInObject, deepCopyFunction\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\n */\nfunction getObjectCount(object)\n{\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 */\nfunction keyInObject(key, object)\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 */\nfunction valueInObject(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\tname: options.name, // override name if set [name gets ignored in tree build anyway]\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\t// base.sub.push(Object.assign({}, attach));\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\t// base.sub.push(Object.assign({}, attach));\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\t// base.sub.push(Object.assign({}, attach[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\t// base.sub.push(Object.assign({}, attach[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}\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\n */\nfunction dec2hex(dec)\n{\n\treturn ('0' + 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 maximumg 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: \"{0} is cool, {1} is not\".format(\"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 characters\n * works on all browsers\n * after many runs it will create duplicates\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} bytes bytes in int\n * @return {String} string in GB/MB/KB\n */\nfunction formatBytes(bytes)\n{\n\tvar i = -1;\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} bytes bytes in int\n * @return {String} string in GB/MB/KB\n */\nfunction formatBytesLong(bytes)\n{\n\tif (isNaN(bytes)) {\n\t\treturn bytes.toString();\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 (\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 * @return {String|Number} A byte number, or original string as is\n */\nfunction stringByteFormat(bytes)\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\treturn bytes;\n}\n\n// __END__\n", "/*\nDescription: HTML Helpers\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { parseQueryString, getQueryStringParam };\nimport { keyInObject } from './JavaScriptHelpers.mjs';\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 */\nfunction parseQueryString(query = '', return_key = '')\n{\n\tif (!query) {\n\t\tquery = window.location.search.substring(1);\n\t}\n\tvar vars = query.split('&');\n\tvar query_string = {};\n\tfor (var i = 0; i < vars.length; i++) {\n\t\tvar pair = vars[i].split('=');\n\t\tvar key = decodeURIComponent(pair[0]);\n\t\tvar value = decodeURIComponent(pair[1]);\n\t\t// skip over run if there is nothing\n\t\tif (!key || value === 'undefined') {\n\t\t\tcontinue;\n\t\t}\n\t\t// If first entry with this name\n\t\tif (typeof query_string[key] === 'undefined') {\n\t\t\tquery_string[key] = decodeURIComponent(value);\n\t\t\t// If second entry with this name\n\t\t} else if (typeof query_string[key] === 'string') {\n\t\t\tvar arr = [query_string[key], decodeURIComponent(value)];\n\t\t\tquery_string[key] = arr;\n\t\t\t// If third or later entry with this name\n\t\t} else {\n\t\t\tquery_string[key].push(decodeURIComponent(value));\n\t\t}\n\t}\n\tif (return_key) {\n\t\tif (keyInObject(return_key, query_string)) {\n\t\t\treturn query_string[return_key];\n\t\t} else {\n\t\t\treturn '';\n\t\t}\n\t} else {\n\t\treturn query_string;\n\t}\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: 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: Login access and menu\nDate: 2025//3/6\nCreator: Clemens Schwaighofer\n*/\n\nexport { loginLogout, createLoginRow, createNavMenu };\nimport { isObject, getObjectCount } from './JavaScriptHelpers.mjs';\nimport { exists } from './DomHelpers.mjs';\nimport { HtmlElementCreator } from './HtmlElementCreator.mjs';\nimport { l10nTranslation } from './l10nTranslation.mjs';\nlet hec = new HtmlElementCreator();\nlet l10n = new l10nTranslation(i18n ?? {});\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/**\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 */\nfunction createLoginRow(login_string, header_id = 'mainHeader')\n{\n\t// if header does not exist, we do nothing\n\tif (exists(header_id)) {\n\t\t// that row must exist already, if not it must be the first in the \"mainHeader\"\n\t\tif (!exists('loginRow')) {\n\t\t\t$('#' + header_id).html(hec.phfo(hec.cel('div', 'loginRow', '', ['loginRow', 'flx-spbt'])));\n\t\t}\n\t\t// clear out just in case for first entry\n\t\t// fill with div name & login/logout button\n\t\t$('#loginRow').html(hec.phfo(hec.cel('div', 'loginRow-name', login_string)));\n\t\t$('#loginRow').append(hec.phfo(hec.cel('div', 'loginRow-info', '')));\n\t\t$('#loginRow').append(hec.phfo(\n\t\t\thec.aelx(\n\t\t\t\t// outer div\n\t\t\t\thec.cel('div', 'loginRow-logout'),\n\t\t\t\t// inner element\n\t\t\t\thec.cel('input', 'logout', '', [], {\n\t\t\t\t\tvalue: l10n.__('Logout'),\n\t\t\t\t\ttype: 'button',\n\t\t\t\t\tonClick: 'loginLogout()'\n\t\t\t\t})\n\t\t\t)\n\t\t));\n\t}\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 */\nfunction createNavMenu(nav_menu, header_id = 'mainHeader')\n{\n\t// must be an object\n\tif (isObject(nav_menu) && getObjectCount(nav_menu) > 1) {\n\t\t// do we have more than one entry, if not, do not show (single page)\n\t\tif (!exists('menuRow')) {\n\t\t\t$('#' + header_id).html(hec.phfo(hec.cel('div', 'menuRow', '', ['menuRow', 'flx-s'])));\n\t\t}\n\t\tvar content = [];\n\t\t$.each(nav_menu, function(key, item) {\n\t\t\t// key is number\n\t\t\t// item is object with entries\n\t\t\tif (key != 0) {\n\t\t\t\tcontent.push(hec.phfo(hec.cel('div', '', '·', ['pd-2'])));\n\t\t\t}\n\t\t\t// ignore item.popup for now\n\t\t\tif (item.enabled) {\n\t\t\t\t// set selected based on window.location.href as the php set will not work\n\t\t\t\tif (window.location.href.indexOf(item.url) != -1) {\n\t\t\t\t\titem.selected = 1;\n\t\t\t\t}\n\t\t\t\t// create the entry\n\t\t\t\tcontent.push(hec.phfo(\n\t\t\t\t\thec.aelx(\n\t\t\t\t\t\thec.cel('div'),\n\t\t\t\t\t\thec.cel('a', '', item.name, ['pd-2'].concat(item.selected ? 'highlight': ''), {\n\t\t\t\t\t\t\thref: item.url\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});\n\t\t$('#menuRow').html(content.join(''));\n\t} else {\n\t\t$('#menuRow').hide();\n\t}\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\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: 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", "/*\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\tcreateLoginRow as _createLoginRow,\n\tcreateNavMenu as _createNavMenu\n} from './utils/LoginMenu.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';\n\nlet aiob = new ActionIndicatorOverlayBox();\nlet hec = new HtmlElementCreator();\n// @ts-ignore\n// eslint-disable-next-line no-undef\nlet l10n = new l10nTranslation(i18n ?? {});\nlet ab = new ActionBox(hec, l10n);\n\n/* export {\n\tescapeHtml,\n\troundPrecision,\n\tformatString,\n\tunescapeHtml,\n\tloadEl,\n\tpop,\n\texpandTA,\n\tgetWindowSize,\n\tgetScrollOffset,\n\tgetScrollOffsetOpener,\n\tsetCenter,\n\tgoToPos,\n\tgoTo,\n\t__,\n\tnumberWithCommas,\n\tconvertLBtoBR,\n\tgetTimestamp,\n\tdec2hex,\n\tgenerateId,\n\trandomIdF,\n\tgetRandomIntInclusive,\n\tisFunction,\n\texecuteFunctionByName,\n\tisObject,\n\tgetObjectCount,\n\tkeyInObject,\n\tgetKeyByValue,\n\tvalueInObject,\n\tdeepCopyFunction,\n\texists,\n\tformatBytes,\n\tformatBytesLong,\n\tstringByteFormat,\n\terrorCatch,\n\tactionIndicator,\n\tactionIndicatorShow,\n\tactionIndicatorHide,\n\toverlayBoxShow,\n\toverlayBoxHide,\n\tsetOverlayBox,\n\thideOverlayBox,\n\tClearCall,\n\tshowActionIndicator,\n\thideActionIndicator,\n\tcheckOverlayExists,\n\tshowOverlayBoxLayers,\n\thideOverlayBoxLayers,\n\tclearCallActionBox,\n\tcel,\n\tael,\n\taelx,\n\taelxar,\n\trel,\n\trcssel,\n\tacssel,\n\tscssel,\n\tphfo,\n\tphfa,\n\thtml_options,\n\thtml_options_block,\n\thtml_options_refill,\n\tparseQueryString,\n\tgetQueryStringParam,\n\tloginLogout,\n\tcreateLoginRow,\n\tcreateNavMenu,\n\tshowFillActionBox,\n\tfillActionBox,\n\tadjustActionBox,\n\thideAllActionBoxes,\n\thideActionBox,\n\tshowActionBox,\n\tcloseActionBox,\n\tshowActionBoxFloat,\n\tcloseActionBoxFloat,\n\tcreateActionBox,\n\tadjustActionBoxHeight,\n}; */\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\t_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\t_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", "createLoginRow", "createNavMenu"] + "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\tname: options.name, // override name if set [name gets ignored in tree build anyway]\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\t// base.sub.push(Object.assign({}, attach));\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\t// base.sub.push(Object.assign({}, attach));\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\t// base.sub.push(Object.assign({}, attach[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\t// base.sub.push(Object.assign({}, attach[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}\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\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 };\nimport { keyInObject } from './JavaScriptHelpers.mjs';\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 */\nfunction parseQueryString(query = '', return_key = '')\n{\n\tif (!query) {\n\t\tquery = window.location.search.substring(1);\n\t}\n\tvar vars = query.split('&');\n\tvar query_string = {};\n\tfor (var i = 0; i < vars.length; i++) {\n\t\tvar pair = vars[i].split('=');\n\t\tvar key = decodeURIComponent(pair[0]);\n\t\tvar value = decodeURIComponent(pair[1]);\n\t\t// skip over run if there is nothing\n\t\tif (!key || value === 'undefined') {\n\t\t\tcontinue;\n\t\t}\n\t\t// If first entry with this name\n\t\tif (typeof query_string[key] === 'undefined') {\n\t\t\tquery_string[key] = decodeURIComponent(value);\n\t\t\t// If second entry with this name\n\t\t} else if (typeof query_string[key] === 'string') {\n\t\t\tvar arr = [query_string[key], decodeURIComponent(value)];\n\t\t\tquery_string[key] = arr;\n\t\t\t// If third or later entry with this name\n\t\t} else {\n\t\t\tquery_string[key].push(decodeURIComponent(value));\n\t\t}\n\t}\n\tif (return_key) {\n\t\tif (keyInObject(return_key, query_string)) {\n\t\t\treturn query_string[return_key];\n\t\t} else {\n\t\t\treturn '';\n\t\t}\n\t} else {\n\t\treturn query_string;\n\t}\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"] } From 46cda40d37cb409dedf5666ed0a0fe5d5e993a2e Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 28 Mar 2025 10:53:42 +0900 Subject: [PATCH 084/105] JavaScript general utils file updates --- www/admin/layout/javascript/utils.js | 43 ++++---------------- www/admin/layout/javascript/utils.min.js | 2 +- www/admin/layout/javascript/utils.min.js.map | 4 +- 3 files changed, 12 insertions(+), 37 deletions(-) diff --git a/www/admin/layout/javascript/utils.js b/www/admin/layout/javascript/utils.js index b581ff32..52124baa 100644 --- a/www/admin/layout/javascript/utils.js +++ b/www/admin/layout/javascript/utils.js @@ -127,8 +127,8 @@ var HtmlElementCreator = class { return { tag, id, + // override name if set, else id is used. Only for input/button name: options.name, - // override name if set [name gets ignored in tree build anyway] content, css, options, @@ -230,6 +230,7 @@ var HtmlElementCreator = class { scssel(_element, rcss, acss) { this.rcssel(_element, rcss); this.acssel(_element, acss); + return _element; } /** * parses the object tree created with cel/ael and converts it into an HTML string @@ -585,6 +586,9 @@ function formatBytes(bytes) { if (typeof bytes === "bigint") { bytes = Number(bytes); } + if (isNaN(bytes)) { + return bytes.toString(); + } do { bytes = bytes / 1024; i++; @@ -628,37 +632,8 @@ function stringByteFormat(bytes, raw = false) { } // src/utils/UrlParser.mjs -function parseQueryString(query = "", return_key = "") { - if (!query) { - query = window.location.search.substring(1); - } - var vars = query.split("&"); - var query_string = {}; - for (var i = 0; i < vars.length; i++) { - var pair = vars[i].split("="); - var key = decodeURIComponent(pair[0]); - var value = decodeURIComponent(pair[1]); - if (!key || value === "undefined") { - continue; - } - if (typeof query_string[key] === "undefined") { - query_string[key] = decodeURIComponent(value); - } else if (typeof query_string[key] === "string") { - var arr = [query_string[key], decodeURIComponent(value)]; - query_string[key] = arr; - } else { - query_string[key].push(decodeURIComponent(value)); - } - } - if (return_key) { - if (keyInObject(return_key, query_string)) { - return query_string[return_key]; - } else { - return ""; - } - } else { - return query_string; - } +function parseQueryString(query = "", return_key = "", single = false) { + return getQueryStringParam(return_key, query, single); } function getQueryStringParam(search = "", query = "", single = false) { if (!query) { @@ -1357,7 +1332,7 @@ function roundPrecision2(number, prec) { return roundPrecision(number, prec); } function formatString2(string, ...args) { - return formatString(string, args); + return formatString(string, ...args); } function unescapeHtml2(string) { return unescapeHtml(string); @@ -1501,7 +1476,7 @@ function ael(base, attach, id = "") { return hec.ael(base, attach, id); } function aelx(base, ...attach) { - return hec.aelx(base, attach); + return hec.aelx(base, ...attach); } function aelxar(base, attach) { return hec.aelxar(base, attach); diff --git a/www/admin/layout/javascript/utils.min.js b/www/admin/layout/javascript/utils.min.js index 3185f018..782dc1ce 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){this.rcssel(_element,rcss),this.acssel(_element,acss)}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;typeof bytes=="bigint"&&(bytes=Number(bytes));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=""){query||(query=window.location.search.substring(1));for(var vars=query.split("&"),query_string={},i=0;i"u")query_string[key]=decodeURIComponent(value);else if(typeof query_string[key]=="string"){var arr=[query_string[key],decodeURIComponent(value)];query_string[key]=arr}else query_string[key].push(decodeURIComponent(value))}return return_key?keyInObject(return_key,query_string)?query_string[return_key]:"":query_string}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)} +`));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)} //# 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 cacaf832..86cf0870 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\tname: options.name, // override name if set [name gets ignored in tree build anyway]\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\t// base.sub.push(Object.assign({}, attach));\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\t// base.sub.push(Object.assign({}, attach));\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\t// base.sub.push(Object.assign({}, attach[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\t// base.sub.push(Object.assign({}, attach[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}\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\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 };\nimport { keyInObject } from './JavaScriptHelpers.mjs';\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 */\nfunction parseQueryString(query = '', return_key = '')\n{\n\tif (!query) {\n\t\tquery = window.location.search.substring(1);\n\t}\n\tvar vars = query.split('&');\n\tvar query_string = {};\n\tfor (var i = 0; i < vars.length; i++) {\n\t\tvar pair = vars[i].split('=');\n\t\tvar key = decodeURIComponent(pair[0]);\n\t\tvar value = decodeURIComponent(pair[1]);\n\t\t// skip over run if there is nothing\n\t\tif (!key || value === 'undefined') {\n\t\t\tcontinue;\n\t\t}\n\t\t// If first entry with this name\n\t\tif (typeof query_string[key] === 'undefined') {\n\t\t\tquery_string[key] = decodeURIComponent(value);\n\t\t\t// If second entry with this name\n\t\t} else if (typeof query_string[key] === 'string') {\n\t\t\tvar arr = [query_string[key], decodeURIComponent(value)];\n\t\t\tquery_string[key] = arr;\n\t\t\t// If third or later entry with this name\n\t\t} else {\n\t\t\tquery_string[key].push(decodeURIComponent(value));\n\t\t}\n\t}\n\tif (return_key) {\n\t\tif (keyInObject(return_key, query_string)) {\n\t\t\treturn query_string[return_key];\n\t\t} else {\n\t\t\treturn '';\n\t\t}\n\t} else {\n\t\treturn query_string;\n\t}\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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", + "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"] } From 32decdd037f003721044c4974c5d13505a81b1a3 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 28 Mar 2025 10:58:07 +0900 Subject: [PATCH 085/105] Readme update --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index dec1cb13..76278ef3 100644 --- a/README.md +++ b/README.md @@ -114,3 +114,11 @@ Add `.libs` to the master .gitingore ### Update phpunit On a version update the old phpunit folder in .libs has to be removed and the new version extracted again + +## Javascript + +The original edit.js javascript functions are now in utils.js or utils.min.js. + +The development for thos files is located in a different repository + +https://[service]/CodeBlocks/javascript-utils From b302fb405347e2ef2a3195064d6ebfc813e5078c Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 1 Apr 2025 11:15:00 +0900 Subject: [PATCH 086/105] Add CombinedDateTime class calcDaysInteral wrapper functions calcDaysIntervalNamedIndex for force using named index and returning only named index calcDaysIntervalNumIndex for force using numeric index and returning only numeric index --- .../Combined/CoreLibsCombinedDateTimeTest.php | 26 +++++++- www/lib/CoreLibs/Combined/DateTime.php | 60 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/4dev/tests/Combined/CoreLibsCombinedDateTimeTest.php b/4dev/tests/Combined/CoreLibsCombinedDateTimeTest.php index 58040c49..1833f708 100644 --- a/4dev/tests/Combined/CoreLibsCombinedDateTimeTest.php +++ b/4dev/tests/Combined/CoreLibsCombinedDateTimeTest.php @@ -1068,8 +1068,32 @@ final class CoreLibsCombinedDateTimeTest extends TestCase return_named:$return_named, include_end_date:$include_end_date, exclude_start_date:$exclude_start_date - ) + ), + 'call calcDaysInterval' ); + if ($return_named) { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::calcDaysIntervalNamedIndex( + $input_a, + $input_b, + include_end_date:$include_end_date, + exclude_start_date:$exclude_start_date + ), + 'call calcDaysIntervalNamedIndex' + ); + } else { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::calcDaysIntervalNumIndex( + $input_a, + $input_b, + include_end_date:$include_end_date, + exclude_start_date:$exclude_start_date + ), + 'call calcDaysIntervalNamedIndex' + ); + } } /** diff --git a/www/lib/CoreLibs/Combined/DateTime.php b/www/lib/CoreLibs/Combined/DateTime.php index d123c53a..89d1cd87 100644 --- a/www/lib/CoreLibs/Combined/DateTime.php +++ b/www/lib/CoreLibs/Combined/DateTime.php @@ -714,6 +714,66 @@ class DateTime } } + /** + * wrapper for calcDaysInterval with numeric return only + * + * @param string $start_date valid start date (y/m/d) + * @param string $end_date valid end date (y/m/d) + * @param bool $include_end_date [default=true] include end date in calc + * @param bool $exclude_start_date [default=false] include end date in calc + * @return array{0:int,1:int,2:int,3:bool} + */ + public static function calcDaysIntervalNumIndex( + string $start_date, + string $end_date, + bool $include_end_date = true, + bool $exclude_start_date = false + ): array { + $values = self::calcDaysInterval( + $start_date, + $end_date, + false, + $include_end_date, + $exclude_start_date + ); + return [ + $values[0], + $values[1], + $values[2], + $values[3], + ]; + } + + /** + * wrapper for calcDaysInterval with named return only + * + * @param string $start_date valid start date (y/m/d) + * @param string $end_date valid end date (y/m/d) + * @param bool $include_end_date [default=true] include end date in calc + * @param bool $exclude_start_date [default=false] include end date in calc + * @return array{overall:int,weekday:int,weekend:int,reverse:bool} + */ + public static function calcDaysIntervalNamedIndex( + string $start_date, + string $end_date, + bool $include_end_date = true, + bool $exclude_start_date = false + ): array { + $values = self::calcDaysInterval( + $start_date, + $end_date, + true, + $include_end_date, + $exclude_start_date + ); + return [ + 'overall' => $values['overall'], + 'weekday' => $values['weekday'], + 'weekend' => $values['weekend'], + 'reverse' => $values['reverse'], + ]; + } + /** * check if a weekend day (sat/sun) is in the given date range * Can have time too, but is not needed From cf1989819aa1e33fc2c913172ae22958b4d1d2dd Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 1 Apr 2025 11:22:59 +0900 Subject: [PATCH 087/105] phpstan fixes --- www/admin/class_test.datetime.php | 5 +++- www/admin/class_test.db.php | 40 ++++++++++++------------- www/admin/class_test.session.php | 2 ++ www/lib/CoreLibs/Combined/DateTime.php | 16 +++++----- www/lib/CoreLibs/Output/ProgressBar.php | 4 +-- 5 files changed, 35 insertions(+), 32 deletions(-) diff --git a/www/admin/class_test.datetime.php b/www/admin/class_test.datetime.php index 45bca5e8..54633004 100644 --- a/www/admin/class_test.datetime.php +++ b/www/admin/class_test.datetime.php @@ -473,7 +473,10 @@ function intervalStringFormatDeprecated( // print "-> V: $value | $part, $time_name | I: " . is_int($value) . " | F: " . is_float($value) // . " | " . ($value != 0 ? 'Not zero' : 'ZERO') . "
"; // var_dump($skip_last_zero); - if ($value != 0 || $skip_zero === false || $skip_last_zero === false) { + if ( + is_numeric($value) && + ($value != 0 || $skip_zero === false || $skip_last_zero === false) + ) { if ($part == 'f') { if ($truncate_nanoseconds === true) { $value = round($value, 3); diff --git a/www/admin/class_test.db.php b/www/admin/class_test.db.php index a5537600..4a7676e7 100644 --- a/www/admin/class_test.db.php +++ b/www/admin/class_test.db.php @@ -76,41 +76,41 @@ $db->dbResetEncoding(); // empty calls, none of the below should fail // -$db->dbGetCursor(); +$foo = $db->dbGetCursor(); // -$db->dbGetCursorExt(); +$foo = $db->dbGetCursorExt(); // -$db->dbGetCursorPos('SELECT foo', ['bar']); +$foo = $db->dbGetCursorPos('SELECT foo', ['bar']); // -$db->dbGetCursorNumRows('SELECT foo', ['bar']); +$foo = $db->dbGetCursorNumRows('SELECT foo', ['bar']); // -$db->dbGetInsertPKName(); +$foo = $db->dbGetInsertPKName(); // -$db->dbGetInsertPK(); +$foo = $db->dbGetInsertPK(); // -$db->dbGetReturningExt(); -$db->dbGetReturningExt('foo'); -$db->dbGetReturningExt('foo', 0); -$db->dbGetReturningExt(pos:0); +$foo = $db->dbGetReturningExt(); +$foo = $db->dbGetReturningExt('foo'); +$foo = $db->dbGetReturningExt('foo', 0); +$foo = $db->dbGetReturningExt(pos:0); // -$db->dbGetReturningArray(); +$foo = $db->dbGetReturningArray(); // -$db->dbGetNumRows(); +$foo = $db->dbGetNumRows(); // -$db->dbGetNumFields(); +$foo = $db->dbGetNumFields(); // -$db->dbGetFieldNames(); +$foo = $db->dbGetFieldNames(); // -$db->dbGetFieldTypes(); +$foo = $db->dbGetFieldTypes(); // -$db->dbGetFieldNameTypes(); +$foo = $db->dbGetFieldNameTypes(); // -$db->dbGetFieldName(0); +$foo = $db->dbGetFieldName(0); // -$db->dbGetFieldType(0); -$db->dbGetFieldType('foo'); +$foo = $db->dbGetFieldType(0); +$foo = $db->dbGetFieldType('foo'); // -$db->dbGetPrepareCursorValue('foo', 'bar'); +$foo = $db->dbGetPrepareCursorValue('foo', 'bar'); // TEST CACHE READS diff --git a/www/admin/class_test.session.php b/www/admin/class_test.session.php index eb73256d..139e6a80 100644 --- a/www/admin/class_test.session.php +++ b/www/admin/class_test.session.php @@ -86,8 +86,10 @@ if (!isset($_SESSION['counter'])) { $_SESSION['counter']++; print "[READ] A " . $var . ": " . ($_SESSION[$var] ?? '{UNSET}') . "
"; $_SESSION[$var] = $value; +/** @phpstan-ignore-next-line nullCoalesce.offset */ print "[READ] B " . $var . ": " . ($_SESSION[$var] ?? '{UNSET}') . "
"; print "[READ] Confirm " . $var . " is " . $value . ": " +/** @phpstan-ignore-next-line equal.alwaysTrue, nullCoalesce.offset */ . (($_SESSION[$var] ?? '') == $value ? 'Matching' : 'Not matching') . "
"; // test set wrappers methods diff --git a/www/lib/CoreLibs/Combined/DateTime.php b/www/lib/CoreLibs/Combined/DateTime.php index 89d1cd87..b86bdee2 100644 --- a/www/lib/CoreLibs/Combined/DateTime.php +++ b/www/lib/CoreLibs/Combined/DateTime.php @@ -737,10 +737,10 @@ class DateTime $exclude_start_date ); return [ - $values[0], - $values[1], - $values[2], - $values[3], + $values[0] ?? 0, + $values[1] ?? 0, + $values[2] ?? 0, + $values[3] ?? false, ]; } @@ -767,10 +767,10 @@ class DateTime $exclude_start_date ); return [ - 'overall' => $values['overall'], - 'weekday' => $values['weekday'], - 'weekend' => $values['weekend'], - 'reverse' => $values['reverse'], + 'overall' => $values['overall'] ?? 0, + 'weekday' => $values['weekday'] ?? 0, + 'weekend' => $values['weekend'] ?? 0, + 'reverse' => $values['reverse'] ?? false, ]; } diff --git a/www/lib/CoreLibs/Output/ProgressBar.php b/www/lib/CoreLibs/Output/ProgressBar.php index 5c4f9c76..c64b6995 100644 --- a/www/lib/CoreLibs/Output/ProgressBar.php +++ b/www/lib/CoreLibs/Output/ProgressBar.php @@ -418,9 +418,7 @@ class ProgressBar // if this is percent, we ignore anything, it is auto positioned if ($this->label[$name]['type'] != 'percent') { foreach (['top', 'left', 'width', 'height'] as $pos_name) { - if ($$pos_name !== false) { - $this->label[$name][$pos_name] = intval($$pos_name); - } + $this->label[$name][$pos_name] = intval($$pos_name); } if ($align != '') { From 3d13f55c35dbc22e65417566d621eb00964c3dd5 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 4 Apr 2025 15:08:58 +0900 Subject: [PATCH 088/105] Update Hash Class Add new constant: STANDARD_HASH for sha256 Deprecate DEFAULT_HASH is now STANDARD_HASH_SHORT Deprecated __sha1Short: replace with __crc32b with the default parameter use_sha false replace with sha1Short if use_sha is true __hash: replace with hashShort if default hash type replace with hash for all others with new default STANDARD_HASH __hashLong: replace with hashLong New: hashShort: returns STANDARD_HASH_SHORT which is __hash default type hashStd: returns STANDARD_HASH sha256 hash: switches to STANDARD_HASH as default type --- 4dev/tests/Create/CoreLibsCreateHashTest.php | 110 +++++++++++++++++-- www/admin/class_test.hash.php | 21 ++-- www/lib/CoreLibs/Create/Hash.php | 83 ++++++++++++-- 3 files changed, 190 insertions(+), 24 deletions(-) diff --git a/4dev/tests/Create/CoreLibsCreateHashTest.php b/4dev/tests/Create/CoreLibsCreateHashTest.php index fcab739e..4eb7e694 100644 --- a/4dev/tests/Create/CoreLibsCreateHashTest.php +++ b/4dev/tests/Create/CoreLibsCreateHashTest.php @@ -114,6 +114,22 @@ final class CoreLibsCreateHashTest extends TestCase ]; } + /** + * Undocumented function + * + * @return array + */ + public function hashStandardProvider(): array + { + $hash_source = 'Some String Text'; + return [ + 'Long Hash check: ' . \CoreLibs\Create\Hash::STANDARD_HASH => [ + $hash_source, + hash(\CoreLibs\Create\Hash::STANDARD_HASH, $hash_source) + ], + ]; + } + /** * Undocumented function * @@ -136,9 +152,13 @@ final class CoreLibsCreateHashTest extends TestCase /** * Undocumented function * + * phpcs:disable Generic.Files.LineLength * @covers ::__sha1Short + * @covers ::__crc32b + * @covers ::sha1Short * @dataProvider sha1ShortProvider - * @testdox __sha1Short $input will be $expected (crc32b) and $expected_sha1 (sha1 short) [$_dataName] + * @testdox __sha1Short/__crc32b/sha1short $input will be $expected (crc32b) and $expected_sha1 (sha1 short) [$_dataName] + * phpcs:enable Generic.Files.LineLength * * @param string $input * @param string $expected @@ -149,16 +169,29 @@ final class CoreLibsCreateHashTest extends TestCase // uses crc32b $this->assertEquals( $expected, - \CoreLibs\Create\Hash::__sha1Short($input) + \CoreLibs\Create\Hash::__sha1Short($input), + '__sha1Short depreacted' ); $this->assertEquals( $expected, - \CoreLibs\Create\Hash::__sha1Short($input, false) + \CoreLibs\Create\Hash::__sha1Short($input, false), + '__sha1Short (false) depreacted' + ); + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::__crc32b($input), + '__crc32b' ); // sha1 type $this->assertEquals( $expected_sha1, - \CoreLibs\Create\Hash::__sha1Short($input, true) + \CoreLibs\Create\Hash::__sha1Short($input, true), + '__sha1Short (true) depreacted' + ); + $this->assertEquals( + $expected_sha1, + \CoreLibs\Create\Hash::sha1Short($input), + 'sha1Short' ); } @@ -166,8 +199,10 @@ final class CoreLibsCreateHashTest extends TestCase * Undocumented function * * @covers ::__hash + * @covers ::hashShort + * @covers ::hashShort * @dataProvider hashProvider - * @testdox __hash $input with $hash_type will be $expected [$_dataName] + * @testdox __hash/hashShort/hash $input with $hash_type will be $expected [$_dataName] * * @param string $input * @param string|null $hash_type @@ -179,12 +214,24 @@ final class CoreLibsCreateHashTest extends TestCase if ($hash_type === null) { $this->assertEquals( $expected, - \CoreLibs\Create\Hash::__hash($input) + \CoreLibs\Create\Hash::__hash($input), + '__hash' + ); + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::hashShort($input), + 'hashShort' ); } else { $this->assertEquals( $expected, - \CoreLibs\Create\Hash::__hash($input, $hash_type) + \CoreLibs\Create\Hash::__hash($input, $hash_type), + '__hash with hash type' + ); + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::hash($input, $hash_type), + 'hash with hash type' ); } } @@ -193,8 +240,9 @@ final class CoreLibsCreateHashTest extends TestCase * Undocumented function * * @covers ::__hashLong + * @covers ::hashLong * @dataProvider hashLongProvider - * @testdox __hashLong $input will be $expected [$_dataName] + * @testdox __hashLong/hashLong $input will be $expected [$_dataName] * * @param string $input * @param string $expected @@ -206,6 +254,52 @@ final class CoreLibsCreateHashTest extends TestCase $expected, \CoreLibs\Create\Hash::__hashLong($input) ); + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::hashLong($input) + ); + } + + /** + * Undocumented function + * + * @covers ::hash + * @covers ::hashStd + * @dataProvider hashStandardProvider + * @testdox hash/hashStd $input will be $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testHashStandard(string $input, string $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::hashStd($input) + ); + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::hash($input) + ); + } + + /** + * Undocumented function + * + * @covers ::hash + * @testdox hash with invalid type [$_dataName] + * + * @return void + */ + public function testInvalidHashType(): void + { + $hash_source = 'Some String Text'; + $expected = hash(\CoreLibs\Create\Hash::STANDARD_HASH, $hash_source); + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::hash($hash_source, 'DOES_NOT_EXIST') + ); } } diff --git a/www/admin/class_test.hash.php b/www/admin/class_test.hash.php index c269a5dc..57da698f 100644 --- a/www/admin/class_test.hash.php +++ b/www/admin/class_test.hash.php @@ -38,9 +38,11 @@ print '

' . $PAGE_NAME . '

'; $to_crc = 'Some text block'; // static -print "S::__CRC32B: $to_crc: " . $hash_class::__crc32b($to_crc) . "
"; -print "S::__SHA1SHORT(off): $to_crc: " . $hash_class::__sha1short($to_crc) . "
"; -print "S::__SHA1SHORT(on): $to_crc: " . $hash_class::__sha1short($to_crc, true) . "
"; +print "S::__CRC32B: $to_crc: " . Hash::__crc32b($to_crc) . "
"; +print "S::__SHA1SHORT(off): $to_crc: " . Hash::__sha1short($to_crc) . "
"; +print "S::hashShort(__sha1Short replace): $to_crc: " . Hash::hashShort($to_crc) . "
"; +print "S::__SHA1SHORT(on): $to_crc: " . Hash::__sha1short($to_crc, true) . "
"; +print "S::sha1Short(__sha1Short replace): $to_crc: " . Hash::sha1Short($to_crc, true) . "
"; print "S::__hash(d): " . $to_crc . "/" . Hash::STANDARD_HASH_SHORT . ": " . $hash_class::__hash($to_crc) . "
"; foreach (['adler32', 'fnv132', 'fnv1a32', 'joaat', 'sha512'] as $__hash_c) { @@ -53,13 +55,16 @@ echo "
"; $text = 'Some String Text'; $type = 'crc32b'; print "Hash: " . $type . ": " . hash($type, $text) . "
"; -print "Class: " . $type . ": " . Hash::__hash($text, $type) . "
"; +print "Class (old): " . $type . ": " . Hash::__hash($text, $type) . "
"; +print "Class (new): " . $type . ": " . Hash::hash($text, $type) . "
"; echo "
"; -print "
CURRENT STANDARD_HASH_SHORT: " . Hash::STANDARD_HASH_SHORT . "
"; -print "
CURRENT STANDARD_HASH_LONG: " . Hash::STANDARD_HASH_LONG . "
"; -print "HASH SHORT: " . $to_crc . ": " . Hash::__hash($to_crc) . "
"; -print "HASH LONG: " . $to_crc . ": " . Hash::__hashLong($to_crc) . "
"; +print "CURRENT STANDARD_HASH_SHORT: " . Hash::STANDARD_HASH_SHORT . "
"; +print "CURRENT STANDARD_HASH_LONG: " . Hash::STANDARD_HASH_LONG . "
"; +print "CURRENT STANDARD_HASH: " . Hash::STANDARD_HASH . "
"; +print "HASH SHORT: " . $to_crc . ": " . Hash::hashShort($to_crc) . "
"; +print "HASH LONG: " . $to_crc . ": " . Hash::hashLong($to_crc) . "
"; +print "HASH DEFAULT: " . $to_crc . ": " . Hash::hashStd($to_crc) . "
"; // print "UNIQU ID SHORT : " . Hash::__uniqId() . "
"; // print "UNIQU ID LONG : " . Hash::__uniqIdLong() . "
"; diff --git a/www/lib/CoreLibs/Create/Hash.php b/www/lib/CoreLibs/Create/Hash.php index bf83afeb..e408f7d3 100644 --- a/www/lib/CoreLibs/Create/Hash.php +++ b/www/lib/CoreLibs/Create/Hash.php @@ -10,9 +10,14 @@ namespace CoreLibs\Create; class Hash { + /** @var string default short hash -> deprecated use STANDARD_HASH_SHORT */ public const DEFAULT_HASH = 'adler32'; + /** @var string default long hash (40 chars) */ public const STANDARD_HASH_LONG = 'ripemd160'; + /** @var string default short hash (8 chars) */ public const STANDARD_HASH_SHORT = 'adler32'; + /** @var string this is the standard hash to use hashStd and hash (64 chars) */ + public const STANDARD_HASH = 'sha256'; /** * checks php version and if >=5.2.7 it will flip the string @@ -20,6 +25,7 @@ class Hash * hash returns false * preg_replace fails for older php version * Use __hash with crc32b or hash('crc32b', ...) for correct output + * For future short hashes use hashShort() instead * * @param string $string string to crc * @return string crc32b hash (old type) @@ -43,19 +49,31 @@ class Hash * replacement for __crc32b call * * @param string $string string to hash - * @param bool $use_sha use sha instead of crc32b (default false) + * @param bool $use_sha use sha1 instead of crc32b (default false) * @return string hash of the string + * @deprecated use __crc32b() for drop in replacement with default, or sha1Short() for use sha true */ public static function __sha1Short(string $string, bool $use_sha = false): string { if ($use_sha) { - // return only the first 9 characters - return substr(hash('sha1', $string), 0, 9); + return self::sha1Short($string); } else { return self::__crc32b($string); } } + /** + * returns a short sha1 + * + * @param string $string string to hash + * @return string hash of the string + */ + public static function sha1Short(string $string): string + { + // return only the first 9 characters + return substr(hash('sha1', $string), 0, 9); + } + /** * replacemend for __crc32b call (alternate) * defaults to adler 32 @@ -65,32 +83,81 @@ class Hash * @param string $string string to hash * @param string $hash_type hash type (default adler32) * @return string hash of the string + * @deprecated use hashShort() of short hashes with adler 32 or hash() for other hash types */ public static function __hash( string $string, - string $hash_type = self::DEFAULT_HASH + string $hash_type = self::STANDARD_HASH_SHORT + ): string { + return self::hash($string, $hash_type); + } + + /** + * creates a hash over string if any valid hash given. + * if no hash type set use sha256 + * + * @param string $string string to ash + * @param string $hash_type hash type (default sha256) + * @return string hash of the string + */ + public static function hash( + string $string, + string $hash_type = self::STANDARD_HASH ): string { - // if not empty, check if in valid list if ( empty($hash_type) || !in_array($hash_type, hash_algos()) ) { // fallback to default hash type if none set or invalid - $hash_type = self::DEFAULT_HASH; + $hash_type = self::STANDARD_HASH; } return hash($hash_type, $string); } /** - * Wrapper function for standard long hashd + * short hash with max length of 8, uses adler32 + * + * @param string $string string to hash + * @return string hash of the string + */ + public static function hashShort(string $string): string + { + return hash(self::STANDARD_HASH_SHORT, $string); + } + + /** + * Wrapper function for standard long hash + * + * @param string $string String to be hashed + * @return string Hashed string + * @deprecated use hashLong() + */ + public static function __hashLong(string $string): string + { + return self::hashLong($string); + } + + /** + * Wrapper function for standard long hash, uses ripmd160 * * @param string $string String to be hashed * @return string Hashed string */ - public static function __hashLong(string $string): string + public static function hashLong(string $string): string { return hash(self::STANDARD_HASH_LONG, $string); } + + /** + * create standard hash basd on STANDAR_HASH, currently sha256 + * + * @param string $string string in + * @return string hash of the string + */ + public static function hashStd(string $string): string + { + return self::hash($string, self::STANDARD_HASH); + } } // __END__ From 1791ec39084fa12b81553d3fb97168c042f2a3b5 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 4 Apr 2025 15:17:42 +0900 Subject: [PATCH 089/105] phan and phpstan fixes for hash uses in CoreLibs --- www/admin/class_test.hash.php | 8 ++++---- www/lib/CoreLibs/Basic.php | 12 ++++++++---- www/lib/CoreLibs/DB/IO.php | 2 +- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/www/admin/class_test.hash.php b/www/admin/class_test.hash.php index 57da698f..0d78175d 100644 --- a/www/admin/class_test.hash.php +++ b/www/admin/class_test.hash.php @@ -39,10 +39,10 @@ print '

' . $PAGE_NAME . '

'; $to_crc = 'Some text block'; // static print "S::__CRC32B: $to_crc: " . Hash::__crc32b($to_crc) . "
"; -print "S::__SHA1SHORT(off): $to_crc: " . Hash::__sha1short($to_crc) . "
"; +// print "S::__SHA1SHORT(off): $to_crc: " . Hash::__sha1short($to_crc) . "
"; print "S::hashShort(__sha1Short replace): $to_crc: " . Hash::hashShort($to_crc) . "
"; -print "S::__SHA1SHORT(on): $to_crc: " . Hash::__sha1short($to_crc, true) . "
"; -print "S::sha1Short(__sha1Short replace): $to_crc: " . Hash::sha1Short($to_crc, true) . "
"; +// print "S::__SHA1SHORT(on): $to_crc: " . Hash::__sha1short($to_crc, true) . "
"; +print "S::sha1Short(__sha1Short replace): $to_crc: " . Hash::sha1Short($to_crc) . "
"; print "S::__hash(d): " . $to_crc . "/" . Hash::STANDARD_HASH_SHORT . ": " . $hash_class::__hash($to_crc) . "
"; foreach (['adler32', 'fnv132', 'fnv1a32', 'joaat', 'sha512'] as $__hash_c) { @@ -55,7 +55,7 @@ echo "
"; $text = 'Some String Text'; $type = 'crc32b'; print "Hash: " . $type . ": " . hash($type, $text) . "
"; -print "Class (old): " . $type . ": " . Hash::__hash($text, $type) . "
"; +// print "Class (old): " . $type . ": " . Hash::__hash($text, $type) . "
"; print "Class (new): " . $type . ": " . Hash::hash($text, $type) . "
"; echo "
"; diff --git a/www/lib/CoreLibs/Basic.php b/www/lib/CoreLibs/Basic.php index c259bd79..1eb3410f 100644 --- a/www/lib/CoreLibs/Basic.php +++ b/www/lib/CoreLibs/Basic.php @@ -1024,8 +1024,12 @@ class Basic */ public function __sha1Short(string $string, bool $use_sha = false): string { - trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\Hash::__sha1Short()', E_USER_DEPRECATED); - return \CoreLibs\Create\Hash::__sha1Short($string, $use_sha); + trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\Hash::sha1Short() or ::__crc32b()', E_USER_DEPRECATED); + if ($use_sha) { + return \CoreLibs\Create\Hash::sha1Short($string); + } else { + return \CoreLibs\Create\Hash::__crc32b($string); + } } /** @@ -1040,8 +1044,8 @@ class Basic */ public function __hash(string $string, string $hash_type = 'adler32'): string { - trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\Hash::__hash()', E_USER_DEPRECATED); - return \CoreLibs\Create\Hash::__hash($string, $hash_type); + trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\Hash::hash()', E_USER_DEPRECATED); + return \CoreLibs\Create\Hash::hash($string, $hash_type); } // *** HASH FUNCTIONS END diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index 47206323..a10a089b 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -4056,7 +4056,7 @@ class IO */ public function dbGetQueryHash(string $query, array $params = []): string { - return Hash::__hashLong( + return Hash::hashLong( $query . ( $params !== [] ? '#' . json_encode($params) : '' From f4ddc5a5fc52aad1fe6828f98393be30edd01f44 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 7 Apr 2025 09:05:37 +0900 Subject: [PATCH 090/105] Add hash hmac to the Create Hash class --- 4dev/tests/Create/CoreLibsCreateHashTest.php | 126 ++++++++++++++++++- www/admin/class_test.hash.php | 30 +++++ www/lib/CoreLibs/Create/Hash.php | 62 ++++++++- 3 files changed, 210 insertions(+), 8 deletions(-) diff --git a/4dev/tests/Create/CoreLibsCreateHashTest.php b/4dev/tests/Create/CoreLibsCreateHashTest.php index 4eb7e694..9ce36b35 100644 --- a/4dev/tests/Create/CoreLibsCreateHashTest.php +++ b/4dev/tests/Create/CoreLibsCreateHashTest.php @@ -21,8 +21,10 @@ final class CoreLibsCreateHashTest extends TestCase public function hashData(): array { return [ - 'any string' => [ + 'hash tests' => [ + // this is the string 'text' => 'Some String Text', + // hash list special 'crc32b_reverse' => 'c5c21d91', // crc32b (in revere) 'sha1Short' => '4d2bc9ba0', // sha1Short // via hash @@ -31,6 +33,8 @@ final class CoreLibsCreateHashTest extends TestCase 'fnv132' => '9df444f9', // hash: fnv132 'fnv1a32' => '2c5f91b9', // hash: fnv1a32 'joaat' => '50dab846', // hash: joaat + 'ripemd160' => 'aeae3f041b20136451519edd9361570909300342', // hash: ripemd160, + 'sha256' => '9055080e022f224fa835929b80582b3c71c672206fa3a49a87412c25d9d42ceb', // hash: sha256 ] ]; } @@ -81,7 +85,7 @@ final class CoreLibsCreateHashTest extends TestCase { $list = []; foreach ($this->hashData() as $name => $values) { - foreach ([null, 'crc32b', 'adler32', 'fnv132', 'fnv1a32', 'joaat'] as $_hash_type) { + foreach ([null, 'crc32b', 'adler32', 'fnv132', 'fnv1a32', 'joaat', 'ripemd160', 'sha256'] as $_hash_type) { // default value test if ($_hash_type === null) { $hash_type = \CoreLibs\Create\Hash::STANDARD_HASH_SHORT; @@ -288,7 +292,7 @@ final class CoreLibsCreateHashTest extends TestCase * Undocumented function * * @covers ::hash - * @testdox hash with invalid type [$_dataName] + * @testdox hash with invalid type * * @return void */ @@ -301,6 +305,122 @@ final class CoreLibsCreateHashTest extends TestCase \CoreLibs\Create\Hash::hash($hash_source, 'DOES_NOT_EXIST') ); } + + /** + * Note: this only tests default sha256 + * + * @covers ::hashHmac + * @testdox hash hmac test + * + * @return void + */ + public function testHashMac(): void + { + $hash_key = 'FIX KEY'; + $hash_source = 'Some String Text'; + $expected = '16479b3ef6fa44e1cdd8b2dcfaadf314d1a7763635e8738f1e7996d714d9b6bf'; + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::hashHmac($hash_source, $hash_key) + ); + } + + /** + * Undocumented function + * + * @covers ::hashHmac + * @testdox hash hmac with invalid type + * + * @return void + */ + public function testInvalidHashMacType(): void + { + $hash_key = 'FIX KEY'; + $hash_source = 'Some String Text'; + $expected = hash_hmac(\CoreLibs\Create\Hash::STANDARD_HASH, $hash_source, $hash_key); + $this->assertEquals( + $expected, + \CoreLibs\Create\Hash::hashHmac($hash_source, $hash_key, 'DOES_NOT_EXIST') + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function providerHashTypes(): array + { + return [ + 'Hash crc32b' => [ + 'crc32b', + true, + false, + ], + 'Hash adler32' => [ + 'adler32', + true, + false, + ], + 'HAsh fnv132' => [ + 'fnv132', + true, + false, + ], + 'Hash fnv1a32' => [ + 'fnv1a32', + true, + false, + ], + 'Hash: joaat' => [ + 'joaat', + true, + false, + ], + 'Hash: ripemd160' => [ + 'ripemd160', + true, + true, + ], + 'Hash: sha256' => [ + 'sha256', + true, + true, + ], + 'Hash: invalid' => [ + 'invalid', + false, + false + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::isValidHashType + * @covers ::isValidHashHmacType + * @dataProvider providerHashTypes + * @testdox check if $hash_type is valid for hash $hash_ok and hash hmac $hash_hmac_ok [$_dataName] + * + * @param string $hash_type + * @param bool $hash_ok + * @param bool $hash_hmac_ok + * @return void + */ + public function testIsValidHashAndHashHmacTypes(string $hash_type, bool $hash_ok, bool $hash_hmac_ok): void + { + $this->assertEquals( + $hash_ok, + \CoreLibs\Create\Hash::isValidHashType($hash_type), + 'hash valid' + ); + $this->assertEquals( + $hash_hmac_ok, + \CoreLibs\Create\Hash::isValidHashHmacType($hash_type), + 'hash hmac valid' + ); + } } // __END__ diff --git a/www/admin/class_test.hash.php b/www/admin/class_test.hash.php index 0d78175d..34ae3830 100644 --- a/www/admin/class_test.hash.php +++ b/www/admin/class_test.hash.php @@ -19,6 +19,7 @@ $LOG_FILE_ID = 'classTest-hash'; ob_end_flush(); use CoreLibs\Create\Hash; +use CoreLibs\Security\CreateKey; $log = new CoreLibs\Logging\Logging([ 'log_folder' => BASE . LOG, @@ -53,10 +54,14 @@ print "U-S::__CRC32B: $to_crc: " . Hash::__crc32b($to_crc) . "
"; echo "
"; $text = 'Some String Text'; +// $text = 'any string'; $type = 'crc32b'; print "Hash: " . $type . ": " . hash($type, $text) . "
"; // print "Class (old): " . $type . ": " . Hash::__hash($text, $type) . "
"; print "Class (new): " . $type . ": " . Hash::hash($text, $type) . "
"; +print "Class: sha256: " . Hash::hash($text) . "
"; +$type = 'ripemd160'; +print "Class: " . $type . ": " . Hash::hash($text, $type) . "
"; echo "
"; print "CURRENT STANDARD_HASH_SHORT: " . Hash::STANDARD_HASH_SHORT . "
"; @@ -66,6 +71,31 @@ print "HASH SHORT: " . $to_crc . ": " . Hash::hashShort($to_crc) . "
"; print "HASH LONG: " . $to_crc . ": " . Hash::hashLong($to_crc) . "
"; print "HASH DEFAULT: " . $to_crc . ": " . Hash::hashStd($to_crc) . "
"; +echo "
"; +$key = CreateKey::generateRandomKey(); +$key = "FIX KEY"; +print "Secret Key: " . $key . "
"; +print "HASHMAC DEFAULT (fix): " . $to_crc . ": " . Hash::hashHmac($to_crc, $key) . "
"; +$key = CreateKey::generateRandomKey(); +print "Secret Key: " . $key . "
"; +print "HASHMAC DEFAULT (random): " . $to_crc . ": " . Hash::hashHmac($to_crc, $key) . "
"; + +echo "
"; +$hash_types = ['crc32b', 'sha256', 'invalid']; +foreach ($hash_types as $hash_type) { + echo "Checking $hash_type:
"; + if (Hash::isValidHashType($hash_type)) { + echo "hash type: $hash_type is valid
"; + } else { + echo "hash type: $hash_type is INVALID
"; + } + if (Hash::isValidHashHmacType($hash_type)) { + echo "hash hmac type: $hash_type is valid
"; + } else { + echo "hash hmac type: $hash_type is INVALID
"; + } +} + // print "UNIQU ID SHORT : " . Hash::__uniqId() . "
"; // print "UNIQU ID LONG : " . Hash::__uniqIdLong() . "
"; diff --git a/www/lib/CoreLibs/Create/Hash.php b/www/lib/CoreLibs/Create/Hash.php index e408f7d3..9ed734db 100644 --- a/www/lib/CoreLibs/Create/Hash.php +++ b/www/lib/CoreLibs/Create/Hash.php @@ -49,7 +49,7 @@ class Hash * replacement for __crc32b call * * @param string $string string to hash - * @param bool $use_sha use sha1 instead of crc32b (default false) + * @param bool $use_sha [default=false] use sha1 instead of crc32b * @return string hash of the string * @deprecated use __crc32b() for drop in replacement with default, or sha1Short() for use sha true */ @@ -81,7 +81,7 @@ class Hash * all that create 8 char long hashes * * @param string $string string to hash - * @param string $hash_type hash type (default adler32) + * @param string $hash_type [default=STANDARD_HASH_SHORT] hash type (default adler32) * @return string hash of the string * @deprecated use hashShort() of short hashes with adler 32 or hash() for other hash types */ @@ -92,12 +92,40 @@ class Hash return self::hash($string, $hash_type); } + /** + * check if hash type is valid, returns false if not + * + * @param string $hash_type + * @return bool + */ + public static function isValidHashType(string $hash_type): bool + { + if (!in_array($hash_type, hash_algos())) { + return false; + } + return true; + } + + /** + * check if hash hmac type is valid, returns false if not + * + * @param string $hash_hmac_type + * @return bool + */ + public static function isValidHashHmacType(string $hash_hmac_type): bool + { + if (!in_array($hash_hmac_type, hash_hmac_algos())) { + return false; + } + return true; + } + /** * creates a hash over string if any valid hash given. * if no hash type set use sha256 * - * @param string $string string to ash - * @param string $hash_type hash type (default sha256) + * @param string $string string to hash + * @param string $hash_type [default=STANDARD_HASH] hash type (default sha256) * @return string hash of the string */ public static function hash( @@ -108,12 +136,36 @@ class Hash empty($hash_type) || !in_array($hash_type, hash_algos()) ) { - // fallback to default hash type if none set or invalid + // fallback to default hash type if empty or invalid $hash_type = self::STANDARD_HASH; } return hash($hash_type, $string); } + /** + * creates a hash mac key + * + * @param string $string string to hash mac + * @param string $key key to use + * @param string $hash_type [default=STANDARD_HASH] + * @return string hash mac string + */ + public static function hashHmac( + string $string, + #[\SensitiveParameter] + string $key, + string $hash_type = self::STANDARD_HASH + ): string { + if ( + empty($hash_type) || + !in_array($hash_type, hash_hmac_algos()) + ) { + // fallback to default hash type if e or invalid + $hash_type = self::STANDARD_HASH; + } + return hash_hmac($hash_type, $string, $key); + } + /** * short hash with max length of 8, uses adler32 * From d09c20ff9d6c1bf97f071cf80292df9f1d7a7eb4 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 7 Apr 2025 09:09:45 +0900 Subject: [PATCH 091/105] hash test page update --- www/admin/class_test.hash.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/www/admin/class_test.hash.php b/www/admin/class_test.hash.php index 34ae3830..bc4d7853 100644 --- a/www/admin/class_test.hash.php +++ b/www/admin/class_test.hash.php @@ -44,10 +44,17 @@ print "S::__CRC32B: $to_crc: " . Hash::__crc32b($to_crc) . "
"; print "S::hashShort(__sha1Short replace): $to_crc: " . Hash::hashShort($to_crc) . "
"; // print "S::__SHA1SHORT(on): $to_crc: " . Hash::__sha1short($to_crc, true) . "
"; print "S::sha1Short(__sha1Short replace): $to_crc: " . Hash::sha1Short($to_crc) . "
"; -print "S::__hash(d): " . $to_crc . "/" - . Hash::STANDARD_HASH_SHORT . ": " . $hash_class::__hash($to_crc) . "
"; -foreach (['adler32', 'fnv132', 'fnv1a32', 'joaat', 'sha512'] as $__hash_c) { - print "S::__hash($__hash_c): $to_crc: " . $hash_class::__hash($to_crc, $__hash_c) . "
"; +// print "S::__hash(d): " . $to_crc . "/" +// . Hash::STANDARD_HASH_SHORT . ": " . $hash_class::__hash($to_crc) . "
"; +$to_crc_list = [ + 'Some text block', + 'Some String Text', + 'any string', +]; +foreach ($to_crc_list as $__to_crc) { + foreach (['adler32', 'fnv132', 'fnv1a32', 'joaat', 'ripemd160', 'sha256', 'sha512'] as $__hash_c) { + print "Hash::hash($__hash_c): $__to_crc: " . Hash::hash($to_crc, $__hash_c) . "
"; + } } // static use print "U-S::__CRC32B: $to_crc: " . Hash::__crc32b($to_crc) . "
"; @@ -59,9 +66,6 @@ $type = 'crc32b'; print "Hash: " . $type . ": " . hash($type, $text) . "
"; // print "Class (old): " . $type . ": " . Hash::__hash($text, $type) . "
"; print "Class (new): " . $type . ": " . Hash::hash($text, $type) . "
"; -print "Class: sha256: " . Hash::hash($text) . "
"; -$type = 'ripemd160'; -print "Class: " . $type . ": " . Hash::hash($text, $type) . "
"; echo "
"; print "CURRENT STANDARD_HASH_SHORT: " . Hash::STANDARD_HASH_SHORT . "
"; From 531229e8b7e1fb6ed4d7d1ca0aee71e9db096f58 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 7 Apr 2025 12:05:06 +0900 Subject: [PATCH 092/105] Add DB Encryption tests --- www/admin/class_test.db.encryption.php | 125 +++++++++++++++++++++++++ www/admin/class_test.php | 1 + 2 files changed, 126 insertions(+) create mode 100644 www/admin/class_test.db.encryption.php diff --git a/www/admin/class_test.db.encryption.php b/www/admin/class_test.db.encryption.php new file mode 100644 index 00000000..6d7701e2 --- /dev/null +++ b/www/admin/class_test.db.encryption.php @@ -0,0 +1,125 @@ + BASE . LOG, + 'log_file_id' => $LOG_FILE_ID, + 'log_per_date' => true, +]); +// db connection and attach logger +$db = new CoreLibs\DB\IO(DB_CONFIG, $log); +$db->log->debug('START', '=============================>'); + +$PAGE_NAME = 'TEST CLASS: DB QUERY ENCRYPTION'; +print ""; +print "" . $PAGE_NAME . ""; +print ""; +print ''; +print '

' . $PAGE_NAME . '

'; + +// encryption key +$key = CreateKey::generateRandomKey(); +print "Secret Key: " . $key . "
"; + +// test text +$text_string = "I a some deep secret"; +// +$crypt = new SymmetricEncryption($key); +$encrypted = $crypt->encrypt($text_string); +$string_hashed = Hash::hashStd($text_string); +$string_hmac = Hash::hashHmac($text_string, $key); +$decrypted = $crypt->decrypt($encrypted); + +print "String: " . $text_string . "
"; +print "Encrypted: " . $encrypted . "
"; +print "Hashed: " . $string_hashed . "
"; +print "Hmac: " . $string_hmac . "
"; + +$db->dbExecParams( + <<dbGetReturningExt('cuuid'); +print "INSERTED: $cuuid
"; +print "LAST ERROR: " . $db->dbGetLastError(true) . "
"; + +// read back +$res = $db->dbReturnRowParams( + <<" . Support::prAr($res) . "

"; + +// do compare + +print ""; + +// __END__ diff --git a/www/admin/class_test.php b/www/admin/class_test.php index 8772e07b..0e2d6ad4 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -95,6 +95,7 @@ $test_files = [ 'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn', 'class_test.db.single.php' => 'Class Test: DB single query tests', 'class_test.db.convert-placeholder.php' => 'Class Test: DB convert placeholder', + 'class_test.db.encryption.php' => 'Class Test: DB pgcrypto', 'class_test.convert.colors.php' => 'Class Test: CONVERT COLORS', 'class_test.check.colors.php' => 'Class Test: CHECK COLORS', 'class_test.mime.php' => 'Class Test: MIME', From 2d30d1d160c61ba7b7aad6506e9942197a6b9439 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 7 Apr 2025 17:27:13 +0900 Subject: [PATCH 093/105] Rewrite DB param lookup * Correct wrong comment lookup * simplify regex by excluding comment and string blocks before * simpler lookup for each type * update checks for more tests for various special cases In DB IO * add a function to return all placeholders found in a query * only numbered parameters are looked up --- 4dev/tests/DB/CoreLibsDBIOTest.php | 47 ++-- .../class_test.db.convert-placeholder.php | 55 ++++- www/admin/class_test.db.encryption.php | 2 +- www/admin/class_test.db.query-placeholder.php | 4 +- www/lib/CoreLibs/DB/IO.php | 11 + www/lib/CoreLibs/DB/SQL/PgSQL.php | 21 +- .../DB/Support/ConvertPlaceholder.php | 201 +++++++++--------- 7 files changed, 208 insertions(+), 133 deletions(-) diff --git a/4dev/tests/DB/CoreLibsDBIOTest.php b/4dev/tests/DB/CoreLibsDBIOTest.php index d85de03f..d4e62d90 100644 --- a/4dev/tests/DB/CoreLibsDBIOTest.php +++ b/4dev/tests/DB/CoreLibsDBIOTest.php @@ -4744,7 +4744,7 @@ final class CoreLibsDBIOTest extends TestCase $res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); // all hast to be string foreach ($res as $key => $value) { - $this->assertIsString($value, 'Aseert string for column: ' . $key); + $this->assertIsString($value, 'Assert string for column: ' . $key); } // convert base only $db->dbSetConvertFlag(Convert::on); @@ -4757,10 +4757,10 @@ final class CoreLibsDBIOTest extends TestCase } switch ($type_layout[$name]) { case 'int': - $this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); + $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name); break; default: - $this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); + $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name); break; } } @@ -4774,13 +4774,13 @@ final class CoreLibsDBIOTest extends TestCase } switch ($type_layout[$name]) { case 'int': - $this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); + $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name); break; case 'float': - $this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); + $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name); break; default: - $this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); + $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name); break; } } @@ -4794,17 +4794,17 @@ final class CoreLibsDBIOTest extends TestCase } switch ($type_layout[$name]) { case 'int': - $this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); + $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name); break; case 'float': - $this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); + $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name); break; case 'json': case 'jsonb': - $this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name); + $this->assertIsArray($value, 'Assert array for column: ' . $key . '/' . $name); break; default: - $this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); + $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name); break; } } @@ -4818,25 +4818,25 @@ final class CoreLibsDBIOTest extends TestCase } switch ($type_layout[$name]) { case 'int': - $this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); + $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name); break; case 'float': - $this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); + $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name); break; case 'json': case 'jsonb': - $this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name); + $this->assertIsArray($value, 'Assert array for column: ' . $key . '/' . $name); break; case 'bytea': // for hex types it must not start with \x $this->assertStringStartsNotWith( '\x', $value, - 'Aseert bytes not starts with \x for column: ' . $key . '/' . $name + 'Assert bytes not starts with \x for column: ' . $key . '/' . $name ); break; default: - $this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); + $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name); break; } } @@ -5235,6 +5235,9 @@ final class CoreLibsDBIOTest extends TestCase $3 -- comment 3 , $4 + -- ignore $5, $6 + -- $7, $8 + -- digest($9, 10) ) SQL, 'count' => 4, @@ -5305,6 +5308,20 @@ final class CoreLibsDBIOTest extends TestCase SQL, 'count' => 2, 'convert' => false, + ], + // a text string with escaped quite + 'text string, with escaped quote' => [ + 'query' => << 2, + 'convert' => false, ] ]; } diff --git a/www/admin/class_test.db.convert-placeholder.php b/www/admin/class_test.db.convert-placeholder.php index 6504326f..8257805c 100644 --- a/www/admin/class_test.db.convert-placeholder.php +++ b/www/admin/class_test.db.convert-placeholder.php @@ -21,6 +21,7 @@ ob_end_flush(); use CoreLibs\Debug\Support; use CoreLibs\DB\Support\ConvertPlaceholder; +use CoreLibs\Convert\Html; $log = new CoreLibs\Logging\Logging([ 'log_folder' => BASE . LOG, @@ -38,10 +39,12 @@ print '

' . $PAGE_NAME . '

'; print "LOGFILE NAME: " . $log->getLogFile() . "
"; print "LOGFILE ID: " . $log->getLogFileId() . "
"; -print "Lookup Regex:
" . ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS . "
"; -print "Replace Named Regex:
" . ConvertPlaceholder::REGEX_REPLACE_NAMED . "
"; -print "Replace Named Regex:
" . ConvertPlaceholder::REGEX_REPLACE_QUESTION_MARK . "
"; -print "Replace Named Regex:
" . ConvertPlaceholder::REGEX_REPLACE_NUMBERED . "
"; +print "Lookup Regex:
" . Html::htmlent(ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS) . "
"; +print "Lookup Numbered Regex:
" . Html::htmlent(ConvertPlaceholder::REGEX_LOOKUP_NUMBERED) . "
"; +print "Replace Named Regex:
" . Html::htmlent(ConvertPlaceholder::REGEX_REPLACE_NAMED) . "
"; +print "Replace Question Mark Regex:
"
+	. Html::htmlent(ConvertPlaceholder::REGEX_REPLACE_QUESTION_MARK) . "
"; +print "Replace Numbered Regex:
" . Html::htmlent(ConvertPlaceholder::REGEX_REPLACE_NUMBERED) . "
"; $uniqid = \CoreLibs\Create\Uids::uniqIdShort(); // $binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: ''); @@ -91,40 +94,63 @@ RETURNING some_binary SQL; -print "[ALL] Convert: " +print "[ALL] Convert: " . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . "
"; echo "
"; $query = "SELECT foo FROM bar WHERE baz = :baz AND buz = :baz AND biz = :biz AND boz = :bez"; $params = [':baz' => 'SETBAZ', ':bez' => 'SETBEZ', ':biz' => 'SETBIZ']; -print "[NO PARAMS] Convert: " +print "[NO PARAMS] Convert: " . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . "
"; echo "
"; $query = "SELECT foo FROM bar WHERE baz = :baz AND buz = :baz AND biz = :biz AND boz = :bez"; $params = null; -print "[NO PARAMS] Convert: " +print "[NO PARAMS] Convert: " . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . "
"; echo "
"; $query = "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar <> :row_varchar"; $params = null; -print "[NO PARAMS] Convert: " +print "[NO PARAMS] Convert: " . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . "
"; echo "
"; $query = "SELECT row_varchar, row_varchar_literal, row_int, row_date FROM table_with_primary_key"; $params = null; -print "[NO PARAMS] TEST: " +print "[NO PARAMS] TEST: " . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . "
"; echo "
"; -print "[P-CONV]: " +$query = <<[All the same params] TEST: " + . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) + . "
"; +echo "
"; + +$query = << 1]; +print "[: param] TEST: " + . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) + . "
"; +echo "
"; + +print "[P-CONV]: " . Support::printAr( ConvertPlaceholder::updateParamList([ 'original' => [ @@ -186,6 +212,13 @@ SQL, 'params' => [\CoreLibs\Create\Uids::uniqIdShort(), 'string A-1', 1234], 'direction' => 'pg', ], + 'b?' => [ + 'query' => << [1234], + 'direction' => 'pg', + ], 'b:' => [ 'query' => << $data) { $query = $data['query']; $params = $data['params']; $direction = $data['direction']; - print "[$info] Convert: " + print "[$info] Convert: " . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params, $direction)) . "
"; echo "
"; diff --git a/www/admin/class_test.db.encryption.php b/www/admin/class_test.db.encryption.php index 6d7701e2..7a0a827d 100644 --- a/www/admin/class_test.db.encryption.php +++ b/www/admin/class_test.db.encryption.php @@ -91,7 +91,7 @@ $db->dbExecParams( ] ); $cuuid = $db->dbGetReturningExt('cuuid'); -print "INSERTED: $cuuid
"; +print "INSERTED: " . print_r($cuuid, true) . "
"; print "LAST ERROR: " . $db->dbGetLastError(true) . "
"; // read back diff --git a/www/admin/class_test.db.query-placeholder.php b/www/admin/class_test.db.query-placeholder.php index c77cff6a..0516ed05 100644 --- a/www/admin/class_test.db.query-placeholder.php +++ b/www/admin/class_test.db.query-placeholder.php @@ -54,7 +54,7 @@ if (($dbh = $db->dbGetDbh()) instanceof \PgSql\Connection) { print "NO DB HANDLER
"; } // REGEX for placeholder count -print "Placeholder regex:
" . CoreLibs\DB\Support\ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS . "
"; +print "Placeholder lookup regex:
" . CoreLibs\DB\Support\ConvertPlaceholder::REGEX_LOOKUP_NUMBERED . "
"; // turn on debug replace for placeholders $db->dbSetDebugReplacePlaceholder(true); @@ -148,6 +148,7 @@ RETURNING bigint_a, number_real, number_double, numeric_3, uuid_var SQL; +print "Placeholders:
" . print_r($db->dbGetQueryParamPlaceholders($query_insert), true) . "
";
 $status = $db->dbExecParams($query_insert, $query_params);
 echo "*
"; echo "INSERT ALL COLUMN TYPES: " @@ -326,6 +327,7 @@ SQL, ) { print "RES: " . Support::prAr($res) . "
"; } +print "PL: " . Support::PrAr($db->dbGetPlaceholderConverted()) . "
"; print "ERROR: " . $db->dbGetLastError(true) . "
"; print ""; diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index a10a089b..9f60d38a 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -4283,6 +4283,17 @@ class IO return $this->field_names[$pos] ?? false; } + /** + * get all the $ placeholders + * + * @param string $query + * @return array + */ + public function dbGetQueryParamPlaceholders(string $query): array + { + return $this->db_functions->__dbGetQueryParams($query); + } + /** * Return a field type for a field name or pos, * will return false if field is not found in list diff --git a/www/lib/CoreLibs/DB/SQL/PgSQL.php b/www/lib/CoreLibs/DB/SQL/PgSQL.php index a343c8bc..352b6a5e 100644 --- a/www/lib/CoreLibs/DB/SQL/PgSQL.php +++ b/www/lib/CoreLibs/DB/SQL/PgSQL.php @@ -978,12 +978,12 @@ class PgSQL implements Interface\SqlFunctions } /** - * Count placeholder queries. $ only + * Get the all the $ params, unique list * * @param string $query - * @return int + * @return array */ - public function __dbCountQueryParams(string $query): int + public function __dbGetQueryParams(string $query): array { $matches = []; // regex for params: only stand alone $number allowed @@ -998,11 +998,22 @@ class PgSQL implements Interface\SqlFunctions // Matches in 1:, must be array_filtered to remove empty, count with array_unique // Regex located in the ConvertPlaceholder class preg_match_all( - ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS, + ConvertPlaceholder::REGEX_LOOKUP_NUMBERED, $query, $matches ); - return count(array_unique(array_filter($matches[3]))); + return array_unique(array_filter($matches[ConvertPlaceholder::MATCHING_POS])); + } + + /** + * Count placeholder queries. $ only + * + * @param string $query + * @return int + */ + public function __dbCountQueryParams(string $query): int + { + return count($this->__dbGetQueryParams($query)); } } diff --git a/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php b/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php index 484b7828..533b6a3a 100644 --- a/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php +++ b/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php @@ -14,76 +14,57 @@ namespace CoreLibs\DB\Support; class ConvertPlaceholder { - // NOTE for missing: range */+ are not iplemented in the regex below, but - is for now - // NOTE some combinations are allowed, but the query will fail before this - /** @var string split regex, entries before $ group */ - private const PATTERN_QUERY_SPLIT = - '\?\?|' // UNKNOWN: double ??, is this to avoid something? - . '[\(,]|' // for ',' and '(' mostly in INSERT or ANY() - . '[<>=]|' // general set for <, >, = in any query with any combination - . '\^@|' // text search for start from text with ^@ - . '\|\||' // concats two elements - . '&&|' // array overlap - . '\-\|\-|' // range overlap for array - . '[^-]-{1}|' // single -, used in JSON too - . '->|->>|#>|#>>|@>|<@|@@|@\?|\?{1}|\?\||\?&|#-|' // JSON searches, Array searchs, etc - . 'THEN|ELSE' // command parts (CASE) - ; - /** @var string the main regex including the pattern query split */ - private const PATTERN_ELEMENT = '(?:\'.*?\')?\s*(?:' . self::PATTERN_QUERY_SPLIT . ')\s*'; + /** @var string text block in SQL, single quited + * Note that does not include $$..$$ strings or anything with token name or nested ones + */ + private const PATTERN_TEXT_BLOCK_SINGLE_QUOTE = '(?:\'(?:[^\'\\\\]|\\\\.)*\')'; + /** @var string text block in SQL, dollar quoted + * NOTE: if this is added everything shifts by one lookup number + */ + private const PATTERN_TEXT_BLOCK_DOLLAR = '(?:\$([^$]*)\$.*?\$\1\$)'; /** @var string comment regex - * anything that starts with -- and ends with a line break but any character that is not line break inbetween */ - private const PATTERN_COMMENT = '(?:\-\-[^\r\n]*?\r?\n)*\s*'; - /** @var string parts to ignore in the SQL */ - private const PATTERN_IGNORE = - // digit -> ignore - '\d+|' - // other string -> ignore - . '(?:\'.*?\')|'; - /** @var string named parameters */ - private const PATTERN_NAMED = '(:\w+)'; - /** @var string question mark parameters */ - private const PATTERN_QUESTION_MARK = '(?:(?:\?\?)?\s*(\?{1}))'; - /** @var string numbered parameters */ + * anything that starts with -- and ends with a line break but any character that is not line break inbetween + * this is the FIRST thing in the line and will skip any further lookups */ + private const PATTERN_COMMENT = '(?:\-\-[^\r\n]*?\r?\n)'; + // below are the params lookups + /** @var string named parameters, must start with single : */ + private const PATTERN_NAMED = '((? add line break to matches in "." . '/s'; + /** @var string lookup for only numbered placeholders */ + public const REGEX_LOOKUP_NUMBERED = '/' + . self::PATTERN_COMMENT . '|' + . self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|' + . self::PATTERN_TEXT_BLOCK_DOLLAR . '|' + // match for replace part + . '(?:' + // $n numbered part (\PG php) [1] + . self::PATTERN_NUMBERED + // end match + . ')' + . '/s'; + /** @var int position for regex in full placeholder lookup: named */ + public const LOOOKUP_NAMED_POS = 2; + /** @var int position for regex in full placeholder lookup: question mark */ + public const LOOOKUP_QUESTION_MARK_POS = 3; + /** @var int position for regex in full placeholder lookup: numbered */ + public const LOOOKUP_NUMBERED_POS = 4; + /** @var int matches position for replacement and single lookup */ + public const MATCHING_POS = 2; /** * Convert PDO type query with placeholders to \PG style and vica versa @@ -132,11 +133,12 @@ class ConvertPlaceholder $found = -1; } /** @var array 1: named */ - $named_matches = array_filter($matches[1]); + $named_matches = array_filter($matches[self::LOOOKUP_NAMED_POS]); /** @var array 2: open ? */ - $qmark_matches = array_filter($matches[2]); + $qmark_matches = array_filter($matches[self::LOOOKUP_QUESTION_MARK_POS]); /** @var array 3: $n matches */ - $numbered_matches = array_filter($matches[3]); + $numbered_matches = array_filter($matches[self::LOOOKUP_NUMBERED_POS]); + // print "**MATCHES**:
" . print_r($matches, true) . "
"; // count matches $count_named = count(array_unique($named_matches)); $count_qmark = count($qmark_matches); @@ -235,38 +237,37 @@ class ConvertPlaceholder $empty_params = $converted_placeholders['original']['empty_params']; switch ($converted_placeholders['type']) { case 'named': - // 0: full - // 0: full - // 1: pre part - // 2: keep part UNLESS '3' is set - // 3: replace part :named + // 1: replace part :named $pos = 0; $query_new = preg_replace_callback( self::REGEX_REPLACE_NAMED, function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) { - // only count up if $match[3] is not yet in lookup table - if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) { + if (!isset($matches[self::MATCHING_POS])) { + throw new \RuntimeException( + 'Cannot lookup ' . self::MATCHING_POS . ' in matches list', + 209 + ); + } + $match = $matches[self::MATCHING_POS]; + // only count up if $match[1] is not yet in lookup table + if (empty($params_lookup[$match])) { $pos++; - $params_lookup[$matches[3]] = '$' . $pos; + $params_lookup[$match] = '$' . $pos; // skip params setup if param list is empty if (!$empty_params) { - $params_new[] = $params[$matches[3]] ?? + $params_new[] = $params[$match] ?? throw new \RuntimeException( - 'Cannot lookup ' . $matches[3] . ' in params list', + 'Cannot lookup ' . $match . ' in params list', 210 ); } } // add the connectors back (1), and the data sets only if no replacement will be done - return $matches[1] . ( - empty($matches[3]) ? - $matches[2] : - $params_lookup[$matches[3]] ?? - throw new \RuntimeException( - 'Cannot lookup ' . $matches[3] . ' in params lookup list', - 211 - ) - ); + return $params_lookup[$match] ?? + throw new \RuntimeException( + 'Cannot lookup ' . $match . ' in params lookup list', + 211 + ); }, $converted_placeholders['original']['query'] ); @@ -276,61 +277,61 @@ class ConvertPlaceholder // order and data stays the same $params_new = $params ?? []; } - // 0: full - // 1: pre part - // 2: keep part UNLESS '3' is set - // 3: replace part ? + // 1: replace part ? $pos = 0; $query_new = preg_replace_callback( self::REGEX_REPLACE_QUESTION_MARK, function ($matches) use (&$pos, &$params_lookup) { + if (!isset($matches[self::MATCHING_POS])) { + throw new \RuntimeException( + 'Cannot lookup ' . self::MATCHING_POS . ' in matches list', + 229 + ); + } + $match = $matches[self::MATCHING_POS]; // only count pos up for actual replacements we will do - if (!empty($matches[3])) { + if (!empty($match)) { $pos++; $params_lookup[] = '$' . $pos; } // add the connectors back (1), and the data sets only if no replacement will be done - return $matches[1] . ( - empty($matches[3]) ? - $matches[2] : - '$' . $pos - ); + return '$' . $pos; }, $converted_placeholders['original']['query'] ); break; case 'numbered': - // 0: full - // 1: pre part - // 2: keep part UNLESS '3' is set - // 3: replace part $numbered + // 1: replace part $numbered $pos = 0; $query_new = preg_replace_callback( self::REGEX_REPLACE_NUMBERED, function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) { - // only count up if $match[3] is not yet in lookup table - if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) { + if (!isset($matches[self::MATCHING_POS])) { + throw new \RuntimeException( + 'Cannot lookup ' . self::MATCHING_POS . ' in matches list', + 239 + ); + } + $match = $matches[self::MATCHING_POS]; + // only count up if $match[1] is not yet in lookup table + if (empty($params_lookup[$match])) { $pos++; - $params_lookup[$matches[3]] = ':' . $pos . '_named'; + $params_lookup[$match] = ':' . $pos . '_named'; // skip params setup if param list is empty if (!$empty_params) { $params_new[] = $params[($pos - 1)] ?? throw new \RuntimeException( 'Cannot lookup ' . ($pos - 1) . ' in params list', - 220 + 230 ); } } // add the connectors back (1), and the data sets only if no replacement will be done - return $matches[1] . ( - empty($matches[3]) ? - $matches[2] : - $params_lookup[$matches[3]] ?? - throw new \RuntimeException( - 'Cannot lookup ' . $matches[3] . ' in params lookup list', - 221 - ) - ); + return $params_lookup[$match] ?? + throw new \RuntimeException( + 'Cannot lookup ' . $match . ' in params lookup list', + 231 + ); }, $converted_placeholders['original']['query'] ); From 37367db878a324e5b90f7e4bfec4a66c1518ebc9 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 7 Apr 2025 19:44:18 +0900 Subject: [PATCH 094/105] Fix regex for $$ PostgresSQL string in convert placeholder --- 4dev/tests/DB/CoreLibsDBIOTest.php | 36 +++++++++++++++++++ .../DB/Support/ConvertPlaceholder.php | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/4dev/tests/DB/CoreLibsDBIOTest.php b/4dev/tests/DB/CoreLibsDBIOTest.php index d4e62d90..4ed9cabd 100644 --- a/4dev/tests/DB/CoreLibsDBIOTest.php +++ b/4dev/tests/DB/CoreLibsDBIOTest.php @@ -135,6 +135,7 @@ final class CoreLibsDBIOTest extends TestCase } // check if they already exist, drop them if ($db->dbShowTableMetaData('table_with_primary_key') !== false) { + $db->dbExec("CREATE EXTENSION IF NOT EXISTS pgcrypto"); $db->dbExec("DROP TABLE table_with_primary_key"); $db->dbExec("DROP TABLE table_without_primary_key"); $db->dbExec("DROP TABLE test_meta"); @@ -5309,6 +5310,38 @@ final class CoreLibsDBIOTest extends TestCase 'count' => 2, 'convert' => false, ], + // special $$ string case + 'text string, with $ placehoders that could be seen as $$ string' => [ + 'query' => << 6, + 'convert' => false, + ], + // NOTE, in SQL heredoc we cannot write $$ strings parts + 'text string, with $ placehoders are in $$ strings' => [ + 'query' => ' + SELECT row_int + FROM table_with_primary_key + WHERE + row_varchar = $$some string$$ OR + row_varchar = $tag$some string$tag$ OR + row_varchar = $btag$some $1 string$btag$ OR + row_varchar = $btag$some $1 $subtag$ something $subtag$string$btag$ OR + row_varchar = $1 + ', + 'count' => 1, + 'convert' => false, + ], // a text string with escaped quite 'text string, with escaped quote' => [ 'query' => << false, ] ]; + $string = << Date: Wed, 9 Apr 2025 11:35:02 +0900 Subject: [PATCH 095/105] Update DB IO for query hash storage and parameter count The parameter count methods in the PgSQL class have changed - the function returns a unique list of $ parameters The count is now done in the DB IO part where it counts over the unique array Query hash is stored like the query for the current run one (reset on dbExec call). The method to create the hash is renamed to dbBuildQueryHash instead of "Get". The dbGetQueryHash function now just returns the last set query hash. There is a matching dbResetQueryHash for unsetting the query hash. --- 4dev/tests/DB/CoreLibsDBIOTest.php | 4 +- www/admin/class_test.db.encryption.php | 26 ++++++++-- www/lib/CoreLibs/DB/IO.php | 51 ++++++++++++++----- .../DB/SQL/Interface/SqlFunctions.php | 4 +- www/lib/CoreLibs/DB/SQL/PgSQL.php | 13 +---- 5 files changed, 65 insertions(+), 33 deletions(-) diff --git a/4dev/tests/DB/CoreLibsDBIOTest.php b/4dev/tests/DB/CoreLibsDBIOTest.php index 4ed9cabd..f9e66c6a 100644 --- a/4dev/tests/DB/CoreLibsDBIOTest.php +++ b/4dev/tests/DB/CoreLibsDBIOTest.php @@ -5009,8 +5009,8 @@ final class CoreLibsDBIOTest extends TestCase ) ), ($params === null ? - $db->dbGetQueryHash($query) : - $db->dbGetQueryHash($query, $params) + $db->dbBuildQueryHash($query) : + $db->dbBuildQueryHash($query, $params) ), 'Failed assertdbGetQueryHash ' ); diff --git a/www/admin/class_test.db.encryption.php b/www/admin/class_test.db.encryption.php index 7a0a827d..0d8da1cb 100644 --- a/www/admin/class_test.db.encryption.php +++ b/www/admin/class_test.db.encryption.php @@ -42,7 +42,10 @@ print ''; print '

' . $PAGE_NAME . '

'; // encryption key -$key = CreateKey::generateRandomKey(); +$key_new = CreateKey::generateRandomKey(); +print "Secret Key NEW: " . $key_new . "
"; +// for reproducable test results +$key = 'e475c19b9a3c8363feb06b51f5b73f1dc9b6f20757d4ab89509bf5cc70ed30ec'; print "Secret Key: " . $key . "
"; // test text @@ -105,20 +108,35 @@ $res = $db->dbReturnRowParams( -- in DB encryption pg_digest_bytea, pg_digest_text, pg_hmac_bytea, pg_hmac_text, - pg_crypt_bytea, pg_crypt_text + pg_crypt_bytea, pg_crypt_text, + encode(pg_crypt_bytea, 'hex') AS pg_crypt_bytea_hex, + pgp_sym_decrypt(pg_crypt_bytea, $2) AS from_pg_crypt_bytea, + pgp_sym_decrypt(decode(pg_crypt_text, 'hex'), $2) AS from_pg_crypt_text FROM test_encryption WHERE cuuid = $1 SQL, [ - $cuuid + $cuuid, $key ] ); print "RES:
" . Support::prAr($res) . "

"; -// do compare +if ($res === false) { + echo "Failed to run query
"; +} else { + if (hash_equals($string_hashed, $res['pg_digest_text'])) { + print "libsodium and pgcrypto hash match
"; + } + if (hash_equals($string_hmac, $res['pg_hmac_text'])) { + print "libsodium and pgcrypto hash hmac match
"; + } +} + + +// do compare for PHP and pgcrypto settings print ""; diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index 9f60d38a..d66a7d9c 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -303,6 +303,8 @@ class IO private string $query = ''; /** @var array current params for query */ private array $params = []; + /** @var string current hash build from query and params */ + private string $query_hash = ''; // if we do have a convert call, store the convert data in here, else it will be empty /** @var array{}|array{original:array{query:string,params:array},type:''|'named'|'numbered'|'question_mark',found:int,matches:array,params_lookup:array,query:string,params:array} */ private array $placeholder_converted = []; @@ -1319,7 +1321,7 @@ class IO */ private function __dbCountQueryParams(string $query): int { - return $this->db_functions->__dbCountQueryParams($query); + return count($this->db_functions->__dbGetQueryParams($query)); } /** @@ -1382,6 +1384,8 @@ class IO $this->query = $query; // current params $this->params = $params; + // empty on new + $this->query_hash = ''; // no query set if (empty($this->query)) { $this->__dbError(11); @@ -1441,7 +1445,7 @@ class IO $this->returning_id = true; } // import protection, hash needed - $query_hash = $this->dbGetQueryHash($this->query, $this->params); + $query_hash = $this->dbBuildQueryHash($this->query, $this->params); // QUERY PARAMS: run query params check and rewrite if ($this->dbGetConvertPlaceholder() === true) { try { @@ -1475,7 +1479,8 @@ class IO return false; } } - + // set query hash + $this->query_hash = $query_hash; // $this->debug('DB IO', 'Q: ' . $this->query . ', RETURN: ' . $this->returning_id); // for DEBUG, only on first time ;) $this->__dbDebug( @@ -1959,7 +1964,7 @@ class IO { // set start array if ($query) { - $array = $this->cursor_ext[$this->dbGetQueryHash($query)] ?? []; + $array = $this->cursor_ext[$this->dbBuildQueryHash($query)] ?? []; } else { $array = $this->cursor_ext; } @@ -2361,7 +2366,7 @@ class IO return false; } // create hash from query ... - $query_hash = $this->dbGetQueryHash($query, $params); + $query_hash = $this->dbBuildQueryHash($query, $params); // pre declare array if (!isset($this->cursor_ext[$query_hash])) { $this->cursor_ext[$query_hash] = [ @@ -2940,7 +2945,7 @@ class IO public function dbCacheReset(string $query, array $params = []): bool { $this->__dbErrorReset(); - $query_hash = $this->dbGetQueryHash($query, $params); + $query_hash = $this->dbBuildQueryHash($query, $params); // clears cache for this query if (empty($this->cursor_ext[$query_hash]['query'])) { $this->__dbWarning(18, context: [ @@ -2982,7 +2987,7 @@ class IO if ($query === null) { return $this->cursor_ext; } - $query_hash = $this->dbGetQueryHash($query, $params); + $query_hash = $this->dbBuildQueryHash($query, $params); if ( !empty($this->cursor_ext) && isset($this->cursor_ext[$query_hash]) @@ -3012,7 +3017,7 @@ class IO $this->__dbError(11); return false; } - $query_hash = $this->dbGetQueryHash($query, $params); + $query_hash = $this->dbBuildQueryHash($query, $params); if ( !empty($this->cursor_ext) && isset($this->cursor_ext[$query_hash]) @@ -3038,7 +3043,7 @@ class IO $this->__dbError(11); return false; } - $query_hash = $this->dbGetQueryHash($query, $params); + $query_hash = $this->dbBuildQueryHash($query, $params); if ( !empty($this->cursor_ext) && isset($this->cursor_ext[$query_hash]) @@ -3064,7 +3069,7 @@ class IO */ public function dbResetQueryCalled(string $query, array $params = []): void { - $this->query_called[$this->dbGetQueryHash($query, $params)] = 0; + $this->query_called[$this->dbBuildQueryHash($query, $params)] = 0; } /** @@ -3077,7 +3082,7 @@ class IO */ public function dbGetQueryCalled(string $query, array $params = []): int { - $query_hash = $this->dbGetQueryHash($query, $params); + $query_hash = $this->dbBuildQueryHash($query, $params); if (!empty($this->query_called[$query_hash])) { return $this->query_called[$query_hash]; } else { @@ -4046,7 +4051,7 @@ class IO } /** - * Returns hash for query + * Creates hash for query and parameters * Hash is used in all internal storage systems for return data * * @param string $query The query to create the hash from @@ -4054,7 +4059,7 @@ class IO * data to create a unique call one, optional * @return string Hash, as set by hash long */ - public function dbGetQueryHash(string $query, array $params = []): string + public function dbBuildQueryHash(string $query, array $params = []): string { return Hash::hashLong( $query . ( @@ -4104,6 +4109,26 @@ class IO $this->params = []; } + /** + * get the current set query hash + * + * @return string Current Query hash + */ + public function dbGetQueryHash(): string + { + return $this->query_hash; + } + + /** + * reset query hash + * + * @return void + */ + public function dbResetQueryHash(): void + { + $this->query_hash = ''; + } + /** * Returns the placeholder convert set or empty * diff --git a/www/lib/CoreLibs/DB/SQL/Interface/SqlFunctions.php b/www/lib/CoreLibs/DB/SQL/Interface/SqlFunctions.php index 8ccfa9a6..5dda8fa9 100644 --- a/www/lib/CoreLibs/DB/SQL/Interface/SqlFunctions.php +++ b/www/lib/CoreLibs/DB/SQL/Interface/SqlFunctions.php @@ -379,9 +379,9 @@ interface SqlFunctions * Undocumented function * * @param string $query - * @return int + * @return array */ - public function __dbCountQueryParams(string $query): int; + public function __dbGetQueryParams(string $query): array; } // __END__ diff --git a/www/lib/CoreLibs/DB/SQL/PgSQL.php b/www/lib/CoreLibs/DB/SQL/PgSQL.php index 352b6a5e..c05195c2 100644 --- a/www/lib/CoreLibs/DB/SQL/PgSQL.php +++ b/www/lib/CoreLibs/DB/SQL/PgSQL.php @@ -978,7 +978,7 @@ class PgSQL implements Interface\SqlFunctions } /** - * Get the all the $ params, unique list + * Get the all the $ params, as a unique list * * @param string $query * @return array @@ -1004,17 +1004,6 @@ class PgSQL implements Interface\SqlFunctions ); return array_unique(array_filter($matches[ConvertPlaceholder::MATCHING_POS])); } - - /** - * Count placeholder queries. $ only - * - * @param string $query - * @return int - */ - public function __dbCountQueryParams(string $query): int - { - return count($this->__dbGetQueryParams($query)); - } } // __END__ From 07e46c91ab45392c923286075148b75f400f98ad Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 14 Apr 2025 09:19:58 +0900 Subject: [PATCH 096/105] Add test decryption for pg crypto columns --- www/admin/class_test.db.encryption.php | 25 +++++++++++++++++++++++++ www/composer.json | 3 ++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/www/admin/class_test.db.encryption.php b/www/admin/class_test.db.encryption.php index 0d8da1cb..1287dd49 100644 --- a/www/admin/class_test.db.encryption.php +++ b/www/admin/class_test.db.encryption.php @@ -15,6 +15,8 @@ ob_start(); define('USE_DATABASE', true); // sample config require 'config.php'; +// for testing encryption compare +use OpenPGP\OpenPGP; // define log file id $LOG_FILE_ID = 'classTest-db-query-encryption'; ob_end_flush(); @@ -50,6 +52,7 @@ print "Secret Key: " . $key . "
"; // test text $text_string = "I a some deep secret"; +$text_string = "I a some deep secret ABC"; // $crypt = new SymmetricEncryption($key); $encrypted = $crypt->encrypt($text_string); @@ -134,6 +137,28 @@ if ($res === false) { print "libsodium and pgcrypto hash hmac match
"; } } +$encryptedMessage_template = <<getLiteralData()->getData(); + print "Pg decrypted PHP: " . $decrypted . "
"; + if ($decrypted == $text_string) { + print "Decryption worked
"; + } +} catch (\Exception $e) { + print "Error decrypting message: " . $e->getMessage() . "
"; +} // do compare for PHP and pgcrypto settings diff --git a/www/composer.json b/www/composer.json index 8fe948fd..d723baf3 100644 --- a/www/composer.json +++ b/www/composer.json @@ -24,6 +24,7 @@ "egrajp/smarty-extended": "^5.4", "php": ">=8.1", "gullevek/dotenv": "^2.0", - "psr/log": "^2.0 || ^3.0" + "psr/log": "^2.0 || ^3.0", + "php-privacy/openpgp": "^2.1" } } From 1cfdc45107fdf48ec16e6d4cbd0840904a3ae08f Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 15 Apr 2025 17:40:54 +0900 Subject: [PATCH 097/105] Fix edit user missing error example for login user id field --- www/lib/CoreLibs/Output/Form/Generate.php | 4 ++-- www/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/www/lib/CoreLibs/Output/Form/Generate.php b/www/lib/CoreLibs/Output/Form/Generate.php index 68e7a1df..b2262c70 100644 --- a/www/lib/CoreLibs/Output/Form/Generate.php +++ b/www/lib/CoreLibs/Output/Form/Generate.php @@ -1371,7 +1371,7 @@ class Generate ) { $this->msg .= sprintf( $this->l->__('Please enter a valid (%s) input for the %s Field!
'), - $this->dba->getTableArray()[$key]['error_example'], + $this->dba->getTableArray()[$key]['error_example'] ?? '[MISSING]', $this->dba->getTableArray()[$key]['output_name'] ); } @@ -2602,7 +2602,7 @@ class Generate } } // add lost error ones - $this->log->error('P: ' . $data['prefix'] . ', ' + $this->log->error('Prefix: ' . $data['prefix'] . ', ' . Support::prAr($_POST['ERROR'][$data['prefix']] ?? [])); if ($this->error && !empty($_POST['ERROR'][$data['prefix']])) { $prfx = $data['prefix']; // short diff --git a/www/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php b/www/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php index c51dbd77..8761c2d4 100644 --- a/www/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php +++ b/www/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php @@ -182,6 +182,7 @@ class EditUsers implements Interface\TableArraysInterface 'type' => 'text', 'error_check' => 'unique|custom', 'error_regex' => "/^[A-Za-z0-9]+$/", + 'error_example' => "ABCdef123", 'emptynull' => 1,'min_edit_acl' => '100', 'min_show_acl' => '100', ], From a66cc09095f950fa4b384c75f2da37addce6f4c8 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 15 Apr 2025 17:46:41 +0900 Subject: [PATCH 098/105] Fix phpstan problems in test db encryption file --- www/admin/class_test.db.encryption.php | 44 ++++++++++++-------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/www/admin/class_test.db.encryption.php b/www/admin/class_test.db.encryption.php index 1287dd49..6b5c6ed7 100644 --- a/www/admin/class_test.db.encryption.php +++ b/www/admin/class_test.db.encryption.php @@ -136,33 +136,31 @@ if ($res === false) { if (hash_equals($string_hmac, $res['pg_hmac_text'])) { print "libsodium and pgcrypto hash hmac match
"; } -} -$encryptedMessage_template = <<getLiteralData()->getData(); - print "Pg decrypted PHP: " . $decrypted . "
"; - if ($decrypted == $text_string) { - print "Decryption worked
"; + {BASE64} + -----END PGP MESSAGE----- + TEXT; + $base64_string = base64_encode(hex2bin($res['pg_crypt_text']) ?: ''); + $encryptedMessage = str_replace( + '{BASE64}', + $base64_string, + $encryptedMessage_template + ); + try { + $literalMessage = OpenPGP::decryptMessage($encryptedMessage, passwords: [$key]); + $decrypted = $literalMessage->getLiteralData()->getData(); + print "Pg decrypted PHP: " . $decrypted . "
"; + if ($decrypted == $text_string) { + print "Decryption worked
"; + } + } catch (\Exception $e) { + print "Error decrypting message: " . $e->getMessage() . "
"; } -} catch (\Exception $e) { - print "Error decrypting message: " . $e->getMessage() . "
"; } - -// do compare for PHP and pgcrypto settings - print ""; // __END__ From b18866077ea586e25bbf1666abd71f8253046b1a Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 15 Apr 2025 17:51:32 +0900 Subject: [PATCH 099/105] Edit user settings class remove password as mandatory --- www/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php b/www/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php index 8761c2d4..fce998ed 100644 --- a/www/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php +++ b/www/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php @@ -50,7 +50,8 @@ class EditUsers implements Interface\TableArraysInterface 'HIDDEN_value' => $_POST['HIDDEN_password'] ?? '', 'CONFIRM_value' => $_POST['CONFIRM_password'] ?? '', 'output_name' => 'Password', - 'mandatory' => 1, + // make it not mandatory to create dummy accounts that can only login via login url id + 'mandatory' => 0, 'type' => 'password', // later has to be password for encryption in database 'update' => [ // connected field updates, and update data 'password_change_date' => [ // db row to update From 8396f7856b0364073b57d9a9419bce6a79754530 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 15 Apr 2025 18:38:14 +0900 Subject: [PATCH 100/105] ACL Login add page information and lookup Add the full page information and a new file name to cuid lookup to the acl array. Add a new method to check if a page name is in the list of pages that can be accessed by the user. --- 4dev/tests/ACL/CoreLibsACLLoginTest.php | 2 ++ www/admin/class_test.login.php | 6 +++++ www/lib/CoreLibs/ACL/Login.php | 34 ++++++++++++++++++++++++- 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/4dev/tests/ACL/CoreLibsACLLoginTest.php b/4dev/tests/ACL/CoreLibsACLLoginTest.php index 1062bb60..f29f02d7 100644 --- a/4dev/tests/ACL/CoreLibsACLLoginTest.php +++ b/4dev/tests/ACL/CoreLibsACLLoginTest.php @@ -12,6 +12,8 @@ Not yet covered tests: - loginGetLocale - loginGetHeaderColor - loginGetPages +- loginGetPageLookupList +- loginPageAccessAllowed - loginGetEuid */ diff --git a/www/admin/class_test.login.php b/www/admin/class_test.login.php index 5b717b0b..ee0eef82 100644 --- a/www/admin/class_test.login.php +++ b/www/admin/class_test.login.php @@ -127,6 +127,12 @@ if (isset($login->loginGetAcl()['unit'])) { // IP check: 'REMOTE_ADDR', 'HTTP_X_FORWARDED_FOR', 'CLIENT_IP' in _SERVER // Agent check: 'HTTP_USER_AGENT' +print "
"; +print "PAGE lookup:
"; +$file_name = 'test_edit_base.php'; +print "Access to '$file_name': " . $log->prAr($login->loginPageAccessAllowed($file_name)) . "
"; +$file_name = 'i_do_not_exists.php'; +print "Access to '$file_name': " . $log->prAr($login->loginPageAccessAllowed($file_name)) . "
"; echo "
"; print "SESSION: " . Support::printAr($_SESSION) . "
"; diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 205bd49f..3ba9a1cc 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -924,7 +924,9 @@ class Login $mandatory_session_vars = [ 'LOGIN_USER_NAME', 'LOGIN_GROUP_NAME', 'LOGIN_EUCUID', 'LOGIN_EUCUUID', 'LOGIN_USER_ADDITIONAL_ACL', 'LOGIN_GROUP_ADDITIONAL_ACL', - 'LOGIN_ADMIN', 'LOGIN_GROUP_ACL_LEVEL', 'LOGIN_PAGES_ACL_LEVEL', 'LOGIN_USER_ACL_LEVEL', + 'LOGIN_ADMIN', 'LOGIN_GROUP_ACL_LEVEL', + 'LOGIN_PAGES', 'LOGIN_PAGES_LOOKUP', 'LOGIN_PAGES_ACL_LEVEL', + 'LOGIN_USER_ACL_LEVEL', 'LOGIN_UNIT', 'LOGIN_UNIT_DEFAULT_EACUID' ]; $force_reauth = false; @@ -1264,6 +1266,7 @@ class Login } $edit_page_ids = []; $pages = []; + $pages_lookup = []; $pages_acl = []; // set pages access $q = << [], 'visible' => [] ]; + $pages_lookup[$res['filename']] = $res['cuid']; // make reference filename -> level $pages_acl[$res['filename']] = $res['level']; } // for each page @@ -1367,6 +1371,7 @@ class Login // write back the pages data to the output array $this->session->setMany([ 'LOGIN_PAGES' => $pages, + 'LOGIN_PAGES_LOOKUP' => $pages_lookup, 'LOGIN_PAGES_ACL_LEVEL' => $pages_acl, ]); // load the edit_access user rights @@ -1526,6 +1531,8 @@ class Login ) { $this->acl['page'] = $_SESSION['LOGIN_PAGES_ACL_LEVEL'][$this->page_name]; } + $this->acl['pages_detail'] = $_SESSION['LOGIN_PAGES']; + $this->acl['pages_lookup_cuid'] = $_SESSION['LOGIN_PAGES_LOOKUP']; $this->acl['unit_cuid'] = null; $this->acl['unit_name'] = null; @@ -2728,6 +2735,31 @@ HTML; return $this->session->get('LOGIN_PAGES'); } + /** + * Return the current loaded list of pages the user can access + * + * @return array + */ + public function loginGetPageLookupList(): array + { + return $this->session->get('LOGIN_PAGES_LOOKUP'); + } + + /** + * Check access to a file in the pages list + * + * @param string $filename File name to check + * @return bool True if page in list and anything other than None access, False if failed + */ + public function loginPageAccessAllowed(string $filename): bool + { + return ( + $this->session->get('LOGIN_PAGES')[ + $this->session->get('LOGIN_PAGES_LOOKUP')[$filename] ?? '' + ] ?? 0 + ) != 0 ? true : false; + } + // MARK: logged in uid(pk)/eucuid/eucuuid /** From 8134da349f16d9189b82dbe4c4186938ba082385 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 16 Apr 2025 17:42:09 +0900 Subject: [PATCH 101/105] DB IO add flag to ignore not existing on cache reset, and ignore in ACL Login in the ACL login cache reset, set flag to ignore unset query data --- www/lib/CoreLibs/ACL/Login.php | 2 +- www/lib/CoreLibs/DB/IO.php | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 3ba9a1cc..e9d3dc97 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -1154,7 +1154,7 @@ class Login $q ); // reset any query data that might exist - $this->db->dbCacheReset($q, $params); + $this->db->dbCacheReset($q, $params, show_warning:false); // never cache return data $res = $this->db->dbReturnParams($q, $params, $this->db::NO_CACHE); // query was not run successful diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index d66a7d9c..232c3038 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -2942,12 +2942,14 @@ class IO * data to create a unique call one, optional * @return bool False if query not found, true if success */ - public function dbCacheReset(string $query, array $params = []): bool + public function dbCacheReset(string $query, array $params = [], bool $show_warning = true): bool { - $this->__dbErrorReset(); $query_hash = $this->dbBuildQueryHash($query, $params); // clears cache for this query - if (empty($this->cursor_ext[$query_hash]['query'])) { + if ( + $show_warning && + empty($this->cursor_ext[$query_hash]['query']) + ) { $this->__dbWarning(18, context: [ 'query' => $query, 'params' => $params, From 0ec19d5b756e18cc071c247e7a0f75f62ca871fa Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 22 Apr 2025 10:36:54 +0900 Subject: [PATCH 102/105] Add array helper for modifying key of a key value array --- .../CoreLibsCombinedArrayHandlerTest.php | 112 ++++++++++++++++++ www/admin/class_test.array.php | 2 + www/lib/CoreLibs/Combined/ArrayHandler.php | 30 +++++ 3 files changed, 144 insertions(+) diff --git a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php index d320b845..e11aab8e 100644 --- a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php +++ b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php @@ -1286,6 +1286,118 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase ) ); } + + /** + * provider for arrayModifyKey + * + * @return array> + */ + public function providerArrayModifyKey(): array + { + return [ + 'prefix and suffix add' => [ + 'array' => [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar', + ], + 'prefix' => 'Prefix: ', + 'suffix' => '.suffix', + 'expected' => [ + 'Prefix: a.suffix' => 'foo', + 'Prefix: b.suffix' => 'bar', + 'Prefix: c.suffix' => 'foobar', + ], + ], + 'prefix add only' => [ + 'array' => [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar', + ], + 'prefix' => 'Prefix: ', + 'suffix' => '', + 'expected' => [ + 'Prefix: a' => 'foo', + 'Prefix: b' => 'bar', + 'Prefix: c' => 'foobar', + ], + ], + 'suffix add only' => [ + 'array' => [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar', + ], + 'prefix' => '', + 'suffix' => '.suffix', + 'expected' => [ + 'a.suffix' => 'foo', + 'b.suffix' => 'bar', + 'c.suffix' => 'foobar', + ], + ], + 'empty array' => [ + 'array' => [], + 'prefix' => '', + 'suffix' => '.suffix', + 'expected' => [], + ], + 'no suffix or prefix' => [ + 'array' => [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar', + ], + 'prefix' => '', + 'suffix' => '', + 'expected' => [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar', + ], + ], + 'integer index mixed' => [ + 'array' => [ + 'a' => 'foo', + 'b' => 'bar', + 3 => 'foobar', + ], + 'prefix' => '', + 'suffix' => '.suffix', + 'expected' => [ + 'a.suffix' => 'foo', + 'b.suffix' => 'bar', + '3.suffix' => 'foobar', + ], + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::arrayModifyKey + * @dataProvider providerArrayModifyKey + * @testdox arrayModifyKey check that key is correctly modified with $key_mod_prefix and $key_mod_suffix [$_dataName] + * + * @param array $in_array + * @param string $key_mod_prefix + * @param string $key_mod_suffix + * @param array $expected + * @return void + */ + public function testArrayModifyKey( + array $in_array, + string $key_mod_prefix, + string $key_mod_suffix, + array $expected + ): void { + $this->assertEquals( + \CoreLibs\Combined\ArrayHandler::arrayModifyKey($in_array, $key_mod_prefix, $key_mod_suffix), + $expected + ); + } } // __END__ diff --git a/www/admin/class_test.array.php b/www/admin/class_test.array.php index 6491ee59..2e826f4d 100644 --- a/www/admin/class_test.array.php +++ b/www/admin/class_test.array.php @@ -263,6 +263,8 @@ $out = array_intersect_key( ); print "array intersect key: " . DgS::printAr($keys) . ": " . DgS::printAr($out) . "
"; +print "array + suffix: " . DgS::printAr(ArrayHandler::arrayModifyKey($array, key_mod_suffix:'_attached')) . "
"; + print ""; // __END__ diff --git a/www/lib/CoreLibs/Combined/ArrayHandler.php b/www/lib/CoreLibs/Combined/ArrayHandler.php index bd9a3b58..18f80961 100644 --- a/www/lib/CoreLibs/Combined/ArrayHandler.php +++ b/www/lib/CoreLibs/Combined/ArrayHandler.php @@ -551,6 +551,36 @@ class ArrayHandler ARRAY_FILTER_USE_KEY ); } + + /** + * 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 + * @param string [default=''] $key_mod_prefix + * @param string [default=''] $key_mod_suffix + * @return array + */ + public static function arrayModifyKey( + array $in_array, + string $key_mod_prefix = '', + string $key_mod_suffix = '' + ): array { + // skip if array is empty or neither prefix or suffix are set + if ( + $in_array == [] || + ($key_mod_prefix == '' && $key_mod_suffix == '') + ) { + return $in_array; + } + return array_combine( + array_map( + fn($key) => $key_mod_prefix . $key . $key_mod_suffix, + array_keys($in_array) + ), + array_values($in_array) + ); + } } // __END__ From 6e086fe7b3f8a4792304c694c947e27b5d6f0a73 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 22 Apr 2025 10:36:54 +0900 Subject: [PATCH 103/105] Add array helper for modifying key of a key value array --- .../CoreLibsCombinedArrayHandlerTest.php | 112 ++++++++++++++++++ www/admin/class_test.array.php | 2 + www/lib/CoreLibs/Combined/ArrayHandler.php | 30 +++++ 3 files changed, 144 insertions(+) diff --git a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php index d320b845..e11aab8e 100644 --- a/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php +++ b/4dev/tests/Combined/CoreLibsCombinedArrayHandlerTest.php @@ -1286,6 +1286,118 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase ) ); } + + /** + * provider for arrayModifyKey + * + * @return array> + */ + public function providerArrayModifyKey(): array + { + return [ + 'prefix and suffix add' => [ + 'array' => [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar', + ], + 'prefix' => 'Prefix: ', + 'suffix' => '.suffix', + 'expected' => [ + 'Prefix: a.suffix' => 'foo', + 'Prefix: b.suffix' => 'bar', + 'Prefix: c.suffix' => 'foobar', + ], + ], + 'prefix add only' => [ + 'array' => [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar', + ], + 'prefix' => 'Prefix: ', + 'suffix' => '', + 'expected' => [ + 'Prefix: a' => 'foo', + 'Prefix: b' => 'bar', + 'Prefix: c' => 'foobar', + ], + ], + 'suffix add only' => [ + 'array' => [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar', + ], + 'prefix' => '', + 'suffix' => '.suffix', + 'expected' => [ + 'a.suffix' => 'foo', + 'b.suffix' => 'bar', + 'c.suffix' => 'foobar', + ], + ], + 'empty array' => [ + 'array' => [], + 'prefix' => '', + 'suffix' => '.suffix', + 'expected' => [], + ], + 'no suffix or prefix' => [ + 'array' => [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar', + ], + 'prefix' => '', + 'suffix' => '', + 'expected' => [ + 'a' => 'foo', + 'b' => 'bar', + 'c' => 'foobar', + ], + ], + 'integer index mixed' => [ + 'array' => [ + 'a' => 'foo', + 'b' => 'bar', + 3 => 'foobar', + ], + 'prefix' => '', + 'suffix' => '.suffix', + 'expected' => [ + 'a.suffix' => 'foo', + 'b.suffix' => 'bar', + '3.suffix' => 'foobar', + ], + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::arrayModifyKey + * @dataProvider providerArrayModifyKey + * @testdox arrayModifyKey check that key is correctly modified with $key_mod_prefix and $key_mod_suffix [$_dataName] + * + * @param array $in_array + * @param string $key_mod_prefix + * @param string $key_mod_suffix + * @param array $expected + * @return void + */ + public function testArrayModifyKey( + array $in_array, + string $key_mod_prefix, + string $key_mod_suffix, + array $expected + ): void { + $this->assertEquals( + \CoreLibs\Combined\ArrayHandler::arrayModifyKey($in_array, $key_mod_prefix, $key_mod_suffix), + $expected + ); + } } // __END__ diff --git a/www/admin/class_test.array.php b/www/admin/class_test.array.php index 6491ee59..2e826f4d 100644 --- a/www/admin/class_test.array.php +++ b/www/admin/class_test.array.php @@ -263,6 +263,8 @@ $out = array_intersect_key( ); print "array intersect key: " . DgS::printAr($keys) . ": " . DgS::printAr($out) . "
"; +print "array + suffix: " . DgS::printAr(ArrayHandler::arrayModifyKey($array, key_mod_suffix:'_attached')) . "
"; + print ""; // __END__ diff --git a/www/lib/CoreLibs/Combined/ArrayHandler.php b/www/lib/CoreLibs/Combined/ArrayHandler.php index bd9a3b58..dae3e1e9 100644 --- a/www/lib/CoreLibs/Combined/ArrayHandler.php +++ b/www/lib/CoreLibs/Combined/ArrayHandler.php @@ -551,6 +551,36 @@ class ArrayHandler ARRAY_FILTER_USE_KEY ); } + + /** + * 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 + * @param string $key_mod_prefix [default=''] key prefix string + * @param string $key_mod_suffix [default=''] key suffix string + * @return array + */ + public static function arrayModifyKey( + array $in_array, + string $key_mod_prefix = '', + string $key_mod_suffix = '' + ): array { + // skip if array is empty or neither prefix or suffix are set + if ( + $in_array == [] || + ($key_mod_prefix == '' && $key_mod_suffix == '') + ) { + return $in_array; + } + return array_combine( + array_map( + fn($key) => $key_mod_prefix . $key . $key_mod_suffix, + array_keys($in_array) + ), + array_values($in_array) + ); + } } // __END__ From 93cb7e0cab7972030d83691cb067757ed9122906 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 22 Apr 2025 11:03:45 +0900 Subject: [PATCH 104/105] DB IO Adjustments for cursor set check and table exists check --- www/lib/CoreLibs/DB/Extended/ArrayIO.php | 10 +++++----- www/lib/CoreLibs/DB/IO.php | 5 ++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/www/lib/CoreLibs/DB/Extended/ArrayIO.php b/www/lib/CoreLibs/DB/Extended/ArrayIO.php index ac98091c..50256c6a 100644 --- a/www/lib/CoreLibs/DB/Extended/ArrayIO.php +++ b/www/lib/CoreLibs/DB/Extended/ArrayIO.php @@ -39,9 +39,9 @@ class ArrayIO extends \CoreLibs\DB\IO { // main calss variables /** @var array */ - private array $table_array; // the array from the table to work on + private array $table_array = []; // the array from the table to work on /** @var string */ - private string $table_name; // the table_name + private string $table_name = ''; // the table_name /** @var string */ private string $pk_name = ''; // the primary key from this table /** @var int|string|null */ @@ -127,9 +127,9 @@ class ArrayIO extends \CoreLibs\DB\IO public function getTableArray(bool $reset = false): array { if (!$reset) { - return $this->table_array ?? []; + return $this->table_array; } - $table_array = $this->table_array ?? []; + $table_array = $this->table_array; reset($table_array); return $table_array; } @@ -194,7 +194,7 @@ class ArrayIO extends \CoreLibs\DB\IO */ public function getTableName(): string { - return $this->table_name ?? ''; + return $this->table_name; } /** diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index 232c3038..732d7a27 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -2544,7 +2544,10 @@ class IO } // only go if NO cursor exists // if cursor exists ... - if ($this->cursor_ext[$query_hash]['cursor']) { + if ( + $this->cursor_ext[$query_hash]['cursor'] instanceof \PgSql\Result || + $this->cursor_ext[$query_hash]['cursor'] == 1 + ) { if ($first_call === true) { $this->cursor_ext[$query_hash]['log'][] = 'First call'; // count the rows returned (if select) From 074d5bed4ce1c9b5cae41a909ab5e273658e0934 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 15 May 2025 15:37:06 +0900 Subject: [PATCH 105/105] class test login logging update --- www/admin/class_test.login.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/www/admin/class_test.login.php b/www/admin/class_test.login.php index ee0eef82..cba9927c 100644 --- a/www/admin/class_test.login.php +++ b/www/admin/class_test.login.php @@ -31,6 +31,7 @@ $log = new CoreLibs\Logging\Logging([ 'log_per_date' => true, ]); $db = new CoreLibs\DB\IO(DB_CONFIG, $log); +$log->setLogFileId('classTest-login-override'); $login = new CoreLibs\ACL\Login( $db, $log, @@ -45,6 +46,7 @@ $login = new CoreLibs\ACL\Login( 'locale_path' => BASE . INCLUDES . LOCALE, ] ); +$log->setLogFileId($LOG_FILE_ID); ob_end_flush(); $login->loginMainCall(); @@ -158,5 +160,6 @@ if (is_string($edit_access_cuid)) { print "EA ID: " . $edit_access_id . "
"; print "EA CUID: " . $log->prAr($edit_access_cuid) . "
"; print "REV EA CUID: " . $log->prAr($edit_access_id_rev) . "
"; +$log->info('This is a test'); print "";