diff --git a/.gitignore b/.gitignore index 2268defa..dd606ef4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ .libs node_modules/ +composer.lock +vendor/ +tools/ +www/composer.lock +www/vendor diff --git a/.phan/config.php b/.phan/config.php index 17b20a04..746ceb07 100644 --- a/.phan/config.php +++ b/.phan/config.php @@ -27,7 +27,7 @@ use Phan\Config; return [ // "target_php_version" => "8.2", - "minimum_target_php_version" => "8.1", + "minimum_target_php_version" => "8.2", // turn color on (-C) "color_issue_messages_if_supported" => true, // If true, missing properties will be created when 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/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 e331bd6a..40271ec8 100755 --- a/4dev/checking/phpunit.sh +++ b/4dev/checking/phpunit.sh @@ -1,49 +1,96 @@ #!/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=""; -if [ "${1}" = "t" ] || [ "${2}" = "t" ]; then - opt_testdox="--testdox"; -fi; -php_bin=""; -if [ -n "${1}" ]; then +opt_verbose=""; +php_version=""; +no_php_version=0; +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; + -t | --testdox) + opt_testdox="--testdox"; + ;; + -v | --verbose) + opt_verbose="--verbose"; + ;; + -p | --php) + php_version="${2-}"; + shift + ;; + -h | --help) + usage + ;; + # invalid option + -?*) + error "[!] Unknown option: '$1'." + ;; + esac + shift; +done; + +if [ -z "${php_version}" ]; then + php_version="${DEFAULT_PHP_VERSION}"; + no_php_version=1; 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; +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; 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}tools/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 [ ! -z "${php_bin}" ]; then - echo "CALLED WITH PHP: ${php_bin}"$(${php_bin} --version); +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); + echo "Default PHP used: $(php --version)"; fi; # __END__ 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/ 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 c4a892bd..be7519b9 100644 --- a/4dev/database/function/set_edit_generic.sql +++ b/4dev/database/function/set_edit_generic.sql @@ -7,10 +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/database/function/upgrade_serial_to_identity.sql b/4dev/database/function/upgrade_serial_to_identity.sql new file mode 100644 index 00000000..0429f407 --- /dev/null +++ b/4dev/database/function/upgrade_serial_to_identity.sql @@ -0,0 +1,110 @@ +-- Upgrade serial to identity type +-- +-- Original: https://www.enterprisedb.com/blog/postgresql-10-identity-columns-explained#section-6 +-- +-- @param reclass tbl The table where the column is located, prefix with 'schema.' if different schema +-- @param name col The column to be changed +-- @param varchar identity_type [default=a] Allowed a, d, assigned, default +-- @param varchar col_type [default=''] Allowed smallint, int, bigint, int2, int4, int8 +-- @returns varchar status tring +-- @raises EXCEPTON on column not found, no linked sequence, more than one linked sequence found, invalid col type +-- +CREATE OR REPLACE FUNCTION upgrade_serial_to_identity( + tbl regclass, + col name, + identity_type varchar = 'a', + col_type varchar = '' +) +RETURNS varchar +LANGUAGE plpgsql +AS $$ +DECLARE + colnum SMALLINT; + seqid OID; + count INT; + col_type_oid INT; + col_type_len INT; + current_col_atttypid OID; + current_col_attlen INT; + status_string VARCHAR; +BEGIN + -- switch between always (default) or default identiy type + IF identity_type NOT IN ('a', 'd', 'assigned', 'default') THEN + identity_type := 'a'; + ELSE + IF identity_type = 'default' THEN + identity_type := 'd'; + ELSIF identity_type = 'assigned' THEN + identity_type := 'a'; + END IF; + END IF; + -- find column number, attribute oid and attribute len + SELECT attnum, atttypid, attlen + INTO colnum, current_col_atttypid, current_col_attlen + FROM pg_attribute + WHERE attrelid = tbl AND attname = col; + IF NOT FOUND THEN + RAISE EXCEPTION 'column does not exist'; + END IF; + + -- find sequence + SELECT INTO seqid objid + FROM pg_depend + WHERE (refclassid, refobjid, refobjsubid) = ('pg_class'::regclass, tbl, colnum) + AND classid = 'pg_class'::regclass AND objsubid = 0 + AND deptype = 'a'; + + GET DIAGNOSTICS count = ROW_COUNT; + IF count < 1 THEN + RAISE EXCEPTION 'no linked sequence found'; + ELSIF count > 1 THEN + RAISE EXCEPTION 'more than one linked sequence found'; + END IF; + + IF col_type <> '' AND col_type NOT IN ('smallint', 'int', 'bigint', 'int2', 'int4', 'int8') THEN + RAISE EXCEPTION 'Invalid col type: %', col_type; + END IF; + + -- drop the default + EXECUTE 'ALTER TABLE ' || tbl || ' ALTER COLUMN ' || quote_ident(col) || ' DROP DEFAULT'; + + -- change the dependency between column and sequence to internal + UPDATE pg_depend + SET deptype = 'i' + WHERE (classid, objid, objsubid) = ('pg_class'::regclass, seqid, 0) + AND deptype = 'a'; + + -- mark the column as identity column + UPDATE pg_attribute + -- set to 'd' for default + SET attidentity = identity_type + WHERE attrelid = tbl + AND attname = col; + status_string := 'Updated to identity for table "' || tbl || '" and columen "' || col || '" with type "' || identity_type || '"'; + + -- set type if requested and not empty + IF col_type <> '' THEN + -- rewrite smallint, int, bigint + IF col_type = 'smallint' THEN + col_type := 'int2'; + ELSIF col_type = 'int' THEN + col_type := 'int4'; + ELSIF col_type = 'bigint' THEN + col_type := 'int8'; + END IF; + -- get the length and oid for selected + SELECT oid, typlen INTO col_type_oid, col_type_len FROM pg_type WHERE typname = col_type; + -- set only if diff or hight + IF current_col_atttypid <> col_type_oid AND col_type_len > current_col_attlen THEN + status_string := status_string || '. Change col type: ' || col_type; + -- update type + UPDATE pg_attribute + SET + atttypid = col_type_oid, attlen = col_type_len + WHERE attrelid = tbl + AND attname = col; + END IF; + END IF; + RETURN status_string; +END; +$$; diff --git a/4dev/database/table/edit_access.sql b/4dev/database/table/edit_access.sql index b0d28111..f9d02e01 100644 --- a/4dev/database/table/edit_access.sql +++ b/4dev/database/table/edit_access.sql @@ -7,7 +7,7 @@ -- 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, diff --git a/4dev/database/table/edit_access_data.sql b/4dev/database/table/edit_access_data.sql index 097efcf1..85d1b53f 100644 --- a/4dev/database/table/edit_access_data.sql +++ b/4dev/database/table/edit_access_data.sql @@ -7,7 +7,7 @@ -- 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/database/table/edit_access_right.sql b/4dev/database/table/edit_access_right.sql index e95deb70..e0a87475 100644 --- a/4dev/database/table/edit_access_right.sql +++ b/4dev/database/table/edit_access_right.sql @@ -8,7 +8,7 @@ -- 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, diff --git a/4dev/database/table/edit_access_user.sql b/4dev/database/table/edit_access_user.sql index 72e40aed..7e413371 100644 --- a/4dev/database/table/edit_access_user.sql +++ b/4dev/database/table/edit_access_user.sql @@ -7,7 +7,7 @@ -- 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, diff --git a/4dev/database/table/edit_generic.sql b/4dev/database/table/edit_generic.sql index 15320aab..1be979b3 100644 --- a/4dev/database/table/edit_generic.sql +++ b/4dev/database/table/edit_generic.sql @@ -8,6 +8,7 @@ -- DROP TABLE edit_generic; CREATE TABLE edit_generic ( cuid VARCHAR, + cuuid UUID DEFAULT gen_random_uuid(), date_created TIMESTAMP WITHOUT TIME ZONE DEFAULT clock_timestamp(), date_updated TIMESTAMP WITHOUT TIME ZONE ); diff --git a/4dev/database/table/edit_group.sql b/4dev/database/table/edit_group.sql index 3acffde2..dcfeb1fb 100644 --- a/4dev/database/table/edit_group.sql +++ b/4dev/database/table/edit_group.sql @@ -7,7 +7,7 @@ -- 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, diff --git a/4dev/database/table/edit_language.sql b/4dev/database/table/edit_language.sql index 033f3782..9552be7c 100644 --- a/4dev/database/table/edit_language.sql +++ b/4dev/database/table/edit_language.sql @@ -8,7 +8,7 @@ -- 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, diff --git a/4dev/database/table/edit_log.sql b/4dev/database/table/edit_log.sql index 42acdc89..7edd8da5 100644 --- a/4dev/database/table/edit_log.sql +++ b/4dev/database/table/edit_log.sql @@ -7,35 +7,54 @@ -- 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, - username VARCHAR, - password VARCHAR, + 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, - ip VARCHAR, + -- 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, - action VARCHAR, - action_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, + -- DEPRECATED [http*] + http_accept VARCHAR, -- in http_data + http_accept_charset VARCHAR, -- in http_data + http_accept_encoding VARCHAR, -- in http_data + -- 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 + 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/database/table/edit_menu_group.sql b/4dev/database/table/edit_menu_group.sql index 11727cbd..6731052b 100644 --- a/4dev/database/table/edit_menu_group.sql +++ b/4dev/database/table/edit_menu_group.sql @@ -7,10 +7,8 @@ -- 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 ) INHERITS (edit_generic) WITHOUT OIDS; - - diff --git a/4dev/database/table/edit_page.sql b/4dev/database/table/edit_page.sql index 2404d277..f0ac58b8 100644 --- a/4dev/database/table/edit_page.sql +++ b/4dev/database/table/edit_page.sql @@ -7,7 +7,7 @@ -- 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, diff --git a/4dev/database/table/edit_page_access.sql b/4dev/database/table/edit_page_access.sql index 56c32794..a403e05a 100644 --- a/4dev/database/table/edit_page_access.sql +++ b/4dev/database/table/edit_page_access.sql @@ -7,7 +7,7 @@ -- 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, @@ -16,5 +16,3 @@ CREATE TABLE edit_page_access ( FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, enabled SMALLINT NOT NULL DEFAULT 0 ) INHERITS (edit_generic) WITHOUT OIDS; - - diff --git a/4dev/database/table/edit_page_content.sql b/4dev/database/table/edit_page_content.sql index 52b1473f..e74c5e1f 100644 --- a/4dev/database/table/edit_page_content.sql +++ b/4dev/database/table/edit_page_content.sql @@ -8,7 +8,7 @@ -- 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, diff --git a/4dev/database/table/edit_query_string.sql b/4dev/database/table/edit_query_string.sql index 1cf562c2..4b374619 100644 --- a/4dev/database/table/edit_query_string.sql +++ b/4dev/database/table/edit_query_string.sql @@ -7,7 +7,7 @@ -- DROP TABLE edit_query_string; CREATE TABLE edit_query_string ( - edit_query_string_id SERIAL PRIMARY KEY, + edit_query_string_id SERIAINT GENERATED ALWAYS AS IDENTITYL 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, diff --git a/4dev/database/table/edit_scheme.sql b/4dev/database/table/edit_scheme.sql index cb75c6aa..5afecb5e 100644 --- a/4dev/database/table/edit_scheme.sql +++ b/4dev/database/table/edit_scheme.sql @@ -7,7 +7,7 @@ -- 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, diff --git a/4dev/database/table/edit_user.sql b/4dev/database/table/edit_user.sql index 98342905..47c4914c 100644 --- a/4dev/database/table/edit_user.sql +++ b/4dev/database/table/edit_user.sql @@ -7,7 +7,7 @@ -- 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, @@ -35,11 +35,10 @@ 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, + -- force lgout counter + force_logout INT DEFAULT 0, -- last login log last_login TIMESTAMP WITHOUT TIME ZONE, -- login error @@ -76,9 +75,8 @@ 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.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/database/table/edit_visible_group.sql b/4dev/database/table/edit_visible_group.sql index bc89d869..7c407934 100644 --- a/4dev/database/table/edit_visible_group.sql +++ b/4dev/database/table/edit_visible_group.sql @@ -7,7 +7,7 @@ -- 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; diff --git a/4dev/tests/AAASetupData/requests/http_requests.php b/4dev/tests/AAASetupData/requests/http_requests.php new file mode 100644 index 00000000..9523a60d --- /dev/null +++ b/4dev/tests/AAASetupData/requests/http_requests.php @@ -0,0 +1,65 @@ + $http_headers + * @param ?string $body + * @return string + */ +function buildContent(array $http_headers, ?string $body): string +{ + if (is_string($body) && !empty($body)) { + $_body = json_decode($body, true); + if (!is_array($_body)) { + $body = [$body]; + } else { + $body = $_body; + } + } elseif (is_string($body)) { + $body = []; + } + return json_encode([ + 'HEADERS' => $http_headers, + "REQUEST_TYPE" => $_SERVER['REQUEST_METHOD'], + "PARAMS" => $_GET, + "BODY" => $body, + ]); +} + +$http_headers = array_filter($_SERVER, function ($value, $key) { + if (str_starts_with($key, 'HTTP_')) { + return true; + } +}, ARRAY_FILTER_USE_BOTH); + +header("Content-Type: application/json; charset=UTF-8"); + +// if the header has Authorization and RunAuthTest then exit with 401 +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(1); +} + +// if server request type is get set file_get to null -> no body +if ($_SERVER['REQUEST_METHOD'] == "GET") { + $file_get = null; +} 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(1); +} + +print buildContent($http_headers, $file_get); + +// __END__ diff --git a/4dev/tests/ACL/CoreLibsACLLoginTest.php b/4dev/tests/ACL/CoreLibsACLLoginTest.php index e2a28b0e..e6abe067 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 @@ -235,22 +265,25 @@ final class CoreLibsACLLoginTest extends TestCase 'ajax_post_action' => 'login', ], ], - 'load, session euid set only, php error' => [ + 'load, session eucuuid set only, php error' => [ [ 'page_name' => 'edit_users.php', ], [], [], [ - 'EUID' => 1, + '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, + 'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST', 'edit_access_uid' => 'AdminAccess', 'edit_access_data' => 'test', 'base_access' => 'list', @@ -259,20 +292,23 @@ final class CoreLibsACLLoginTest extends TestCase [], [], [ - 'EUID' => 1, - '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' => [ - 'AdminAccess' => 1, + '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' => [ - 1 => [ + 'LOGIN_UNIT' => [ + '123456789012' => [ + 'id' => 1, 'acl_level' => 80, 'name' => 'Admin Access', 'uid' => 'AdminAccess', @@ -284,8 +320,8 @@ final class CoreLibsACLLoginTest extends TestCase 'additional_acl' => [] ], ], - // 'UNIT_DEFAULT' => '', - // 'DEFAULT_ACL_LIST' => [], + // 'LOGIN_UNIT_DEFAULT' => '', + // 'LOGIN_DEFAULT_ACL_LIST' => [], ], 0, [ @@ -293,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, @@ -412,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 @@ -437,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 @@ -462,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 @@ -487,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, @@ -511,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' @@ -536,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', @@ -555,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, @@ -565,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' @@ -590,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', @@ -616,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 @@ -641,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', @@ -659,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, @@ -669,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', @@ -688,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, @@ -698,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', @@ -717,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, @@ -727,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', @@ -746,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, @@ -777,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', @@ -800,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, @@ -810,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', @@ -833,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, @@ -843,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', @@ -869,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', @@ -889,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, @@ -899,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', @@ -925,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', @@ -945,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, @@ -955,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', @@ -981,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', @@ -1008,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', @@ -1038,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, @@ -1085,9 +1154,9 @@ final class CoreLibsACLLoginTest extends TestCase /** @var \CoreLibs\Create\Session&MockObject */ $session_mock = $this->createPartialMock( \CoreLibs\Create\Session::class, - ['startSession', 'checkActiveSession', 'sessionDestroy'] + ['getSessionId', 'checkActiveSession', 'sessionDestroy'] ); - $session_mock->method('startSession')->willReturn('ACLLOGINTEST12'); + $session_mock->method('getSessionId')->willReturn('ACLLOGINTEST12'); $session_mock->method('checkActiveSession')->willReturn(true); $session_mock->method('sessionDestroy')->will( $this->returnCallback(function () { @@ -1107,11 +1176,15 @@ final class CoreLibsACLLoginTest extends TestCase $_POST[$post_var] = $post_value; } + // set ingoing session cuuid if requested + if (isset($session['LOGIN_EUCUUID']) && $session['LOGIN_EUCUUID'] == 'SET_EUCUUID_IN_TEST') { + $session['LOGIN_EUCUUID'] = self::$edit_user_cuuid; + } + // set _SESSION data 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([ @@ -1130,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') @@ -1148,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 @@ -1365,6 +1442,19 @@ final class CoreLibsACLLoginTest extends TestCase // run test try { + // preset, we cannot set that in the provider + 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 @@ -1423,6 +1513,31 @@ 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' + ); + // - 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( $expected['check_access'], @@ -1445,7 +1560,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' @@ -1476,11 +1591,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 || @@ -1788,9 +1904,9 @@ final class CoreLibsACLLoginTest extends TestCase /** @var \CoreLibs\Create\Session&MockObject */ $session_mock = $this->createPartialMock( \CoreLibs\Create\Session::class, - ['startSession', 'checkActiveSession', 'sessionDestroy'] + ['getSessionId', 'checkActiveSession', 'sessionDestroy'] ); - $session_mock->method('startSession')->willReturn('ACLLOGINTEST34'); + $session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34'); $session_mock->method('checkActiveSession')->willReturn(true); $session_mock->method('sessionDestroy')->will( $this->returnCallback(function () { @@ -1902,9 +2018,9 @@ final class CoreLibsACLLoginTest extends TestCase /** @var \CoreLibs\Create\Session&MockObject */ $session_mock = $this->createPartialMock( \CoreLibs\Create\Session::class, - ['startSession', 'checkActiveSession', 'sessionDestroy'] + ['getSessionId', 'checkActiveSession', 'sessionDestroy'] ); - $session_mock->method('startSession')->willReturn('ACLLOGINTEST34'); + $session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34'); $session_mock->method('checkActiveSession')->willReturn(true); $session_mock->method('sessionDestroy')->will( $this->returnCallback(function () { @@ -1990,9 +2106,9 @@ final class CoreLibsACLLoginTest extends TestCase /** @var \CoreLibs\Create\Session&MockObject */ $session_mock = $this->createPartialMock( \CoreLibs\Create\Session::class, - ['startSession', 'checkActiveSession', 'sessionDestroy'] + ['getSessionId', 'checkActiveSession', 'sessionDestroy'] ); - $session_mock->method('startSession')->willReturn('ACLLOGINTEST34'); + $session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34'); $session_mock->method('checkActiveSession')->willReturn(true); $session_mock->method('sessionDestroy')->will( $this->returnCallback(function () { @@ -2086,9 +2202,9 @@ final class CoreLibsACLLoginTest extends TestCase /** @var \CoreLibs\Create\Session&MockObject */ $session_mock = $this->createPartialMock( \CoreLibs\Create\Session::class, - ['startSession', 'checkActiveSession', 'sessionDestroy'] + ['getSessionId', 'checkActiveSession', 'sessionDestroy'] ); - $session_mock->method('startSession')->willReturn('ACLLOGINTEST34'); + $session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34'); $session_mock->method('checkActiveSession')->willReturn(true); $session_mock->method('sessionDestroy')->will( $this->returnCallback(function () { diff --git a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql index af0a5f67..1b6855b5 100644 --- a/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql +++ b/4dev/tests/ACL/database/CoreLibsACLLogin_database_create_data.sql @@ -5,15 +5,15 @@ CREATE FUNCTION random_string(randomLength int) RETURNS text AS $$ SELECT array_to_string( - ARRAY( - SELECT substring( - 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', - trunc(random() * 62)::int + 1, - 1 - ) - FROM generate_series(1, randomLength) AS gs(x) - ), - '' + ARRAY( + SELECT substring( + 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', + trunc(random() * 62)::int + 1, + 1 + ) + FROM generate_series(1, randomLength) AS gs(x) + ), + '' ) $$ LANGUAGE SQL @@ -27,15 +27,16 @@ CREATE OR REPLACE FUNCTION set_edit_generic() RETURNS TRIGGER AS $$ DECLARE - random_length INT = 12; -- that should be long enough + random_length INT = 12; -- that should be long enough BEGIN - IF TG_OP = 'INSERT' THEN - NEW.date_created := 'now'; - NEW.cuid := random_string(random_length); - ELSIF TG_OP = 'UPDATE' THEN - NEW.date_updated := 'now'; - END IF; - RETURN NEW; + IF TG_OP = 'INSERT' THEN + NEW.date_created := clock_timestamp(); + NEW.cuid := random_string(random_length); + NEW.cuuid := gen_random_uuid(); + ELSIF TG_OP = 'UPDATE' THEN + NEW.date_updated := clock_timestamp(); + END IF; + RETURN NEW; END; $$ LANGUAGE 'plpgsql'; @@ -46,29 +47,29 @@ LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION set_edit_access_uid() RETURNS TRIGGER AS $$ DECLARE - myrec RECORD; - v_uid VARCHAR; + myrec RECORD; + v_uid VARCHAR; BEGIN - -- skip if NEW.name is not set - IF NEW.name IS NOT NULL AND NEW.name <> '' THEN - -- use NEW.name as base, remove all spaces - -- name data is already unique, so we do not need to worry about this here - v_uid := REPLACE(NEW.name, ' ', ''); - IF TG_OP = 'INSERT' THEN - -- always set - NEW.uid := v_uid; - ELSIF TG_OP = 'UPDATE' THEN - -- check if not set, then set - SELECT INTO myrec t.* FROM edit_access t WHERE edit_access_id = NEW.edit_access_id; - IF FOUND THEN - NEW.uid := v_uid; - END IF; - END IF; - END IF; - RETURN NEW; + -- skip if NEW.name is not set + IF NEW.name IS NOT NULL AND NEW.name <> '' THEN + -- use NEW.name as base, remove all spaces + -- name data is already unique, so we do not need to worry about this here + v_uid := REPLACE(NEW.name, ' ', ''); + IF TG_OP = 'INSERT' THEN + -- always set + NEW.uid := v_uid; + ELSIF TG_OP = 'UPDATE' THEN + -- check if not set, then set + SELECT INTO myrec t.* FROM edit_access t WHERE edit_access_id = NEW.edit_access_id; + IF FOUND THEN + NEW.uid := v_uid; + END IF; + END IF; + END IF; + RETURN NEW; END; $$ - LANGUAGE 'plpgsql'; + LANGUAGE 'plpgsql'; -- END: function/edit_access_set_uid.sql -- START: function/edit_group_set_uid.sql -- add uid add for edit_group table @@ -76,29 +77,29 @@ $$ CREATE OR REPLACE FUNCTION set_edit_group_uid() RETURNS TRIGGER AS $$ DECLARE - myrec RECORD; - v_uid VARCHAR; + myrec RECORD; + v_uid VARCHAR; BEGIN - -- skip if NEW.name is not set - IF NEW.name IS NOT NULL AND NEW.name <> '' THEN - -- use NEW.name as base, remove all spaces - -- name data is already unique, so we do not need to worry about this here - v_uid := REPLACE(NEW.name, ' ', ''); - IF TG_OP = 'INSERT' THEN - -- always set - NEW.uid := v_uid; - ELSIF TG_OP = 'UPDATE' THEN - -- check if not set, then set - SELECT INTO myrec t.* FROM edit_group t WHERE edit_group_id = NEW.edit_group_id; - IF FOUND THEN - NEW.uid := v_uid; - END IF; - END IF; - END IF; - RETURN NEW; + -- skip if NEW.name is not set + IF NEW.name IS NOT NULL AND NEW.name <> '' THEN + -- use NEW.name as base, remove all spaces + -- name data is already unique, so we do not need to worry about this here + v_uid := REPLACE(NEW.name, ' ', ''); + IF TG_OP = 'INSERT' THEN + -- always set + NEW.uid := v_uid; + ELSIF TG_OP = 'UPDATE' THEN + -- check if not set, then set + SELECT INTO myrec t.* FROM edit_group t WHERE edit_group_id = NEW.edit_group_id; + IF FOUND THEN + NEW.uid := v_uid; + END IF; + END IF; + END IF; + RETURN NEW; END; $$ - LANGUAGE 'plpgsql'; + LANGUAGE 'plpgsql'; -- END: function/edit_group_set_uid.sql -- START: function/edit_log_partition_insert.sql -- AUTHOR: Clemens Schwaighofer @@ -112,142 +113,142 @@ CREATE OR REPLACE FUNCTION edit_log_insert_trigger () RETURNS TRIGGER AS $$ DECLARE - start_date DATE := '2010-01-01'; - end_date DATE; - timeformat TEXT := 'YYYY'; - selector TEXT := 'year'; - base_table TEXT := 'edit_log'; - _interval INTERVAL := '1 ' || selector; - _interval_next INTERVAL := '2 ' || selector; - table_name TEXT; - -- compare date column - compare_date DATE := NEW.event_date; - compare_date_name TEXT := 'event_date'; - -- the create commands - command_create_table TEXT := 'CREATE TABLE IF NOT EXISTS {TABLE_NAME} (CHECK({COMPARE_DATE_NAME} >= {START_DATE} AND {COMPARE_DATE_NAME} < {END_DATE})) INHERITS ({BASE_NAME})'; - command_create_primary_key TEXT := 'ALTER TABLE {TABLE_NAME} ADD PRIMARY KEY ({BASE_TABLE}_id)'; - command_create_foreign_key_1 TEXT := 'ALTER TABLE {TABLE_NAME} ADD CONSTRAINT {TABLE_NAME}_euid_fkey FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL'; - command_create_trigger_1 TEXT = 'CREATE TRIGGER trg_{TABLE_NAME} BEFORE INSERT OR UPDATE ON {TABLE_NAME} FOR EACH ROW EXECUTE PROCEDURE set_edit_generic()'; + start_date DATE := '2010-01-01'; + end_date DATE; + timeformat TEXT := 'YYYY'; + selector TEXT := 'year'; + base_table TEXT := 'edit_log'; + _interval INTERVAL := '1 ' || selector; + _interval_next INTERVAL := '2 ' || selector; + table_name TEXT; + -- compare date column + compare_date DATE := NEW.event_date; + compare_date_name TEXT := 'event_date'; + -- the create commands + command_create_table TEXT := 'CREATE TABLE IF NOT EXISTS {TABLE_NAME} (CHECK({COMPARE_DATE_NAME} >= {START_DATE} AND {COMPARE_DATE_NAME} < {END_DATE})) INHERITS ({BASE_NAME})'; + command_create_primary_key TEXT := 'ALTER TABLE {TABLE_NAME} ADD PRIMARY KEY ({BASE_TABLE}_id)'; + command_create_foreign_key_1 TEXT := 'ALTER TABLE {TABLE_NAME} ADD CONSTRAINT {TABLE_NAME}_euid_fkey FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL'; + command_create_trigger_1 TEXT = 'CREATE TRIGGER trg_{TABLE_NAME} BEFORE INSERT OR UPDATE ON {TABLE_NAME} FOR EACH ROW EXECUTE PROCEDURE set_edit_generic()'; BEGIN - -- we are in valid start time area - IF (NEW.event_date >= start_date) THEN - -- current table name - table_name := base_table || '_' || to_char(NEW.event_date, timeformat); - BEGIN - EXECUTE 'INSERT INTO ' || quote_ident(table_name) || ' SELECT ($1).*' USING NEW; - -- if insert failed because of missing table, create new below - EXCEPTION - WHEN undefined_table THEN - -- another block, so in case the creation fails here too - BEGIN - -- create new table here + all indexes - start_date := date_trunc(selector, NEW.event_date); - end_date := date_trunc(selector, NEW.event_date + _interval); - -- creat table - EXECUTE format(REPLACE( -- end date - REPLACE( -- start date - REPLACE( -- compare date name - REPLACE( -- base name (inherit) - REPLACE( -- table name - command_create_table, - '{TABLE_NAME}', - table_name - ), - '{BASE_NAME}', - base_table - ), - '{COMPARE_DATE_NAME}', - compare_date_name - ), - '{START_DATE}', - quote_literal(start_date) - ), - '{END_DATE}', - quote_literal(end_date) - )); - -- create all indexes and triggers - EXECUTE format(REPLACE( - REPLACE( - command_create_primary_key, - '{TABLE_NAME}', - table_name - ), - '{BASE_TABLE}', - base_table - )); - -- FK constraints - EXECUTE format(REPLACE(command_create_foreign_key_1, '{TABLE_NAME}', table_name)); - -- generic trigger - EXECUTE format(REPLACE(command_create_trigger_1, '{TABLE_NAME}', table_name)); + -- we are in valid start time area + IF (NEW.event_date >= start_date) THEN + -- current table name + table_name := base_table || '_' || to_char(NEW.event_date, timeformat); + BEGIN + EXECUTE 'INSERT INTO ' || quote_ident(table_name) || ' SELECT ($1).*' USING NEW; + -- if insert failed because of missing table, create new below + EXCEPTION + WHEN undefined_table THEN + -- another block, so in case the creation fails here too + BEGIN + -- create new table here + all indexes + start_date := date_trunc(selector, NEW.event_date); + end_date := date_trunc(selector, NEW.event_date + _interval); + -- creat table + EXECUTE format(REPLACE( -- end date + REPLACE( -- start date + REPLACE( -- compare date name + REPLACE( -- base name (inherit) + REPLACE( -- table name + command_create_table, + '{TABLE_NAME}', + table_name + ), + '{BASE_NAME}', + base_table + ), + '{COMPARE_DATE_NAME}', + compare_date_name + ), + '{START_DATE}', + quote_literal(start_date) + ), + '{END_DATE}', + quote_literal(end_date) + )); + -- create all indexes and triggers + EXECUTE format(REPLACE( + REPLACE( + command_create_primary_key, + '{TABLE_NAME}', + table_name + ), + '{BASE_TABLE}', + base_table + )); + -- FK constraints + EXECUTE format(REPLACE(command_create_foreign_key_1, '{TABLE_NAME}', table_name)); + -- generic trigger + EXECUTE format(REPLACE(command_create_trigger_1, '{TABLE_NAME}', table_name)); - -- insert try again - EXECUTE 'INSERT INTO ' || quote_ident(table_name) || ' SELECT ($1).*' USING NEW; - EXCEPTION - WHEN OTHERS THEN - -- if this faled, throw it into the overflow table (so we don't loose anything) - INSERT INTO edit_log_overflow VALUES (NEW.*); - END; - -- other errors, insert into overlow - WHEN OTHERS THEN - -- if this faled, throw it into the overflow table (so we don't loose anything) - INSERT INTO edit_log_overflow VALUES (NEW.*); - END; - -- main insert run done, check if we have to create next months table - BEGIN - -- check if next month table exists - table_name := base_table || '_' || to_char((SELECT NEW.event_date + _interval)::DATE, timeformat); - -- RAISE NOTICE 'SEARCH NEXT: %', table_name; - IF (SELECT to_regclass(table_name)) IS NULL THEN - -- move inner interval same - start_date := date_trunc(selector, NEW.event_date + _interval); - end_date := date_trunc(selector, NEW.event_date + _interval_next); - -- RAISE NOTICE 'CREATE NEXT: %', table_name; - -- create table - EXECUTE format(REPLACE( -- end date - REPLACE( -- start date - REPLACE( -- compare date name - REPLACE( -- base name (inherit) - REPLACE( -- table name - command_create_table, - '{TABLE_NAME}', - table_name - ), - '{BASE_NAME}', - base_table - ), - '{COMPARE_DATE_NAME}', - compare_date_name - ), - '{START_DATE}', - quote_literal(start_date) - ), - '{END_DATE}', - quote_literal(end_date) - )); - -- create all indexes and triggers - EXECUTE format(REPLACE( - REPLACE( - command_create_primary_key, - '{TABLE_NAME}', - table_name - ), - '{BASE_TABLE}', - base_table - )); - -- FK constraints - EXECUTE format(REPLACE(command_create_foreign_key_1, '{TABLE_NAME}', table_name)); - -- generic trigger - EXECUTE format(REPLACE(command_create_trigger_1, '{TABLE_NAME}', table_name)); - END IF; - EXCEPTION - WHEN OTHERS THEN - RAISE NOTICE 'Failed to create next table: %', table_name; - END; - ELSE - -- if outside valid date, insert into overflow - INSERT INTO edit_log_overflow VALUES (NEW.*); - END IF; - RETURN NULL; + -- insert try again + EXECUTE 'INSERT INTO ' || quote_ident(table_name) || ' SELECT ($1).*' USING NEW; + EXCEPTION + WHEN OTHERS THEN + -- if this faled, throw it into the overflow table (so we don't loose anything) + INSERT INTO edit_log_overflow VALUES (NEW.*); + END; + -- other errors, insert into overlow + WHEN OTHERS THEN + -- if this faled, throw it into the overflow table (so we don't loose anything) + INSERT INTO edit_log_overflow VALUES (NEW.*); + END; + -- main insert run done, check if we have to create next months table + BEGIN + -- check if next month table exists + table_name := base_table || '_' || to_char((SELECT NEW.event_date + _interval)::DATE, timeformat); + -- RAISE NOTICE 'SEARCH NEXT: %', table_name; + IF (SELECT to_regclass(table_name)) IS NULL THEN + -- move inner interval same + start_date := date_trunc(selector, NEW.event_date + _interval); + end_date := date_trunc(selector, NEW.event_date + _interval_next); + -- RAISE NOTICE 'CREATE NEXT: %', table_name; + -- create table + EXECUTE format(REPLACE( -- end date + REPLACE( -- start date + REPLACE( -- compare date name + REPLACE( -- base name (inherit) + REPLACE( -- table name + command_create_table, + '{TABLE_NAME}', + table_name + ), + '{BASE_NAME}', + base_table + ), + '{COMPARE_DATE_NAME}', + compare_date_name + ), + '{START_DATE}', + quote_literal(start_date) + ), + '{END_DATE}', + quote_literal(end_date) + )); + -- create all indexes and triggers + EXECUTE format(REPLACE( + REPLACE( + command_create_primary_key, + '{TABLE_NAME}', + table_name + ), + '{BASE_TABLE}', + base_table + )); + -- FK constraints + EXECUTE format(REPLACE(command_create_foreign_key_1, '{TABLE_NAME}', table_name)); + -- generic trigger + EXECUTE format(REPLACE(command_create_trigger_1, '{TABLE_NAME}', table_name)); + END IF; + EXCEPTION + WHEN OTHERS THEN + RAISE NOTICE 'Failed to create next table: %', table_name; + END; + ELSE + -- if outside valid date, insert into overflow + INSERT INTO edit_log_overflow VALUES (NEW.*); + END IF; + RETURN NULL; END $$ LANGUAGE 'plpgsql'; @@ -260,22 +261,22 @@ CREATE OR REPLACE FUNCTION set_login_user_id_set_date() RETURNS TRIGGER AS $$ BEGIN - -- if new is not null/empty - -- and old one is null or old one different new one - -- set NOW() - -- if new one is NULL - -- set NULL - IF - NEW.login_user_id IS NOT NULL AND NEW.login_user_id <> '' AND - (OLD.login_user_id IS NULL OR NEW.login_user_id <> OLD.login_user_id) - THEN - NEW.login_user_id_set_date = NOW(); - NEW.login_user_id_last_revalidate = NOW(); - ELSIF NEW.login_user_id IS NULL OR NEW.login_user_id = '' THEN - NEW.login_user_id_set_date = NULL; - NEW.login_user_id_last_revalidate = NULL; - END IF; - RETURN NEW; + -- if new is not null/empty + -- and old one is null or old one different new one + -- set NOW() + -- if new one is NULL + -- set NULL + IF + NEW.login_user_id IS NOT NULL AND NEW.login_user_id <> '' AND + (OLD.login_user_id IS NULL OR NEW.login_user_id <> OLD.login_user_id) + THEN + NEW.login_user_id_set_date = NOW(); + NEW.login_user_id_last_revalidate = NOW(); + ELSIF NEW.login_user_id IS NULL OR NEW.login_user_id = '' THEN + NEW.login_user_id_set_date = NULL; + NEW.login_user_id_last_revalidate = NULL; + END IF; + RETURN NEW; END; $$ LANGUAGE 'plpgsql'; @@ -290,8 +291,8 @@ LANGUAGE 'plpgsql'; -- DROP TABLE temp_files; CREATE TABLE temp_files ( - filename VARCHAR, - folder VARCHAR + filename VARCHAR, + folder VARCHAR ); -- END: table/edit_temp_files.sql -- START: table/edit_generic.sql @@ -304,9 +305,10 @@ CREATE TABLE temp_files ( -- DROP TABLE edit_generic; CREATE TABLE edit_generic ( - cuid VARCHAR, - date_created TIMESTAMP WITHOUT TIME ZONE DEFAULT clock_timestamp(), - date_updated TIMESTAMP WITHOUT TIME ZONE + cuid VARCHAR, + cuuid UUID, + date_created TIMESTAMP WITHOUT TIME ZONE DEFAULT clock_timestamp(), + date_updated TIMESTAMP WITHOUT TIME ZONE ); -- END: table/edit_generic.sql -- START: table/edit_visible_group.sql @@ -319,9 +321,9 @@ CREATE TABLE edit_generic ( -- DROP TABLE edit_visible_group; CREATE TABLE edit_visible_group ( - edit_visible_group_id SERIAL PRIMARY KEY, - name VARCHAR, - flag VARCHAR + edit_visible_group_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR, + flag VARCHAR ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_visible_group.sql -- START: table/edit_menu_group.sql @@ -334,10 +336,10 @@ CREATE TABLE edit_visible_group ( -- DROP TABLE edit_menu_group; CREATE TABLE edit_menu_group ( - edit_menu_group_id SERIAL PRIMARY KEY, - name VARCHAR, - flag VARCHAR, - order_number INT NOT NULL + edit_menu_group_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR, + flag VARCHAR, + order_number INT NOT NULL ) INHERITS (edit_generic) WITHOUT OIDS; @@ -352,18 +354,18 @@ CREATE TABLE edit_menu_group ( -- DROP TABLE edit_page; CREATE TABLE edit_page ( - edit_page_id SERIAL 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, - name VARCHAR UNIQUE, - order_number INT NOT NULL, - online SMALLINT NOT NULL DEFAULT 0, - menu SMALLINT NOT NULL DEFAULT 0, - popup SMALLINT NOT NULL DEFAULT 0, - popup_x SMALLINT, - popup_y SMALLINT, - hostname VARCHAR + 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, + name VARCHAR UNIQUE, + order_number INT NOT NULL, + online SMALLINT NOT NULL DEFAULT 0, + menu SMALLINT NOT NULL DEFAULT 0, + popup SMALLINT NOT NULL DEFAULT 0, + popup_x SMALLINT, + popup_y SMALLINT, + hostname VARCHAR ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_page.sql -- START: table/edit_query_string.sql @@ -376,13 +378,13 @@ CREATE TABLE edit_page ( -- DROP TABLE edit_query_string; CREATE TABLE edit_query_string ( - edit_query_string_id SERIAL 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, - name VARCHAR, - value VARCHAR, - dynamic SMALLINT NOT NULL DEFAULT 0 + 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, + name VARCHAR, + value VARCHAR, + dynamic SMALLINT NOT NULL DEFAULT 0 ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_query_string.sql -- START: table/edit_page_visible_group.sql @@ -395,10 +397,10 @@ CREATE TABLE edit_query_string ( -- DROP TABLE edit_page_visible_group; CREATE TABLE edit_page_visible_group ( - 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_visible_group_id INT NOT NULL, - FOREIGN KEY (edit_visible_group_id) REFERENCES edit_visible_group (edit_visible_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE + 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_visible_group_id INT NOT NULL, + FOREIGN KEY (edit_visible_group_id) REFERENCES edit_visible_group (edit_visible_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE ); -- END: table/edit_page_visible_group.sql -- START: table/edit_page_menu_group.sql @@ -411,10 +413,10 @@ CREATE TABLE edit_page_visible_group ( -- DROP TABLE edit_page_menu_group; CREATE TABLE edit_page_menu_group ( - 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_menu_group_id INT NOT NULL, - FOREIGN KEY (edit_menu_group_id) REFERENCES edit_menu_group (edit_menu_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE + 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_menu_group_id INT NOT NULL, + FOREIGN KEY (edit_menu_group_id) REFERENCES edit_menu_group (edit_menu_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE ); -- END: table/edit_page_menu_group.sql -- START: table/edit_access_right.sql @@ -428,11 +430,11 @@ CREATE TABLE edit_page_menu_group ( -- DROP TABLE edit_access_right; CREATE TABLE edit_access_right ( - edit_access_right_id SERIAL PRIMARY KEY, - name VARCHAR, - level SMALLINT, - type VARCHAR, - UNIQUE (level,type) + edit_access_right_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name VARCHAR, + level SMALLINT, + type VARCHAR, + UNIQUE (level,type) ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_access_right.sql -- START: table/edit_scheme.sql @@ -445,12 +447,12 @@ CREATE TABLE edit_access_right ( -- DROP TABLE edit_scheme; CREATE TABLE edit_scheme ( - edit_scheme_id SERIAL PRIMARY KEY, - enabled SMALLINT NOT NULL DEFAULT 0, - name VARCHAR, - header_color VARCHAR, - css_file VARCHAR, - template VARCHAR + edit_scheme_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + enabled SMALLINT NOT NULL DEFAULT 0, + name VARCHAR, + header_color VARCHAR, + css_file VARCHAR, + template VARCHAR ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_scheme.sql -- START: table/edit_language.sql @@ -464,13 +466,13 @@ CREATE TABLE edit_scheme ( -- DROP TABLE edit_language; CREATE TABLE edit_language ( - edit_language_id SERIAL PRIMARY KEY, - enabled SMALLINT NOT NULL DEFAULT 0, - lang_default SMALLINT NOT NULL DEFAULT 0, - long_name VARCHAR, - short_name VARCHAR, -- en_US, en or en_US@latin without encoding - iso_name VARCHAR, -- should actually be encoding - order_number INT + 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, + short_name VARCHAR, -- en_US, en or en_US@latin without encoding + iso_name VARCHAR, -- should actually be encoding + order_number INT ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_language.sql -- START: table/edit_group.sql @@ -483,16 +485,16 @@ CREATE TABLE edit_language ( -- DROP TABLE edit_group; CREATE TABLE edit_group ( - edit_group_id SERIAL 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, - FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, - enabled SMALLINT NOT NULL DEFAULT 0, - deleted SMALLINT DEFAULT 0, - uid VARCHAR, - name VARCHAR, - additional_acl JSONB + 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, + FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + enabled SMALLINT NOT NULL DEFAULT 0, + deleted SMALLINT DEFAULT 0, + uid VARCHAR, + name VARCHAR, + additional_acl JSONB ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_group.sql -- START: table/edit_page_access.sql @@ -505,14 +507,14 @@ CREATE TABLE edit_group ( -- DROP TABLE edit_page_access; CREATE TABLE edit_page_access ( - edit_page_access_id SERIAL 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, - 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, - FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, - enabled SMALLINT NOT NULL DEFAULT 0 + 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, + 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, + FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + enabled SMALLINT NOT NULL DEFAULT 0 ) INHERITS (edit_generic) WITHOUT OIDS; @@ -528,15 +530,15 @@ CREATE TABLE edit_page_access ( -- DROP TABLE edit_page_content; CREATE TABLE edit_page_content ( - edit_page_content_id SERIAL 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, - FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, - uid VARCHAR UNIQUE, - name VARCHAR, - order_number INT NOT NULL, - online SMALLINT NOT NULL DEFAULT 0 + 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, + FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + uid VARCHAR UNIQUE, + name VARCHAR, + order_number INT NOT NULL, + online SMALLINT NOT NULL DEFAULT 0 ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_page_content.sql -- START: table/edit_user.sql @@ -549,63 +551,62 @@ CREATE TABLE edit_page_content ( -- DROP TABLE edit_user; CREATE TABLE edit_user ( - edit_user_id SERIAL 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, - FOREIGN KEY (edit_language_id) REFERENCES edit_language (edit_language_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, - 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_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, - FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, - -- username/password - username VARCHAR UNIQUE, - password VARCHAR, - -- name block - first_name VARCHAR, - last_name VARCHAR, - first_name_furigana VARCHAR, - last_name_furigana VARCHAR, - -- email - email VARCHAR, - -- eanbled/deleted flag - enabled SMALLINT NOT NULL DEFAULT 0, - deleted SMALLINT NOT NULL DEFAULT 0, - -- general flags - 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 - last_login TIMESTAMP WITHOUT TIME ZONE, - -- login error - login_error_count INT DEFAULT 0, - login_error_date_last TIMESTAMP WITHOUT TIME ZONE, - login_error_date_first TIMESTAMP WITHOUT TIME ZONE, - -- time locked - lock_until TIMESTAMP WITHOUT TIME ZONE, - lock_after TIMESTAMP WITHOUT TIME ZONE, - -- password change - password_change_date TIMESTAMP WITHOUT TIME ZONE, -- only when password is first set or changed - password_change_interval INTERVAL, -- null if no change is needed, or d/m/y time interval - password_reset_time TIMESTAMP WITHOUT TIME ZONE, -- when the password reset was requested - password_reset_uid VARCHAR, -- the uid to access the password reset page - -- _GET login id for direct login - login_user_id VARCHAR UNIQUE, -- the loginUserId, at least 32 chars - login_user_id_set_date TIMESTAMP WITHOUT TIME ZONE, -- when above uid was set - login_user_id_last_revalidate TIMESTAMP WITHOUT TIME ZONE, -- when the last login was done with user name and password - login_user_id_valid_from TIMESTAMP WITHOUT TIME ZONE, -- if set, from when the above uid is valid - login_user_id_valid_until TIMESTAMP WITHOUT TIME ZONE, -- if set, until when the above uid is valid - login_user_id_revalidate_after INTERVAL, -- user must login to revalidated loginUserId after set days, 0 for forever - login_user_id_locked SMALLINT DEFAULT 0, -- lock for loginUserId, but still allow normal login - -- additional ACL json block - additional_acl JSONB -- additional ACL as JSON string (can be set by other pages) + 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, + FOREIGN KEY (edit_language_id) REFERENCES edit_language (edit_language_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + 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_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, + FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + -- username/password + username VARCHAR UNIQUE, + password VARCHAR, + -- name block + first_name VARCHAR, + last_name VARCHAR, + first_name_furigana VARCHAR, + last_name_furigana VARCHAR, + -- email + email VARCHAR, + -- eanbled/deleted flag + enabled SMALLINT NOT NULL DEFAULT 0, + deleted SMALLINT NOT NULL DEFAULT 0, + -- general flags + strict SMALLINT DEFAULT 0, + locked SMALLINT DEFAULT 0, + 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 + login_error_count INT DEFAULT 0, + login_error_date_last TIMESTAMP WITHOUT TIME ZONE, + login_error_date_first TIMESTAMP WITHOUT TIME ZONE, + -- time locked + lock_until TIMESTAMP WITHOUT TIME ZONE, + lock_after TIMESTAMP WITHOUT TIME ZONE, + -- password change + password_change_date TIMESTAMP WITHOUT TIME ZONE, -- only when password is first set or changed + password_change_interval INTERVAL, -- null if no change is needed, or d/m/y time interval + password_reset_time TIMESTAMP WITHOUT TIME ZONE, -- when the password reset was requested + password_reset_uid VARCHAR, -- the uid to access the password reset page + -- _GET login id for direct login + login_user_id VARCHAR UNIQUE, -- the loginUserId, at least 32 chars + login_user_id_set_date TIMESTAMP WITHOUT TIME ZONE, -- when above uid was set + login_user_id_last_revalidate TIMESTAMP WITHOUT TIME ZONE, -- when the last login was done with user name and password + login_user_id_valid_from TIMESTAMP WITHOUT TIME ZONE, -- if set, from when the above uid is valid + login_user_id_valid_until TIMESTAMP WITHOUT TIME ZONE, -- if set, until when the above uid is valid + login_user_id_revalidate_after INTERVAL, -- user must login to revalidated loginUserId after set days, 0 for forever + login_user_id_locked SMALLINT DEFAULT 0, -- lock for loginUserId, but still allow normal login + -- additional ACL json block + additional_acl JSONB -- additional ACL as JSON string (can be set by other pages) ) INHERITS (edit_generic) WITHOUT OIDS; -- create unique index @@ -618,8 +619,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'; @@ -650,37 +649,56 @@ 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, - 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, - event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, - ip VARCHAR, - error TEXT, - event TEXT, - data_binary BYTEA, - data TEXT, - page VARCHAR, - action VARCHAR, - action_id VARCHAR, - action_yes VARCHAR, - action_flag VARCHAR, - action_menu VARCHAR, - action_loaded VARCHAR, - action_value VARCHAR, - action_type VARCHAR, - action_error VARCHAR, - user_agent VARCHAR, - referer VARCHAR, - script_name VARCHAR, - query_string VARCHAR, - server_name VARCHAR, - http_host VARCHAR, - http_accept VARCHAR, - http_accept_charset VARCHAR, - http_accept_encoding VARCHAR, - session_id VARCHAR + 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, + 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 + 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, + referer VARCHAR, + script_name VARCHAR, + query_string VARCHAR, + request_scheme VARCHAR, -- http or https + 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 + -- 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 + 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 @@ -707,15 +725,15 @@ 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, - enabled SMALLINT NOT NULL DEFAULT 0, - protected SMALLINT DEFAULT 0, - deleted SMALLINT DEFAULT 0, - uid VARCHAR, - name VARCHAR UNIQUE, - description VARCHAR, - color VARCHAR, - additional_acl JSONB + edit_access_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + enabled SMALLINT NOT NULL DEFAULT 0, + protected SMALLINT DEFAULT 0, + deleted SMALLINT DEFAULT 0, + uid VARCHAR, + name VARCHAR UNIQUE, + description VARCHAR, + color VARCHAR, + additional_acl JSONB ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_access.sql -- START: table/edit_access_user.sql @@ -728,15 +746,15 @@ CREATE TABLE edit_access ( -- DROP TABLE edit_access_user; CREATE TABLE edit_access_user ( - edit_access_user_id SERIAL 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, - FOREIGN KEY (edit_user_id) REFERENCES edit_user (edit_user_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, - edit_access_right_id INT NOT NULL, - FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, - edit_default SMALLINT DEFAULT 0, - enabled SMALLINT NOT NULL DEFAULT 0 + 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, + FOREIGN KEY (edit_user_id) REFERENCES edit_user (edit_user_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_access_right_id INT NOT NULL, + FOREIGN KEY (edit_access_right_id) REFERENCES edit_access_right (edit_access_right_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE, + edit_default SMALLINT DEFAULT 0, + enabled SMALLINT NOT NULL DEFAULT 0 ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_access_user.sql -- START: table/edit_access_data.sql @@ -749,12 +767,12 @@ CREATE TABLE edit_access_user ( -- DROP TABLE edit_access_data; CREATE TABLE edit_access_data ( - edit_access_data_id SERIAL 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, - name VARCHAR, - value VARCHAR + 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, + name VARCHAR, + value VARCHAR ) INHERITS (edit_generic) WITHOUT OIDS; -- create a unique index for each attached data block for each edit access can @@ -1010,7 +1028,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/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/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/4dev/tests/Convert/CoreLibsConvertColorTest.php b/4dev/tests/Convert/CoreLibsConvertColorTest.php new file mode 100644 index 00000000..d95d51b2 --- /dev/null +++ b/4dev/tests/Convert/CoreLibsConvertColorTest.php @@ -0,0 +1,1186 @@ + round($v), + $values + ); + } + + // MARK: single test + + public function testSingle() + { + $this->assertTrue(true, 'Single test'); + // $rgb = new Color\Coordinates\RGB([0, 0, 60]); + // print "IN: " . print_r($rgb, true) . "\n"; + // $hsl = Color\Color::rgbToHsl($rgb); + // print "to HSL: " . print_r($hsl, true) . "\n"; + // $hsb = Color\Color::hslToHsb($hsl); + // print "to HSB: " . print_r($hsb, true) . "\n"; + // $hwb = Color\Color::hsbToHwb($hsb); + // print "to HWB: " . print_r($hwb, true) . "\n"; + // // and reverse + // $hsb_r = Color\Color::hwbToHsb($hwb); + // print "R to HSB: " . print_r($hsb_r, true) . "\n"; + // $hsl_r = Color\Color::hsbToHsl($hsb_r); + // print "R to HSB: " . print_r($hsl_r, true) . "\n"; + // $rgb_r = Color\Color::hslToRgb($hsl_r); + // print "R to RGB: " . print_r($rgb_r, true) . "\n"; + + // $hsl = new Color\Coordinates\HSL([0, 0, 0]); + // print "IN HSL: " . print_r($hsl, true) . "\n"; + // $hsb = Color\Color::hslToHsb($hsl); + // print "to HSB: " . print_r($hsb, true) . "\n"; + // $hwb = Color\Color::hsbToHwb($hsb); + // print "to HWB: " . print_r($hwb, true) . "\n"; + // // and reverse + // $hsb_r = Color\Color::hwbToHsb($hwb); + // print "R to HSB: " . print_r($hsb_r, true) . "\n"; + // $hsl_r = Color\Color::hsbToHsl($hsb_r); + // print "R to HSL: " . print_r($hsl_r, true) . "\n"; + // print "--------\n"; + // $hsb = new Color\Coordinates\HSB([0, 20, 0]); + // print "IN HSB: " . print_r($hsb, true) . "\n"; + // $hsl = Color\Color::hsbToHsl($hsb); + // print "to HSL: " . print_r($hsl, true) . "\n"; + // $hwb = Color\Color::hslToHwb($hsl); + // print "to HWB: " . print_r($hwb, true) . "\n"; + // // and reverse + // $hsl_r = Color\Color::hwbToHsl($hwb); + // print "R to HSB: " . print_r($hsb_r, true) . "\n"; + // $hsb_r = Color\Color::hslToHsb($hsl_r); + // print "R to HSL: " . print_r($hsb_r, true) . "\n"; + // print "--------\n"; + // $hwb = new Color\Coordinates\HWB([0, 20, 100]); + // print "IN: " . print_r($hwb, true) . "\n"; + // $hsl = Color\Color::hwbToHsl($hwb); + // print "to HSL: " . print_r($hsl, true) . "\n"; + // $hwb_r = Color\Color::hslToHwb($hsl); + // print "HSL to HWB: " . print_r($hwb_r, true) . "\n"; + // $hsb = Color\Color::hwbToHsb($hwb); + // print "to HSB: " . print_r($hsb, true) . "\n"; + // $hwb_r = Color\Color::hsbToHwb($hsb); + // print "HSL to HWB: " . print_r($hwb_r, true) . "\n"; + } + + // MARK: RGB base + + /** + * From/To RGB <-> ... conversion tests + * + * @covers ::rgbToHsb + * @covers ::rgbToHsl + * @covers ::rgbToHwb + * @covers ::hsbToRgb + * @covers ::hslToRgb + * @covers ::hwebToRgb + * @testdox Convert from and to RGB via HSL, HWB, HSB/V + * + * @return void + */ + public function testRgbColorCoordinateConvertToAndBack(): void + { + for ($r = 0; $r <= 300; $r += 60) { + for ($g = 0; $g <= 300; $g += 60) { + for ($b = 0; $b <= 300; $b += 60) { + // for this test we stay in the correct lane + if ($r > 255) { + $r = 255; + } + if ($g > 255) { + $g = 255; + } + if ($b > 255) { + $b = 255; + } + // base is always the same + $color = new Color\Coordinates\RGB([$r, $g, $b]); + $base = 'rgb'; + foreach (['hsb', 'hsl', 'hwb'] as $coord) { + // print "COORD: " . $coord . ", RGB: " . print_r($color->returnAsArray(), true) . "\n"; + // rgb to X and back must be same + $target = $base . 'To' . ucfirst($coord); + $source = $coord . 'To' . ucfirst($base); + $converted_color = Color\Color::$target($color); + $color_b = Color\Color::$source($converted_color); + // $converted_color = Color\Color::rgbToHsb($color); + // $rgb_b = Color\Color::hsbToRgb($converted_color); + $this->assertEqualsWithDelta( + $color->returnAsArray(), + $color_b->returnAsArray(), + self::DELTA, + 'Convert ' . $base . ' to ' . $coord . ': ' . print_r($color->returnAsArray(), true) . '/' + . print_r($color_b->returnAsArray(), true) + ); + } + } + } + } + } + + // HSL / HSB / HWB conversion are not reversable if + // HSL: lightness 0 or 100 + // HSB: saturation or brightness 0 + // HWB: blackness >= 80 and whitness >= 20 or B>=20 & W>=20 or B>=50 & W>=50 + + // MARK: HSL base + + /** + * Undocumented function + * + * @covers ::hslToHsb + * @covers ::hsbToHsl + * @covers ::hslToHwb + * @covers ::hwbToHsl + * @testdox Convert from and to HSL via RGB, HWB, HSB/V + * + * @return void + */ + public function testHslColorCoordinateConvertToAndBack(): void + { + for ($H = 0; $H <= 360; $H += 60) { + for ($S = 0; $S <= 100; $S += 20) { + for ($L = 0; $L <= 100; $L += 20) { + // if lightness 0 or 100 then we cannot reverse (B/W) + if (($L == 0 or $L == 100)) { + continue; + } + $color = new Color\Coordinates\HSL([$H, $S, $L]); + $base = 'hsl'; + foreach (['hsb', 'hwb', 'rgb'] as $coord) { + // for rgb hue on S = 0 is irrelevant (B/W) + if ($H > 0 && $coord == 'rgb') { + continue; + } + $target = $base . 'To' . ucfirst($coord); + $source = $coord . 'To' . ucfirst($base); + $converted_color = Color\Color::$target($color); + $color_b = Color\Color::$source($converted_color); + // print "COORD: " . $coord . ", HSL: " . print_r($color->returnAsArray(), true) . "\n"; + $this->assertEqualsWithDelta( + $color->returnAsArray(), + $color_b->returnAsArray(), + self::DELTA, + 'Convert HSL to ' . $coord . ': ' . print_r($color->returnAsArray(), true) . '/' + . print_r($color_b->returnAsArray(), true) + ); + } + } + } + } + } + + // MARK: HSB + + /** + * Undocumented function + * + * @covers ::hsbToHsl + * @covers ::hslToHsb + * @covers ::hsbToHwb + * @covers ::hwbToHsb + * @testdox Convert from and to HSB via RGB, HWB, HSL + * + * @return void + */ + public function testHsbColorCoordinateConvertToAndBack(): void + { + for ($H = 0; $H <= 360; $H += 60) { + for ($S = 0; $S <= 100; $S += 20) { + for ($B = 0; $B <= 100; $B += 20) { + // if sat or brightness is 0 then we cannot reverse correctly (B/W) + if ($S == 0 or $B == 0) { + continue; + } + $color = new Color\Coordinates\HSB([$H, $S, $B]); + $base = 'hsb'; + foreach (['hwb', 'hsl', 'rgb'] as $coord) { + $target = $base . 'To' . ucfirst($coord); + $source = $coord . 'To' . ucfirst($base); + $converted_color = Color\Color::$target($color); + $color_b = Color\Color::$source($converted_color); + // print "COORD: " . $coord . ", HSL: " . print_r($color->returnAsArray(), true) . "\n"; + $this->assertEqualsWithDelta( + $color->returnAsArray(), + $color_b->returnAsArray(), + self::DELTA, + 'Convert ' . $base . ' to ' . $coord . ': ' . print_r($color->returnAsArray(), true) . '/' + . print_r($color_b->returnAsArray(), true) + ); + } + } + } + } + } + + // MARK: HWB + + /** + * Undocumented function + * + * @covers ::hwbToHsl + * @covers ::hslToHwb + * @covers ::hwbToHsb + * @covers ::hsbToHwb + * @testdox Convert from and to HWB via RGB, HSL, HSB/V + * + * @return void + */ + public function testHwbColorCoordinateConvertToAndBack(): void + { + for ($H = 0; $H <= 360; $H += 60) { + for ($W = 0; $W <= 100; $W += 20) { + for ($B = 0; $B <= 100; $B += 20) { + // if W>=20 and B>=80 or B>=20 and W>=20 or both >=50 + // we cannot reverse correctl (B/W) + if ( + ($W >= 20 && $B >= 80) || + ($W >= 80 && $B >= 20) || + ($W >= 50 && $B >= 50) + ) { + continue; + } + $base = 'hwb'; + $color = new Color\Coordinates\HWB([$H, $W, $B]); + foreach (['hsl', 'hsb', 'rgb'] as $coord) { + // for rgb hue on S = 0 is irrelevant (B/W) + if ($H > 0 && $coord == 'rgb') { + continue; + } + $target = $base . 'To' . ucfirst($coord); + $source = $coord . 'To' . ucfirst($base); + $converted_color = Color\Color::$target($color); + $color_b = Color\Color::$source($converted_color); + // print "COORD: " . $coord . ", HSL: " . print_r($color->returnAsArray(), true) . "\n"; + $this->assertEqualsWithDelta( + $color->returnAsArray(), + $color_b->returnAsArray(), + self::DELTA, + 'Convert ' . $base . ' to ' . $coord . ': ' . print_r($color->returnAsArray(), true) . '/' + . print_r($color_b->returnAsArray(), true) + ); + } + } + } + } + } + + // MARK: RGB to hex + + /** + * Undocumented function + * + * @covers ::returnAsHex() + * @testdox Convert from and to RGB via hex + * + * @return void + */ + public function testRgbToFromHex(): void + { + for ($r = 0; $r <= 300; $r += 60) { + for ($g = 0; $g <= 300; $g += 60) { + for ($b = 0; $b <= 300; $b += 60) { + // for this test we stay in the correct lane + if ($r > 255) { + $r = 255; + } + if ($g > 255) { + $g = 255; + } + if ($b > 255) { + $b = 255; + } + // with or without prefix + foreach ([true, false] as $hex_prefix) { + $hex_color = (new Color\Coordinates\RGB([$r, $g, $b])) + ->returnAsHex($hex_prefix); + // parse into hex to rgb and see if we get the same r/g/b + $color = (new Color\Coordinates\RGB($hex_color))->returnAsArray(); + // + $this->assertEquals( + [$r, $g, $b], + $color, + 'Convert rgb to hex and back: ' . print_r([$r, $g, $b], true) . '/' + . print_r($color, true) + ); + } + } + } + } + } + + // MARK: RGB Linear + + /** + * linear RGB conversion tests + * + * @covers ::fromLinear + * @covers ::toLinear + * @testdox Convert from and to RGB linear conversion check + * + * @return void + */ + public function testRgbFromToLinear() + { + $rgb = (new Color\Coordinates\RGB([10, 20, 30]))->toLinear(); + $this->assertEquals( + true, + $rgb->get('linear'), + 'On create flagged linear missing' + ); + $rgb_color = $rgb->returnAsArray(); + $rgb->toLinear(); + $this->assertEquals( + $rgb_color, + $rgb->returnAsArray(), + 'Double linear call does double linear encoding' + ); + $rgb->fromLinear(); + $this->assertEquals( + false, + $rgb->get('linear'), + 'On reverse linear, flag is missing' + ); + $rgb_color = $rgb->returnAsArray(); + $this->assertEquals( + $rgb_color, + $rgb->returnAsArray(), + 'Double linear inverse call does double linear decoding' + ); + $rgb = new Color\Coordinates\RGB([20, 30, 40]); + $rgb_color = $rgb->returnAsArray(); + $this->assertEquals( + false, + $rgb->get('linear'), + 'On create without linear flag is linear' + ); + $rgb->toLinear(); + $this->assertEquals( + true, + $rgb->get('linear'), + 'On linear call flag is not linear' + ); + $rgb->fromLinear(); + $this->assertEquals( + $rgb_color, + $rgb->returnAsArray(), + 'conversion to and from linear not matching' + ); + } + + // MARK: okLab + + /** + * From/To RGB <-> OkLab / OkLch + * + * @covers ::rgbToOkLab + * @covers ::rgbToOkLch + * @covers ::okLabToRgb + * @covers ::okLchToRgb + * @testdox Convert from and to RGB to OkLab / OkLch + * + * @return void + */ + public function testRgbColorCoordinateConvertToAndBackBackOkLab() + { + for ($r = 0; $r <= 300; $r += 60) { + for ($g = 0; $g <= 300; $g += 60) { + for ($b = 0; $b <= 300; $b += 60) { + // for this test we stay in the correct lane + if ($r > 255) { + $r = 255; + } + if ($g > 255) { + $g = 255; + } + if ($b > 255) { + $b = 255; + } + // base is always the same + $color = new Color\Coordinates\RGB([$r, $g, $b]); + $base = 'rgb'; + foreach (['okLab', 'okLch'] as $coord) { + // print "COORD: " . $coord . ", RGB: " . print_r($color->returnAsArray(), true) . "\n"; + // rgb to X and back must be same + $target = $base . 'To' . ucfirst($coord); + $source = $coord . 'To' . ucfirst($base); + $converted_color = Color\Color::$target($color); + $color_b = Color\Color::$source($converted_color); + // $converted_color = Color\Color::rgbToHsb($color); + // $rgb_b = Color\Color::hsbToRgb($converted_color); + $this->assertEqualsWithDelta( + $color->returnAsArray(), + $color_b->returnAsArray(), + self::DELTA_OKLAB, + 'Convert ' . $base . ' to ' . $coord . ': ' . print_r($color->returnAsArray(), true) . '/' + . print_r($color_b->returnAsArray(), true) + ); + } + } + } + } + } + + /** + * internal oklab/oklch conversion + * + * @covers ::okLchToOkLab + * @covers ::okLabToOkLch + * @testdox Convert from and to OkLab / OkLch + * + * @return void + */ + public function testOkLabOkLchColorCoordinateConvertToFrom() + { + for ($L = 0.0; $L <= 1.0; $L += 0.2) { + for ($C = 0.0; $C <= 0.5; $C += 0.1) { + for ($H = 0.0; $H <= 360.0; $H += 60.0) { + // chroma 0.0 is B/W skip it + if ($C == 0.0) { + continue; + } + $color = new Color\Coordinates\LCH([$L, $C, $H], 'OkLab'); + $base = 'okLch'; + foreach (['okLab'] as $coord) { + // rgb to X and back must be same + $target = $base . 'To' . ucfirst($coord); + $source = $coord . 'To' . ucfirst($base); + $converted_color = Color\Color::$target($color); + $color_b = Color\Color::$source($converted_color); + // $converted_color = Color\Color::rgbToHsb($color); + // $rgb_b = Color\Color::hsbToRgb($converted_color); + $this->assertEqualsWithDelta( + $color->returnAsArray(), + $color_b->returnAsArray(), + self::DELTA, + 'Convert ' . $base . ' to ' . $coord . ': ' . print_r($color->returnAsArray(), true) . '/' + . print_r($color_b->returnAsArray(), true) + ); + } + } + } + } + } + + // MARK: CIELab + + /** + * From/To RGB <-> Cie lab / Cie lch + * + * @covers ::rgbToLab + * @covers ::rgbToLch + * @covers ::labToRgb + * @covers ::lchToRgb + * @testdox Convert from and to RGB to Cie Lab / Cie Lch + * + * @return void + */ + public function testRgbColorCoordinateConvertToAndBackBackCieLab() + { + for ($r = 0; $r <= 300; $r += 60) { + for ($g = 0; $g <= 300; $g += 60) { + for ($b = 0; $b <= 300; $b += 60) { + // for this test we stay in the correct lane + if ($r > 255) { + $r = 255; + } + if ($g > 255) { + $g = 255; + } + if ($b > 255) { + $b = 255; + } + // base is always the same + $color = new Color\Coordinates\RGB([$r, $g, $b]); + $base = 'rgb'; + foreach (['lab', 'lch'] as $coord) { + // print "COORD: " . $coord . ", RGB: " . print_r($color->returnAsArray(), true) . "\n"; + // rgb to X and back must be same + $target = $base . 'To' . ucfirst($coord); + $source = $coord . 'To' . ucfirst($base); + $converted_color = Color\Color::$target($color); + $color_b = Color\Color::$source($converted_color); + // $converted_color = Color\Color::rgbToHsb($color); + // $rgb_b = Color\Color::hsbToRgb($converted_color); + $this->assertEqualsWithDelta( + $color->returnAsArray(), + $color_b->returnAsArray(), + self::DELTA_OKLAB, + 'Convert ' . $base . ' to ' . $coord . ': ' . print_r($color->returnAsArray(), true) . '/' + . print_r($color_b->returnAsArray(), true) + ); + } + } + } + } + } + + /** + * internal cie lab/cie lch conversion + * + * @covers ::lchToLab + * @covers ::labToLch + * @testdox Convert from and to Cie Lab / Cie Lch + * + * @return void + */ + public function testLabLchColorCoordinateConvertToFrom() + { + for ($L = 0.0; $L <= 1.0; $L += 0.2) { + for ($C = 0.0; $C <= 0.5; $C += 0.1) { + for ($H = 0.0; $H <= 360.0; $H += 60.0) { + // chroma 0.0 is B/W skip it + if ($C == 0.0) { + continue; + } + $color = new Color\Coordinates\LCH([$L, $C, $H], 'OkLab'); + $base = 'lch'; + foreach (['lab'] as $coord) { + // rgb to X and back must be same + $target = $base . 'To' . ucfirst($coord); + $source = $coord . 'To' . ucfirst($base); + $converted_color = Color\Color::$target($color); + $color_b = Color\Color::$source($converted_color); + // $converted_color = Color\Color::rgbToHsb($color); + // $rgb_b = Color\Color::hsbToRgb($converted_color); + $this->assertEqualsWithDelta( + $color->returnAsArray(), + $color_b->returnAsArray(), + self::DELTA, + 'Convert ' . $base . ' to ' . $coord . ': ' . print_r($color->returnAsArray(), true) . '/' + . print_r($color_b->returnAsArray(), true) + ); + } + } + } + } + } + + // MARK: Exceptions + + /** + * Undocumented function + * + * @return array + */ + public function providerHueBased(): array + { + // all HSB/V HSL HWB have the same value range, create test data for all of them + return [ + 'H' => [ + 'color' => [900, 10, 10], + 'error_code' => 1, + 'error_string' => '/ for hue is not in the range of 0 to 360$/' + ], + 'H' => [ + 'color' => [-1, 10, 10], 'error_code' => 1, + 'error_string' => '/ for hue is not in the range of 0 to 360$/', + ], + 'H close' => [ + 'color' => [360.1, 10, 10], + 'error_code' => 1, + 'error_string' => '/ for hue is not in the range of 0 to 360$/' + ], + 'H close' => [ + 'color' => [-0.1, 10, 10], 'error_code' => 1, + 'error_string' => '/ for hue is not in the range of 0 to 360$/', + ], + 'S/W' => [ + 'color' => [90, 900, 10], 'error_code' => 2, + 'error_string' => 'is not in the range of 0 to 100', + ], + 'S/W' => [ + 'color' => [90, -1, 10], 'error_code' => 2, + 'error_string' => 'is not in the range of 0 to 100', + ], + 'S/W close' => [ + 'color' => [90, 100.1, 10], 'error_code' => 2, + 'error_string' => 'is not in the range of 0 to 100', + ], + 'S/W close' => [ + 'color' => [90, -0.1, 10], 'error_code' => 2, + 'error_string' => 'is not in the range of 0 to 100', + ], + 'L/B' => [ + 'color' => [90, 10, 900], 'error_code' => 3, + 'error_string' => 'is not in the range of 0 to 100', + ], + 'L/B' => [ + 'color' => [90, 10, -1], 'error_code' => 3, + 'error_string' => 'is not in the range of 0 to 100', + ], + 'L/B close' => [ + 'color' => [90, 10, 100.1], 'error_code' => 3, + 'error_string' => 'is not in the range of 0 to 100', + ], + 'L/B close' => [ + 'color' => [90, 10, -0.1], 'error_code' => 3, + 'error_string' => 'is not in the range of 0 to 100', + ], + ]; + } + + // MARK: HSB Exceptions + + /** + * Undocumented function + * + * @dataProvider providerHueBased + * @testdox Exception handling for HSB for error $error_code [$_dataName] + * + * @param array $color + * @param int $error_code + * @param string $error_string + * @return void + */ + public function testExceptionHSB(array $color, int $error_code, string $error_string): void + { + // error string based on code + switch ($error_code) { + case 2: + $error_string = "/ for saturation $error_string$/"; + break; + case 3: + $error_string = "/ for brightness $error_string$/"; + break; + } + // for H/S/B exception the same + $this->expectException(\LengthException::class); + $this->expectExceptionCode($error_code); + $this->expectExceptionMessageMatches($error_string); + new Color\Coordinates\HSB($color); + } + + /** + * Undocumented function + * + * @testdox Exception handling for HSB general calls + * + * @return void + */ + public function testExceptionHSBGeneral() + { + // allow + $b = new Color\Coordinates\HSB([0, 0, 0], 'sRGB'); + // invalid access to class + $b = new Color\Coordinates\HSB([0, 0, 0]); + $this->expectException(\ErrorException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Creation of dynamic property is not allowed"); + $b->get('o'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Only array colors allowed"); + new Color\Coordinates\HSB('string'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Not allowed colorspace"); + new Color\Coordinates\HSB([0, 0, 0], 'FOO_BAR'); + } + + // MARK: HSL Exceptions + + /** + * Undocumented function + * + * @dataProvider providerHueBased + * @testdox Exception handling for HSL for error $error_code [$_dataName] + * + * @param array $color + * @param int $error_code + * @param string $error_string + * @return void + */ + public function testExceptionHSL(array $color, int $error_code, string $error_string): void + { + // error string based on code + switch ($error_code) { + case 2: + $error_string = "/ for saturation $error_string$/"; + break; + case 3: + $error_string = "/ for lightness $error_string$/"; + break; + } + // for H/S/B exception the same + $this->expectException(\LengthException::class); + $this->expectExceptionCode($error_code); + $this->expectExceptionMessageMatches($error_string); + new Color\Coordinates\HSL($color); + } + + /** + * Undocumented function + * + * @testdox Exception handling for HSL general calls + * + * @return void + */ + public function testExceptionHSLGeneral() + { + // allow + $b = new Color\Coordinates\HSL([0, 0, 0], 'sRGB'); + // invalid access to class + $b = new Color\Coordinates\HSL([0, 0, 0]); + $this->expectException(\ErrorException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Creation of dynamic property is not allowed"); + $b->get('o'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Only array colors allowed"); + new Color\Coordinates\HSL('string'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Not allowed colorspace"); + new Color\Coordinates\HSL([0, 0, 0], 'FOO_BAR'); + } + + // MARK: HWB Exceptions + + /** + * Undocumented function + * + * @dataProvider providerHueBased + * @testdox Exception handling for HWB for error $error_code [$_dataName] + * + * @param array $color + * @param int $error_code + * @param string $error_string + * @return void + */ + public function testExceptionHWB(array $color, int $error_code, string $error_string): void + { + // error string based on code + switch ($error_code) { + case 2: + $error_string = "/ for whiteness $error_string$/"; + break; + case 3: + $error_string = "/ for blackness $error_string$/"; + break; + } + // for H/S/B exception the same + $this->expectException(\LengthException::class); + $this->expectExceptionCode($error_code); + $this->expectExceptionMessageMatches($error_string); + new Color\Coordinates\HWB($color); + } + + /** + * Undocumented function + * + * @testdox Exception handling for HWB general calls + * + * @return void + */ + public function testExceptionHWBGeneral() + { + // allow + $b = new Color\Coordinates\HWB([0, 0, 0], 'sRGB'); + // invalid access to class + $b = new Color\Coordinates\HWB([0, 0, 0]); + $this->expectException(\ErrorException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Creation of dynamic property is not allowed"); + $b->get('o'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Only array colors allowed"); + new Color\Coordinates\HWB('string'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Not allowed colorspace"); + new Color\Coordinates\HWB([0, 0, 0], 'FOO_BAR'); + } + + // MARK: RGB Exceptions + + /** + * Undocumented function + * + * @return array + */ + public function providerRgbBased(): array + { + // all HSB/V HSL HWB have the same value range, create test data for all of them + return [ + 'R' => [ + 'color' => [900, 10, 10], + 'error_code' => 1, + 'error_string' => '/ is not in the range of 0 to 255$/', + 'linear' => false, + ], + 'R' => [ + 'color' => [-1, 10, 10], 'error_code' => 1, + 'error_string' => '/ is not in the range of 0 to 255$/', + 'linear' => false, + ], + 'G' => [ + 'color' => [90, 900, 10], 'error_code' => 1, + 'error_string' => '/ is not in the range of 0 to 255$/', + 'linear' => false, + ], + 'G' => [ + 'color' => [90, -1, 10], 'error_code' => 1, + 'error_string' => '/ is not in the range of 0 to 255$/', + 'linear' => false, + ], + 'B' => [ + 'color' => [90, 10, 900], 'error_code' => 1, + 'error_string' => '/ is not in the range of 0 to 255$/', + 'linear' => false, + ], + 'B' => [ + 'color' => [90, 10, -1], 'error_code' => 1, + 'error_string' => '/ is not in the range of 0 to 255$/', + 'linear' => false, + ], + 'R linear' => [ + 'color' => [2, 0.5, 0.5], + 'error_code' => 2, + 'error_string' => '/ is not in the range of 0 to 1 for linear rgb$/', + 'linear' => true, + ], + 'R linear' => [ + 'color' => [-1, 0.5, 0.5], + 'error_code' => 2, + 'error_string' => '/ is not in the range of 0 to 1 for linear rgb$/', + 'linear' => true, + ], + 'G linear' => [ + 'color' => [0.5, 2, 0.5], + 'error_code' => 2, + 'error_string' => '/ is not in the range of 0 to 1 for linear rgb$/', + 'linear' => true, + ], + 'G linear' => [ + 'color' => [0.5, -1, 0.5], + 'error_code' => 2, + 'error_string' => '/ is not in the range of 0 to 1 for linear rgb$/', + 'linear' => true, + ], + 'B linear' => [ + 'color' => [0.5, 0.5, 2], + 'error_code' => 2, + 'error_string' => '/ is not in the range of 0 to 1 for linear rgb$/', + 'linear' => true, + ], + 'B linear' => [ + 'color' => [0.5, 0.5, -1], + 'error_code' => 2, + 'error_string' => '/ is not in the range of 0 to 1 for linear rgb$/', + 'linear' => true, + ], + ]; + } + + /** + * Undocumented function + * + * @dataProvider providerRgbBased + * @testdox Exception handling for RGB for error $error_code [$_dataName] + * + * @param string|array $color + * @param int $error_code + * @param string $error_string + * @param bool $linear + * @return void + */ + public function testExceptionRGB(string|array $color, int $error_code, string $error_string, bool $linear): void + { + // for RGB exception the same + $this->expectException(\LengthException::class); + $this->expectExceptionCode($error_code); + $this->expectExceptionMessageMatches($error_string); + new Color\Coordinates\RGB($color, options: ["linear" => $linear]); + } + + /** + * Undocumented function + * + * @covers ::__get + * @testdox Exception handling for RGB general calls + * + * @return void + */ + public function testExceptionRGBGeneral() + { + // allow + $b = new Color\Coordinates\RGB([0, 0, 0], 'sRGB'); + // invalid access to class + $b = new Color\Coordinates\RGB([0, 0, 0]); + $this->expectException(\ErrorException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Creation of dynamic property is not allowed"); + $b->get('o'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Not allowed colorspace"); + new Color\Coordinates\RGB([0, 0, 0], 'FOO_BAR'); + } + + /** + * Undocumented function + * + * @covers ::setFromHex + * @testdox Exception handling for RGB setFromHex failues + * + * @return void + */ + public function testExceptionRGBFromHex() + { + $color = ''; + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(3); + $this->expectExceptionMessage('hex_string argument cannot be empty'); + new Color\Coordinates\RGB($color); + + $color = 'zshj'; + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(3); + $this->expectExceptionMessage('hex_string argument cannot be empty'); + new Color\Coordinates\RGB($color); + + $color = 'aabff'; + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionCode(4); + $this->expectExceptionMessageMatches('/^Invalid hex_string: /'); + new Color\Coordinates\RGB($color); + } + + // MARK: Lab Exceptions + + /** + * Undocumented function + * + * @return array + */ + public function providerLabBased(): array + { + // all HSB/V HSL HWB have the same value range, create test data for all of them + return [ + 'L CieLab' => [ + 'color' => [900, 10, 10], 'error_code' => 1, + 'error_string' => '/ for lightness is not in the range of 0 to 100 for CIE Lab$/', + 'colorspace' => 'CIELab', + ], + 'L CieLab' => [ + 'color' => [-1, 10, 10], 'error_code' => 1, + 'error_string' => '/ for lightness is not in the range of 0 to 100 for CIE Lab$/', + 'colorspace' => 'CIELab', + ], + 'L OkLab' => [ + 'color' => [900, 0.2, 0.2], 'error_code' => 1, + 'error_string' => '/ for lightness is not in the range of 0.0 to 1.0 for OkLab$/', + 'colorspace' => 'OkLab', + ], + 'L OkLab' => [ + 'color' => [-1, 0.2, 0.2], 'error_code' => 1, + 'error_string' => '/ for lightness is not in the range of 0.0 to 1.0 for OkLab$/', + 'colorspace' => 'OkLab', + ], + 'a CieLab' => [ + 'color' => [90, 900, 10], 'error_code' => 2, + 'error_string' => '/ for a is not in the range of -125 to 125 for CIE Lab$/', + 'colorspace' => 'CIELab', + ], + 'a CieLab' => [ + 'color' => [90, -900, 10], 'error_code' => 2, + 'error_string' => '/ for a is not in the range of -125 to 125 for CIE Lab$/', + 'colorspace' => 'CIELab', + ], + 'a OkLab' => [ + 'color' => [0.5, 900, 0.2], 'error_code' => 2, + 'error_string' => '/ for a is not in the range of -0.5 to 0.5 for OkLab$/', + 'colorspace' => 'OkLab', + ], + 'a OkLab' => [ + 'color' => [0.6, -900, 0.2], 'error_code' => 2, + 'error_string' => '/ for a is not in the range of -0.5 to 0.5 for OkLab$/', + 'colorspace' => 'OkLab', + ], + 'b CieLab' => [ + 'color' => [90, 10, 900], 'error_code' => 3, + 'error_string' => '/ for b is not in the range of -125 to 125 for CIE Lab$/', + 'colorspace' => 'CIELab', + ], + 'b CieLab' => [ + 'color' => [90, 10, -999], 'error_code' => 3, + 'error_string' => '/ for b is not in the range of -125 to 125 for CIE Lab$/', + 'colorspace' => 'CIELab', + ], + 'b OkLab' => [ + 'color' => [0.6, 0.2, 900], 'error_code' => 3, + 'error_string' => '/ for b is not in the range of -0.5 to 0.5 for OkLab$/', + 'colorspace' => 'OkLab', + ], + 'b OkLab' => [ + 'color' => [0.6, 0.2, -999], 'error_code' => 3, + 'error_string' => '/ for b is not in the range of -0.5 to 0.5 for OkLab$/', + 'colorspace' => 'OkLab', + ], + ]; + } + + /** + * Undocumented function + * + * @dataProvider providerLabBased + * @testdox Exception handling for Lab for error $error_code [$_dataName] + * + * @param string|array $color + * @param int $error_code + * @param string $error_string + * @param string $colorspace + * @return void + */ + public function testExceptionLab( + string|array $color, + int $error_code, + string $error_string, + string $colorspace + ): void { + // for RGB exception the same + $this->expectException(\LengthException::class); + $this->expectExceptionCode($error_code); + $this->expectExceptionMessageMatches($error_string); + new Color\Coordinates\Lab($color, colorspace: $colorspace); + } + + /** + * Undocumented function + * + * @covers ::__get + * @testdox Exception handling for Lab general calls + * + * @return void + */ + public function testExceptionLabGeneral() + { + // allow + $b = new Color\Coordinates\Lab([0, 0, 0], 'OkLab'); + // invalid access to class + $b = new Color\Coordinates\Lab([0, 0, 0], 'CIELab'); + $this->expectException(\ErrorException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Creation of dynamic property is not allowed"); + $b->get('o'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Only array colors allowed"); + new Color\Coordinates\Lab('string', 'CIELab'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Not allowed colorspace"); + new Color\Coordinates\Lab([0, 0, 0], 'FOO_BAR'); + } + + // MARK: LCH Exceptions + + // public function testExceptionLch(string|array $color, int $error_code, string $error_string): void + + /** + * Undocumented function + * + * @covers ::__get + * @testdox Exception handling for LCH general calls + * + * @return void + */ + public function testExceptionLchGeneral() + { + // allow + $b = new Color\Coordinates\LCH([0, 0, 0], 'OkLab'); + // invalid access to class + $b = new Color\Coordinates\LCH([0, 0, 0], 'CIELab'); + $this->expectException(\ErrorException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Creation of dynamic property is not allowed"); + $b->get('o'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Only array colors allowed"); + new Color\Coordinates\LCH('string', 'CIELab'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Not allowed colorspace"); + new Color\Coordinates\LCH([0, 0, 0], 'FOO_BAR'); + } + + // MARK: XYZ Exceptions + + // Note, we do not check for value exceptions here + // public function testExceptionXyz(string|array $color, int $error_code, string $error_string): void + + /** + * Undocumented function + * + * @covers ::__get + * @testdox Exception handling for XYZ general calls + * + * @return void + */ + public function testExceptionXyzGeneral() + { + // allow + $b = new Color\Coordinates\XYZ([0, 0, 0], 'CIEXYZ'); + // invalid access to class + $b = new Color\Coordinates\XYZ([0, 0, 0]); + $this->expectException(\ErrorException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Creation of dynamic property is not allowed"); + $b->get('o'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Only array colors allowed"); + new Color\Coordinates\XYZ('string'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(0); + $this->expectExceptionMessage("Not allowed colorspace"); + new Color\Coordinates\XYZ([0, 0, 0], 'FOO_BAR'); + } +} + +// __END__ diff --git a/4dev/tests/Convert/CoreLibsConvertColorsTest.php b/4dev/tests/Convert/CoreLibsConvertColorsTest.php index aecd3f09..5d730055 100644 --- a/4dev/tests/Convert/CoreLibsConvertColorsTest.php +++ b/4dev/tests/Convert/CoreLibsConvertColorsTest.php @@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase; /** * Test class for Convert\Colors * @coversDefaultClass \CoreLibs\Convert\Colors - * @testdox \CoreLibs\Convert\Colors method tests + * @testdox \CoreLibs\Convert\Colors legacy method tests */ final class CoreLibsConvertColorsTest extends TestCase { @@ -21,7 +21,7 @@ final class CoreLibsConvertColorsTest extends TestCase * * @return array */ - public function rgb2hexColorProvider(): array + public function providerRgb2hexColor(): array { return [ 'color' => [ @@ -88,7 +88,7 @@ final class CoreLibsConvertColorsTest extends TestCase * * @return array */ - public function hex2rgbColorProvider(): array + public function providerHex2rgbColor(): array { return [ 'color' => [ @@ -215,7 +215,7 @@ final class CoreLibsConvertColorsTest extends TestCase * * @return array */ - public function rgb2hsbColorProvider(): array + public function providerRgb2hsbColor(): array { $list = []; foreach ($this->rgb2hslAndhsbList() as $name => $values) { @@ -234,7 +234,7 @@ final class CoreLibsConvertColorsTest extends TestCase * * @return array */ - public function hsb2rgbColorProvider(): array + public function providerHsb2rgbColor(): array { $list = []; foreach ($this->rgb2hslAndhsbList() as $name => $values) { @@ -253,7 +253,7 @@ final class CoreLibsConvertColorsTest extends TestCase * * @return array */ - public function rgb2hslColorProvider(): array + public function providerRgb2hslColor(): array { $list = []; foreach ($this->rgb2hslAndhsbList() as $name => $values) { @@ -272,7 +272,7 @@ final class CoreLibsConvertColorsTest extends TestCase * * @return array */ - public function hsl2rgbColorProvider(): array + public function providerHsl2rgbColor(): array { $list = []; foreach ($this->rgb2hslAndhsbList() as $name => $values) { @@ -291,7 +291,7 @@ final class CoreLibsConvertColorsTest extends TestCase * TODO: add cross convert check * * @covers ::rgb2hex - * @dataProvider rgb2hexColorProvider + * @dataProvider providerRgb2hexColor * @testdox rgb2hex $input_r,$input_g,$input_b will be $expected [$_dataName] * * @param int $input_r @@ -342,7 +342,7 @@ final class CoreLibsConvertColorsTest extends TestCase * Undocumented function * * @covers ::hex2rgb - * @dataProvider hex2rgbColorProvider + * @dataProvider providerHex2rgbColor * @testdox hex2rgb $input will be $expected, $expected_str str[,], $expected_str_sep str[$separator] [$_dataName] * * @param string $input @@ -385,7 +385,7 @@ final class CoreLibsConvertColorsTest extends TestCase * Undocumented function * * @covers ::rgb2hsb - * @dataProvider rgb2hsbColorProvider + * @dataProvider providerRgb2hsbColor * @testdox rgb2hsb $input_r,$input_g,$input_b will be $expected [$_dataName] * * @param integer $input_r @@ -409,7 +409,7 @@ final class CoreLibsConvertColorsTest extends TestCase * Undocumented function * * @covers ::hsb2rgb - * @dataProvider hsb2rgbColorProvider + * @dataProvider providerHsb2rgbColor * @testdox hsb2rgb $input_h,$input_s,$input_b will be $expected [$_dataName] * * @param float $input_h @@ -434,7 +434,7 @@ final class CoreLibsConvertColorsTest extends TestCase * Undocumented function * * @covers ::rgb2hsl - * @dataProvider rgb2hslColorProvider + * @dataProvider providerRgb2hslColor * @testdox rgb2hsl $input_r,$input_g,$input_b will be $expected [$_dataName] * * @param integer $input_r @@ -458,7 +458,7 @@ final class CoreLibsConvertColorsTest extends TestCase * Undocumented function * * @covers ::hsl2rgb - * @dataProvider hsl2rgbColorProvider + * @dataProvider providerHsl2rgbColor * @testdox hsl2rgb $input_h,$input_s,$input_l will be $expected [$_dataName] * * @param integer|float $input_h diff --git a/4dev/tests/Convert/CoreLibsConvertMathTest.php b/4dev/tests/Convert/CoreLibsConvertMathTest.php index 9a97e37e..5df06aa0 100644 --- a/4dev/tests/Convert/CoreLibsConvertMathTest.php +++ b/4dev/tests/Convert/CoreLibsConvertMathTest.php @@ -18,7 +18,7 @@ final class CoreLibsConvertMathTest extends TestCase * * @return array */ - public function fceilProvider(): array + public function providerFceil(): array { return [ '5.5 must be 6' => [5.5, 6], @@ -31,7 +31,7 @@ final class CoreLibsConvertMathTest extends TestCase * Undocumented function * * @covers ::fceil - * @dataProvider fceilProvider + * @dataProvider providerFceil * @testdox fceil: Input $input must be $expected * * @param float $input @@ -51,7 +51,7 @@ final class CoreLibsConvertMathTest extends TestCase * * @return array */ - public function floorProvider(): array + public function providerFloor(): array { return [ '5123456 with -3 must be 5123000' => [5123456, -3, 5123000], @@ -63,7 +63,7 @@ final class CoreLibsConvertMathTest extends TestCase * Undocumented function * * @covers ::floorp - * @dataProvider floorProvider + * @dataProvider providerFloor * @testdox floor: Input $input with cutoff $cutoff must be $expected * * @param int $input @@ -84,7 +84,7 @@ final class CoreLibsConvertMathTest extends TestCase * * @return array */ - public function initNumericProvider(): array + public function providerInitNumeric(): array { return [ '5 must be 5' => [5, 5, 'int'], @@ -98,7 +98,7 @@ final class CoreLibsConvertMathTest extends TestCase * Undocumented function * * @covers ::initNumeric - * @dataProvider initNumericProvider + * @dataProvider providerInitNumeric * @testdox initNumeric: Input $info $input must match $expected [$_dataName] * * @param int|float|string $input @@ -113,6 +113,388 @@ final class CoreLibsConvertMathTest extends TestCase \CoreLibs\Convert\Math::initNumeric($input) ); } + + /** + * Undocumented function + * + * @return array + */ + public function providerCbrt(): array + { + return [ + 'cube root of 2' => [2, 1.25992, 5], + 'cube root of 3' => [3, 1.44225, 5], + 'cube root of -1' => [-1, 'NAN', 0], + ]; + } + + /** + * Undocumented function + * + * @covers ::cbrt + * @dataProvider providerCbrt + * @testdox initNumeric: Input $input must match $expected [$_dataName] + * + * @param float|int $number + * @param float $expected + * @param int $round_to + * @return void + */ + public function testCbrt(float|int $number, float|string $expected, int $round_to): void + { + $this->assertEquals( + $expected, + round(\CoreLibs\Convert\Math::cbrt($number), $round_to) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function providerMultiplyMatrices(): array + { + return [ + '[3] x [3] => [3x1]' => [ + [1, 2, 3], + [1, 2, 3], + [14] + ], + '[3] x [3x1]' => [ + [1, 2, 3], + [[1], [2], [3]], + [14] + ], + '[3] x [3x1]' => [ + [1, 2, 3], + [[1], [2], [3]], + [14] + ], + '[1x3L] x [3x1]' => [ + [[1, 2, 3]], + [[1], [2], [3]], + [14] + ], + '[1x3] x [3x1]' => [ + [[1], [2], [3]], + [[1], [2], [3]], + [1, 2, 3] + ], + '[2x3] x [3] => [3x1]' => [ + [ + [1, 2, 3], + [1, 2, 3] + ], + [1, 2, 3], + [ + 14, + 14 + ] + ], + '[2x3] x [3x1]' => [ + [ + [1, 2, 3], + [1, 2, 3] + ], + [[1], [2], [3]], + [ + 14, + 14 + ] + ], + '[2x3] x [2x3] => [3x3]' => [ + [ + [1, 2, 3], + [1, 2, 3], + ], + [ + [1, 2, 3], + [1, 2, 3], + ], + [ + [3, 6, 9], + [3, 6, 9] + ] + ], + '[2x3] x [3x3]' => [ + [ + [1, 2, 3], + [1, 2, 3], + ], + [ + [1, 2, 3], + [1, 2, 3], + [0, 0, 0], + ], + [ + [3, 6, 9], + [3, 6, 9] + ] + ], + '[2x3] x [3x2]' => [ + 'a' => [ + [1, 2, 3], + [1, 2, 3], + ], + 'b' => [ + [1, 1], + [2, 2], + [3, 3], + ], + 'prod' => [ + [14, 14], + [14, 14], + ] + ], + '[3x3] x [3] => [1x3]' => [ + [ + [1, 2, 3], + [1, 2, 3], + [1, 2, 3], + ], + [1, 2, 3], + [ + 14, + 14, + 14 + ] + ], + '[3x3] x [2x3] => [3x3]' => [ + [ + [1, 2, 3], + [1, 2, 3], + [1, 2, 3], + ], + [ + [1, 2, 3], + [1, 2, 3], + ], + [ + [3, 6, 9], + [3, 6, 9], + [3, 6, 9], + ] + ], + '[3x3] x [3x3]' => [ + [ + [1, 2, 3], + [1, 2, 3], + [1, 2, 3], + ], + [ + [1, 2, 3], + [1, 2, 3], + // [0, 0, 0], + ], + [ + [3, 6, 9], + [3, 6, 9], + [3, 6, 9], + ] + ], + '[3] x [3x3]' => [ + [1, 2, 3], + [ + [1, 2, 3], + [1, 2, 3], + [1, 2, 3], + ], + [ + [6, 12, 18], + ] + ], + '[2x3] x [3x3]' => [ + [ + [1, 2, 3], + [1, 2, 3], + ], + [ + [1, 2, 3], + [1, 2, 3], + [1, 2, 3], + ], + [ + [6, 12, 18], + [6, 12, 18], + ] + ], + 'inblanaced [2x2,3] x [3x2]' => [ + 'a' => [ + [1, 2, 3], + [4, 5] + ], + 'b' => [ + [6, 7], + [8, 9], + [10, 11] + ], + 'result' => [ + [52, 58], + [64, 73], + ] + ], + 'inblanaced [2x3] x [3x1,2]' => [ + 'a' => [ + [1, 2, 3], + [4, 5, 7] + ], + 'b' => [ + [7, 8], + [9, 10], + [11] + ], + 'result' => [ + [58, 28], + [150, 82], + ] + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::multiplyMatrices + * @dataProvider providerMultiplyMatrices + * @testdox initNumeric: Input $input_a x $input_b must match $expected [$_dataName] + * + * @param array $input_a + * @param array $input_b + * @param array $expected + * @return void + */ + public function testMultiplyMatrices(array $input_a, array $input_b, array $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Math::multiplyMatrices($input_a, $input_b) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function providerEqualWithEpsilon(): array + { + return [ + 'equal' => [ + 'a' => 0.000000000000000222, + 'b' => 0.000000000000000222, + 'epsilon' => PHP_FLOAT_EPSILON, + 'equal' => true, + ], + 'almost equal' => [ + 'a' => 0.000000000000000222, + 'b' => 0.000000000000000232, + 'epsilon' => PHP_FLOAT_EPSILON, + 'equal' => true, + ], + 'not equal' => [ + 'a' => 0.000000000000000222, + 'b' => 0.000000000000004222, + 'epsilon' => PHP_FLOAT_EPSILON, + 'equal' => false, + ], + 'equal, different epsilon' => [ + 'a' => 0.000000000000000222, + 'b' => 0.000000000000004222, + 'epsilon' => 0.0001, + 'equal' => true, + ], + 'not equal, different epsilon' => [ + 'a' => 0.0001, + 'b' => 0.0002, + 'epsilon' => 0.0001, + 'equal' => false, + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::equalWithEpsilon + * @dataProvider providerEqualWithEpsilon + * @testdox equalWithEpsilon with $a and $b and Epsilon: $epsilon must be equal: $equal [$_dataName] + * + * @return void + */ + public function testEqualWithEpsilon(float $a, float $b, float $epsilon, bool $equal): void + { + $this->assertEquals( + $equal, + \CoreLibs\Convert\Math::equalWithEpsilon($a, $b, $epsilon) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function providerCompareWithEpsilon(): array + { + return [ + 'smaller, true' => [ + 'value' => 0.0001, + 'compare' => '<', + 'limit' => 0.0002, + 'epsilon' => 0.00001, + 'match' => true, + ], + 'smaller, false' => [ + 'value' => 0.0001, + 'compare' => '<', + 'limit' => 0.0001, + 'epsilon' => 0.00001, + 'match' => false, + ], + 'bigger, true' => [ + 'value' => 0.0002, + 'compare' => '>', + 'limit' => 0.0001, + 'epsilon' => 0.00001, + 'match' => true, + ], + 'bigger, false' => [ + 'value' => 0.0001, + 'compare' => '>', + 'limit' => 0.0001, + 'epsilon' => 0.00001, + 'match' => false, + ], + ]; + } + + /** + * Undocumented function + * + * @covers ::compareWithEpsilon + * @dataProvider providerCompareWithEpsilon + * @testdox compareWithEpsilon $value $compare $limit with $epsilon must match: $match [$_dataName] + * + * @param float $value + * @param string $compare + * @param float $limit + * @param float $epslion + * @param bool $match + * @return void + */ + public function testCompareWithEpsilon( + float $value, + string $compare, + float $limit, + float $epsilon, + bool $match + ): void { + $this->assertEquals( + $match, + \CoreLibs\Convert\Math::compareWithEpsilon($value, $compare, $limit, $epsilon) + ); + } } // __END__ diff --git a/4dev/tests/Create/CoreLibsCreateSessionTest.php b/4dev/tests/Create/CoreLibsCreateSessionTest.php index f9c89109..411b610b 100644 --- a/4dev/tests/Create/CoreLibsCreateSessionTest.php +++ b/4dev/tests/Create/CoreLibsCreateSessionTest.php @@ -22,7 +22,6 @@ final class CoreLibsCreateSessionTest extends TestCase public function sessionProvider(): array { // 0: session name as parameter or for GLOBAL value - // 1: type p: parameter, g: global, d: php.ini default // 2: mock data as array // checkCliStatus: true/false, // getSessionStatus: PHP_SESSION_DISABLED for abort, @@ -31,13 +30,10 @@ final class CoreLibsCreateSessionTest extends TestCase // checkActiveSession: true/false, [1st call, 2nd call] // getSessionId: string or false // 3: exepcted name (session)] - // 4: Exception thrown on error - // 5: exception code, null for none - // 6: expected error string + // 4: auto write close flag return [ 'session parameter' => [ 'sessionNameParameter', - 'p', [ 'checkCliStatus' => false, 'getSessionStatus' => PHP_SESSION_NONE, @@ -47,12 +43,9 @@ final class CoreLibsCreateSessionTest extends TestCase ], 'sessionNameParameter', null, - null, - '', ], 'session globals' => [ 'sessionNameGlobals', - 'g', [ 'checkCliStatus' => false, 'getSessionStatus' => PHP_SESSION_NONE, @@ -61,13 +54,12 @@ final class CoreLibsCreateSessionTest extends TestCase 'getSessionId' => '1234abcd4567' ], 'sessionNameGlobals', - null, - null, - '', + [ + 'auto_write_close' => false, + ], ], - 'session name default' => [ - '', - 'd', + 'auto write close' => [ + 'sessionNameAutoWriteClose', [ 'checkCliStatus' => false, 'getSessionStatus' => PHP_SESSION_NONE, @@ -75,109 +67,10 @@ final class CoreLibsCreateSessionTest extends TestCase 'checkActiveSession' => [false, true], 'getSessionId' => '1234abcd4567' ], - '', - null, - null, - '', - ], - // error checks - // 1: we are in cli - 'on cli error' => [ - '', - 'd', + 'sessionNameAutoWriteClose', [ - 'checkCliStatus' => true, - 'getSessionStatus' => PHP_SESSION_NONE, - 'setSessionName' => true, - 'checkActiveSession' => [false, true], - 'getSessionId' => '1234abcd4567' + 'auto_write_close' => true, ], - '', - 'RuntimeException', - 1, - '[SESSION] No sessions in php cli' - ], - // 2: session disabled - 'session disabled error' => [ - '', - 'd', - [ - 'checkCliStatus' => false, - 'getSessionStatus' => PHP_SESSION_DISABLED, - 'setSessionName' => true, - 'checkActiveSession' => [false, true], - 'getSessionId' => '1234abcd4567' - ], - '', - 'RuntimeException', - 2, - '[SESSION] Sessions are disabled' - ], - // 3: invalid session name: string - 'invalid name chars error' => [ - '1invalid$session#;', - 'p', - [ - 'checkCliStatus' => false, - 'getSessionStatus' => PHP_SESSION_NONE, - 'setSessionName' => false, - 'checkActiveSession' => [false, true], - 'getSessionId' => '1234abcd4567' - ], - '', - 'UnexpectedValueException', - 3, - '[SESSION] Invalid session name: 1invalid$session#;' - ], - // 3: invalid session name: only numbers - 'invalid name numbers only error' => [ - '123', - 'p', - [ - 'checkCliStatus' => false, - 'getSessionStatus' => PHP_SESSION_NONE, - 'setSessionName' => false, - 'checkActiveSession' => [false, true], - 'getSessionId' => '1234abcd4567' - ], - '', - 'UnexpectedValueException', - 3, - '[SESSION] Invalid session name: 123' - ], - // 3: invalid session name: invalid name short - // 3: invalid session name: too long (128) - // 4: failed to start session (2nd false on check active session) - 'invalid name numbers only error' => [ - '', - 'd', - [ - 'checkCliStatus' => false, - 'getSessionStatus' => PHP_SESSION_NONE, - 'setSessionName' => true, - 'checkActiveSession' => [false, false], - 'getSessionId' => '1234abcd4567' - ], - '', - 'RuntimeException', - 4, - '[SESSION] Failed to activate session' - ], - // 5: get session id return false - 'invalid name numbers only error' => [ - '', - 'd', - [ - 'checkCliStatus' => false, - 'getSessionStatus' => PHP_SESSION_NONE, - 'setSessionName' => true, - 'checkActiveSession' => [false, true], - 'getSessionId' => false - ], - '', - 'UnexpectedValueException', - 5, - '[SESSION] getSessionId did not return a session id' ], ]; } @@ -190,32 +83,24 @@ final class CoreLibsCreateSessionTest extends TestCase * @testdox startSession $input name for $type will be $expected (error: $expected_error) [$_dataName] * * @param string $input - * @param string $type * @param array $mock_data * @param string $expected - * @param string|null $exception - * @param string $expected_error + * @param array $options * @return void */ public function testStartSession( string $input, - string $type, array $mock_data, string $expected, - ?string $exception, - ?int $exception_code, - string $expected_error + ?array $options, ): void { - // override expected - if ($type == 'd') { - $expected = ini_get('session.name'); - } /** @var \CoreLibs\Create\Session&MockObject $session_mock */ $session_mock = $this->createPartialMock( \CoreLibs\Create\Session::class, [ - 'checkCliStatus', 'getSessionStatus', 'checkActiveSession', - 'setSessionName', 'startSessionCall', 'getSessionId', + 'checkCliStatus', + 'getSessionStatus', 'checkActiveSession', + 'getSessionId', 'getSessionName' ] ); @@ -234,12 +119,8 @@ final class CoreLibsCreateSessionTest extends TestCase $mock_data['checkActiveSession'][0], $mock_data['checkActiveSession'][1], ); - // dummy set for session name - $session_mock->method('setSessionName')->with($input)->willReturn($mock_data['setSessionName']); // set session name & return bsed on request data $session_mock->method('getSessionName')->willReturn($expected); - // will not return anything - $session_mock->method('startSessionCall'); // in test case only return string // false: will return false $session_mock->method('getSessionId')->willReturn($mock_data['getSessionId']); @@ -247,25 +128,7 @@ final class CoreLibsCreateSessionTest extends TestCase // regex for session id $ression_id_regex = "/^\w+$/"; - if ($exception !== null) { - $this->expectException($exception); - $this->expectExceptionCode($exception_code); - } - - unset($GLOBALS['SET_SESSION_NAME']); - $session_id = ''; - switch ($type) { - case 'p': - $session_id = $session_mock->startSession($input); - break; - case 'g': - $GLOBALS['SET_SESSION_NAME'] = $input; - $session_id = $session_mock->startSession(); - break; - case 'd': - $session_id = $session_mock->startSession(); - break; - } + $session_id = $session_mock->getSessionId(); // asert checks if (!empty($session_id)) { $this->assertMatchesRegularExpression( @@ -284,6 +147,79 @@ final class CoreLibsCreateSessionTest extends TestCase } } + /** + * Undocumented function + * + * @return array + */ + public function providerSessionException(): array + { + return [ + 'not cli' => [ + 'TEST_EXCEPTION', + \RuntimeException::class, + 1, + '/^\[SESSION\] No sessions in php cli$/', + ], + /* 'session disabled ' => [ + 'TEST_EXCEPTION', + \RuntimeException::class, + 2, + '/^\[SESSION\] Sessions are disabled/' + ], + 'invalid session name' => [ + '--#as^-292p-', + \UnexpectedValueException::class, + 3, + '/^\[SESSION\] Invalid session name: /' + ], + 'failed to activate session' => [ + 'TEST_EXCEPTION', + \RuntimeException::class, + 4, + '/^\[SESSION\] Failed to activate session/' + ], + 'expired session' => [ + \RuntimeException::class, + 5, + '/^\[SESSION\] Expired session found/' + ], + 'not a valid session id returned' => [ + \UnexpectedValueException::class, + 6, + '/^\[SESSION\] getSessionId did not return a session id/' + ], */ + ]; + } + + /** + * exception checks + * + * @covers ::initSession + * @dataProvider providerSessionException + * @testdox create session $session_name with exception $exception ($exception_code) [$_dataName] + * + * @param string $session_name + * @param string $exception + * @param int $exception_code + * @param string $expected_error + * @return void + */ + public function testSessionException( + string $session_name, + string $exception, + int $exception_code, + string $expected_error, + ): void { + // + // throws only on new Object creation + $this->expectException($exception); + $this->expectExceptionCode($exception_code); + $this->expectExceptionMessageMatches($expected_error); + // 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]); + } + /** * provider for session name check * @@ -347,109 +283,147 @@ final class CoreLibsCreateSessionTest extends TestCase * * @return array */ - public function sessionDataProvider(): array + public function providerSessionData(): array { return [ 'test' => [ 'foo', 'bar', 'bar', + null, ], 'int key test' => [ 123, 'bar', 'bar', + \UnexpectedValueException::class ], // more complex value tests 'array values' => [ 'array', [1, 2, 3], [1, 2, 3], + null, ] ]; } + // NOTE: with auto start session, we cannot test this in the command line + /** * method call test * - * @covers ::setS - * @covers ::getS - * @covers ::issetS - * @covers ::unsetS - * @dataProvider sessionDataProvider - * @testdox setS/getS/issetS/unsetS $name with $input is $expected [$_dataName] + * @covers ::set + * @covers ::get + * @covers ::isset + * @covers ::unset + * @dataProvider providerSessionData + * @testdox set/get/isset/unset $name with $input is $expected ($exception) [$_dataName] * * @param string|int $name * @param mixed $input * @param mixed $expected + * @param ?mixed $exception * @return void */ - public function testMethodSetGet($name, $input, $expected): void + public function testMethodSetGet($name, $input, $expected, $exception): void { - $session = new \CoreLibs\Create\Session(); - $session->setS($name, $input); + if (\CoreLibs\Get\System::checkCLI()) { + $this->markTestSkipped('Cannot run testMethodSetGet in CLI'); + } + $session = new \CoreLibs\Create\Session('TEST_METHOD'); + if ($expected !== null) { + $this->expectException($exception); + } + $session->set($name, $input); $this->assertEquals( $expected, - $session->getS($name), + $session->get($name), 'method set assert' ); // isset true $this->assertTrue( - $session->issetS($name), + $session->isset($name), 'method isset assert ok' ); - $session->unsetS($name); + $session->unset($name); $this->assertEquals( '', - $session->getS($name), + $session->get($name), 'method unset assert' ); - // iset false + // isset false $this->assertFalse( - $session->issetS($name), + $session->isset($name), 'method isset assert false' ); } /** - * magic call test + * Undocumented function * - * @covers ::__set - * @covers ::__get - * @covers ::__isset - * @covers ::__unset - * @dataProvider sessionDataProvider - * @testdox __set/__get/__iseet/__unset $name with $input is $expected [$_dataName] + * @return array + */ + public function providerSessionDataMany(): array + { + return [ + 'valid set' => [ + [ + 'foo 1' => 'bar 1', + 'foo 2' => 'bar 1', + ], + [ + 'foo 1' => 'bar 1', + 'foo 2' => 'bar 1', + ], + null, + ], + 'invalid entry' => [ + [ + 'foo 1' => 'bar 1', + 123 => 'bar 1', + ], + [ + 'foo 1' => 'bar 1', + ], + \UnexpectedValueException::class + ] + ]; + } + + /** + * Undocumented function * - * @param string|int $name - * @param mixed $input - * @param mixed $expected + * @covers ::setMany + * @covers ::getMany + * @dataProvider providerSessionDataMany + * @testdox setMany/getMany/unsetMany $set is $expected ($exception) [$_dataName] + * + * @param array $set + * @param array $expected + * @param ?mixed $exception * @return void */ - public function testMagicSetGet($name, $input, $expected): void + public function testMany($set, $expected, $exception): void { - $session = new \CoreLibs\Create\Session(); - $session->$name = $input; + if (\CoreLibs\Get\System::checkCLI()) { + $this->markTestSkipped('Cannot run testMethodSetGet in CLI'); + } + $session = new \CoreLibs\Create\Session('TEST_METHOD'); + if ($expected !== null) { + $this->expectException($exception); + } + $session->setMany($set); $this->assertEquals( $expected, - $session->$name, - 'magic set assert' + $session->getMany(array_keys($set)), + 'set many failed' ); - // isset true - $this->assertTrue( - isset($session->$name), - 'magic isset assert ok' - ); - unset($session->$name); + $session->unsetMany(array_keys($set)); $this->assertEquals( - '', - $session->$name, - 'magic unset assert' - ); - // isset true - $this->assertFalse( - isset($session->$name), - 'magic isset assert false' + [], + $session->getMany(array_keys($set)), + 'unset many failed' ); } @@ -463,27 +437,30 @@ final class CoreLibsCreateSessionTest extends TestCase */ public function testUnsetAll(): void { + if (\CoreLibs\Get\System::checkCLI()) { + $this->markTestSkipped('Cannot run testUnsetAll in CLI'); + } $test_values = [ 'foo' => 'abc', 'bar' => '123' ]; - $session = new \CoreLibs\Create\Session(); + $session = new \CoreLibs\Create\Session('TEST_UNSET'); foreach ($test_values as $name => $value) { - $session->setS($name, $value); + $session->set($name, $value); // confirm set $this->assertEquals( $value, - $session->getS($name), + $session->get($name), 'set assert: ' . $name ); } // unset all - $session->unsetAllS(); + $session->clear(); // check unset foreach (array_keys($test_values) as $name) { $this->assertEquals( '', - $session->getS($name), + $session->get($name), 'unsert assert: ' . $name ); } diff --git a/4dev/tests/Create/CoreLibsCreateUidsTest.php b/4dev/tests/Create/CoreLibsCreateUidsTest.php index 3612ee89..308d9640 100644 --- a/4dev/tests/Create/CoreLibsCreateUidsTest.php +++ b/4dev/tests/Create/CoreLibsCreateUidsTest.php @@ -121,6 +121,7 @@ final class CoreLibsCreateUidsTest extends TestCase * must match 7e78fe0d-59b8-4637-af7f-e88d221a7d1e * * @covers ::uuidv4 + * @covers ::validateUuidv4 * @testdox uuidv4 check that return is matching regex [$_dataName] * * @return void @@ -129,13 +130,18 @@ final class CoreLibsCreateUidsTest extends TestCase { $uuid = \CoreLibs\Create\Uids::uuidv4(); $this->assertMatchesRegularExpression( - '/^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$/', - $uuid + '/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/', + $uuid, + 'Failed regex check' + ); + $this->assertTrue( + \CoreLibs\Create\Uids::validateUuuidv4($uuid), + 'Failed validate regex method' + ); + $this->assertFalse( + \CoreLibs\Create\Uids::validateUuuidv4('not-a-uuidv4'), + 'Failed wrong uuid validated as true' ); - // $this->assertStringMatchesFormat( - // '%4s%4s-%4s-%4s-%4s-%4s%4s%4s', - // $uuid - // ); } /** diff --git a/4dev/tests/DB/CoreLibsDBExtendedArrayIOTest.php b/4dev/tests/DB/CoreLibsDBExtendedArrayIOTest.php index f5204ed5..1e50e92d 100644 --- a/4dev/tests/DB/CoreLibsDBExtendedArrayIOTest.php +++ b/4dev/tests/DB/CoreLibsDBExtendedArrayIOTest.php @@ -10,7 +10,6 @@ use PHPUnit\Framework\TestCase; * Test class for DB\Extended\ArrayIO * This will only test the PgSQL parts * @coversDefaultClass \CoreLibs\DB\Extended\ArrayIO - * @coversDefaultClass \CoreLibs\DB\Extended\ArrayIO * @testdox \CoreLibs\Extended\ArrayIO method tests for extended DB interface */ final class CoreLibsDBExtendedArrayIOTest extends TestCase diff --git a/4dev/tests/DB/CoreLibsDBIOTest.php b/4dev/tests/DB/CoreLibsDBIOTest.php index 93703db8..d85de03f 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, @@ -37,8 +37,9 @@ namespace tests; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; -use CoreLibs\Logging\Logger\Level; +use CoreLibs\Logging; use CoreLibs\DB\Options\Convert; +use CoreLibs\DB\Support\ConvertPlaceholder; /** * Test class for DB\IO + DB\SQL\PgSQL @@ -117,7 +118,7 @@ final class CoreLibsDBIOTest extends TestCase ); } // define basic connection set valid and one invalid - self::$log = new \CoreLibs\Logging\Logging([ + self::$log = new Logging\Logging([ // 'log_folder' => __DIR__ . DIRECTORY_SEPARATOR . 'log', 'log_folder' => DIRECTORY_SEPARATOR . 'tmp', 'log_file_id' => 'CoreLibs-DB-IO-Test', @@ -161,13 +162,9 @@ final class CoreLibsDBIOTest extends TestCase // primary key name is table + '_id' <<dbExec( <<dbClose(); // second conenction with log set NOT debug - $log = new \CoreLibs\Logging\Logging([ + $log = new Logging\Logging([ // 'log_folder' => __DIR__ . DIRECTORY_SEPARATOR . 'log', 'log_folder' => DIRECTORY_SEPARATOR . 'tmp', 'log_file_id' => 'CoreLibs-DB-IO-Test', - 'log_level' => \CoreLibs\Logging\Logger\Level::Notice, + 'log_level' => Logging\Logger\Level::Notice, ]); $db = new \CoreLibs\DB\IO( self::$db_config[$connection], @@ -3293,6 +3290,7 @@ final class CoreLibsDBIOTest extends TestCase 'query' => 'INSERT INTO table_with_primary_key (row_int, uid) ' . 'VALUES ($1, $2) RETURNING table_with_primary_key_id', 'returning_id' => true, + 'placeholder_converted' => [], ], ], // update @@ -3327,6 +3325,7 @@ final class CoreLibsDBIOTest extends TestCase 'query' => 'UPDATE table_with_primary_key SET row_int = $1, ' . 'row_varchar = $2 WHERE uid = $3', 'returning_id' => false, + 'placeholder_converted' => [], ], ], // select @@ -3356,6 +3355,7 @@ final class CoreLibsDBIOTest extends TestCase 'count' => 1, 'query' => 'SELECT row_int, uid FROM table_with_primary_key WHERE uid = $1', 'returning_id' => false, + 'placeholder_converted' => [], ], ], // any query but with no parameters @@ -3388,6 +3388,7 @@ final class CoreLibsDBIOTest extends TestCase 'count' => 0, 'query' => 'SELECT row_int, uid FROM table_with_primary_key', 'returning_id' => false, + 'placeholder_converted' => [], ], ], // no statement name (25) @@ -3411,6 +3412,7 @@ final class CoreLibsDBIOTest extends TestCase 'count' => 0, 'query' => '', 'returning_id' => false, + 'placeholder_converted' => [], ], ], // no query (prepare 11) @@ -3435,6 +3437,7 @@ final class CoreLibsDBIOTest extends TestCase 'count' => 0, 'query' => '', 'returning_id' => false, + 'placeholder_converted' => [], ], ], // no db connection (prepare/execute 16) @@ -3464,6 +3467,7 @@ final class CoreLibsDBIOTest extends TestCase 'count' => 0, 'query' => 'SELECT row_int, uid FROM table_with_primary_key', 'returning_id' => false, + 'placeholder_converted' => [], ], ], // prepare with different statement name @@ -3489,6 +3493,7 @@ final class CoreLibsDBIOTest extends TestCase 'count' => 0, 'query' => 'SELECT row_int, uid FROM table_with_primary_key', 'returning_id' => false, + 'placeholder_converted' => [], ], ], // insert wrong data count compared to needed (execute 23) @@ -3514,10 +3519,12 @@ final class CoreLibsDBIOTest extends TestCase 'query' => 'INSERT INTO table_with_primary_key (row_int, uid) VALUES ' . '($1, $2) RETURNING table_with_primary_key_id', 'returning_id' => true, + 'placeholder_converted' => [], ], ], // execute does not return a result (22) // TODO execute does not return a result + // TODO prepared statement with placeholder params auto convert ]; } @@ -3662,7 +3669,7 @@ final class CoreLibsDBIOTest extends TestCase } // check dbGetPrepareCursorValue - foreach (['pk_name', 'count', 'query', 'returning_id'] as $key) { + foreach (['pk_name', 'count', 'query', 'returning_id', 'placeholder_converted'] as $key) { $this->assertEquals( $prepare_cursor[$key], $db->dbGetPrepareCursorValue($stm_name, $key), @@ -3685,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 @@ -3729,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 @@ -3762,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 @@ -5031,8 +5126,233 @@ final class CoreLibsDBIOTest extends TestCase $db->dbClose(); } - // query placeholder convert + // MARK: QUERY PLACEHOLDERS + // test query placeholder detection for all possible sets + // ::dbPrepare + + /** + * placeholder sql + * + * @return array + */ + public function providerDbCountQueryParams(): array + { + return [ + 'one place holder' => [ + 'query' => 'SELECT row_varchar FROM table_with_primary_key WHERE row_varchar = $1', + 'count' => 1, + 'convert' => false, + ], + 'one place holder, json call' => [ + 'query' => "SELECT row_varchar FROM table_with_primary_key WHERE row_jsonb->>'data' = $1", + 'count' => 1, + 'convert' => false, + ], + 'one place holder, <> compare' => [ + 'query' => "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar <> $1", + 'count' => 1, + 'convert' => false, + ], + 'one place holder, named' => [ + 'query' => "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar <> :row_varchar", + 'count' => 1, + 'convert' => true, + ], + 'no replacement' => [ + 'query' => "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar = '$1'", + 'count' => 0, + 'convert' => false, + ], + 'insert' => [ + 'query' => "INSERT INTO table_with_primary_key (row_varchar, row_jsonb, row_int) VALUES ($1, $2, $3)", + 'count' => 3, + 'convert' => false, + ], + 'update' => [ + 'query' => "UPDATE table_with_primary_key SET row_varchar = $1, row_jsonb = $2, row_int = $3 WHERE row_numeric = $4", + 'count' => 4, + 'convert' => false, + ], + 'multiple, multline' => [ + 'query' => << 3, + 'convert' => false, + ], + 'two digit numbers' => [ + 'query' => << 10, + 'convert' => false, + ], + 'things in brackets' => [ + 'query' => << 4, + 'convert' => false, + ], + 'number compare' => [ + 'query' => <<= $1 OR row_int <= $2 OR + row_int > $3 OR row_int < $4 + OR row_int = $5 OR row_int <> $6 + SQL, + 'count' => 6, + 'convert' => false, + ], + 'comments in insert' => [ + 'query' => << 4, + 'convert' => false + ], + 'comment in update' => [ + '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, + ], + // all the same + 'all the same numbered' => [ + 'query' => << 1, + 'convert' => false, + ], + 'update with case' => [ + 'query' => << 3, + 'convert' => false, + ], + 'select with case' => [ + 'query' => << 2, + 'convert' => false, + ] + ]; + } + + /** + * Placeholder check and convert tests + * + * @covers ::dbPrepare + * @covers ::__dbCountQueryParams + * @onvers ::convertPlaceholderInQuery + * @dataProvider providerDbCountQueryParams + * @testdox Query replacement count test [$_dataName] + * + * @param string $query + * @param int $count + * @return void + */ + public function testDbCountQueryParams(string $query, int $count, bool $convert): void + { + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + $id = sha1($query); + $db->dbSetConvertPlaceholder($convert); + $db->dbPrepare($id, $query); + // print "\n**\n"; + // print "PCount: " . $db->dbGetPrepareCursorValue($id, 'count') . "\n"; + // print "\n**\n"; + $this->assertEquals( + $count, + $db->dbGetPrepareCursorValue($id, 'count'), + 'DB count params' + ); + $placeholder = ConvertPlaceholder::convertPlaceholderInQuery($query, null, 'pg'); + // print "RES: " . print_r($placeholder, true) . "\n"; + $this->assertEquals( + $count, + $placeholder['needed'], + 'convert params' + ); + } + + /** + * query placeholder convert + * + * @return array + */ public function queryPlaceholderReplaceProvider(): array { // WHERE row_varchar = $1 @@ -5076,7 +5396,9 @@ final class CoreLibsDBIOTest extends TestCase WHERE row_varchar = $1 SQL, 'expected_params' => ['string a'], - ] + ], + // TODO: test with multiple entries + // TODO: test with same entry ($1, $1, :var, :var) ]; } @@ -5178,6 +5500,8 @@ final class CoreLibsDBIOTest extends TestCase // - data debug // dbDumpData + // MARK: ASYNC + // ASYNC at the end because it has 1s timeout // - asynchronous executions // dbExecAsync, dbCheckAsync 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/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/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 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/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' ); } 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/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/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php index d3f502af..1251a6da 100644 --- a/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php +++ b/4dev/tests/Security/CoreLibsSecuritySymmetricEncryptionTest.php @@ -15,6 +15,77 @@ use CoreLibs\Security\SymmetricEncryption; */ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase { + // MARK: key set compare + + /** + * 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' + ); + } + + // 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 * @@ -56,7 +127,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 +153,24 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $decrypted, 'Class Instance call', ); + } + /** + * test encrypt/decrypt produce correct output + * + * @covers ::generateRandomKey + * @covers ::encryptKey + * @covers ::decryptKey + * @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); @@ -77,6 +182,8 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase ); } + // MARK: invalid key + /** * Undocumented function * @@ -114,13 +221,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 ::encryptKey + * @covers ::decryptKey + * @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); @@ -128,6 +273,8 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase SymmetricEncryption::decryptKey($encrypted, $wrong_key); } + // MARK: wrong key + /** * Undocumented function * @@ -144,6 +291,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' + ] ]; } @@ -164,6 +315,7 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $enc_key = CreateKey::generateRandomKey(); // class + $this->expectExceptionMessage($exception_message); $crypt = new SymmetricEncryption($key); $this->expectExceptionMessage($exception_message); $crypt->encrypt('test'); @@ -172,6 +324,23 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $this->expectExceptionMessage($exception_message); $crypt->setKey($key); $crypt->decrypt($encrypted); + } + + /** + * 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); @@ -180,6 +349,23 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $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 ::encryptKey + * @covers ::decryptKey + * @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); @@ -190,6 +376,8 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase SymmetricEncryption::decryptKey($encrypted, $key); } + // MARK: wrong input + /** * Undocumented function * @@ -232,6 +420,49 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase $this->expectExceptionMessage($exception_message); SymmetricEncryption::decryptKey($input, $key); } + + /** + * 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::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 ::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::generateRandomKey(); + // class static + $this->expectExceptionMessage($exception_message); + SymmetricEncryption::decryptKey($input, $key); + } } // __END__ diff --git a/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php b/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php new file mode 100644 index 00000000..e5a22024 --- /dev/null +++ b/4dev/tests/UrlRequests/CoreLibsUrlRequestsCurlTest.php @@ -0,0 +1,1264 @@ + true, + 'base_uri' => '', + 'query' => [], + 'headers' => [], + 'timeout' => 0, + 'connection_timeout' => 300, + ]; + + /** + * check if we have some backend for testing + * + * @return void + */ + protected function setUp(): void + { + // check if local http servers + // or special started: + // php -S localhost:30999 \ + // -t /storage/var/www/html/developers/clemens/core_data/php_libraries/trunk/4dev/tests/AAASetupData/requests/ + foreach ( + [ + // main dev + 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/' + . '4dev/tests/AAASetupData/requests/http_requests.php', + // composer package + 'https://soba.egplusww.jp/developers/clemens/core_data/composer-packages/' + . 'CoreLibs-Composer-All/test/phpunit/AAASetupData/requests/http_requests.php', + // if we run php -S localhost:30999 -t [see below] + // dev: /storage/var/www/html/developers/clemens/core_data/php_libraries/trunk/4dev/tests/AAASetupData/requests/ + // composer: /storage/var/www/html/developers/clemens/core_data/composer-packages/CoreLibs-Composer-All/test/phpunit/AAASetupData + 'localhost:30999/http_requests.php', + ] as $url + ) { + $handle = curl_init($url); + if ($handle === false) { + continue; + } + $this->url_basic = $url; + // split out the last / part for url set test + curl_close($handle); + // print "Open: $url\n"; + break; + } + } + + /** + * Undocumented function + * + * @return void + */ + protected function tearDown(): void + { + // end some httpserver + } + + /** + * Undocumented function + * + * @param string $url + * @return array + */ + private function splitUrl(string $url): array + { + + if (($lastSlashPos = strrpos($url, '/')) !== false) { + return [ + substr($url, 0, $lastSlashPos + 1), + substr($url, $lastSlashPos + 1, $lastSlashPos + 1) + ]; + } else { + return [0 => '', 1 => '']; + } + } + + // MARK: class setup tests + + /** + * Undocumented function + * + * @return array + */ + public function providerUrlRequestsCurlSetup(): array + { + return [ + // MARK: base config + 'no config' => [ + 'config' => null, + 'expected_set' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + 'new_base_uri' => null, + 'set_header' => null, + 'set_header_add' => null, + 'remove_header' => null, + 'expected_change' => null, + ], + 'setup all possible configs' => [ + 'config' => [ + 'auth' => ['user', 'passowrd', 'Basic'], + 'http_errors' => false, + 'base_uri' => 'http://foo.bar.com', + 'headers' => [ + 'something' => 'other', + ], + 'query' => [ + 'foo' => 'bar', + ], + 'timeout' => 5, + 'connection_timeout' => 10, + ], + 'expected_set' => [ + 'auth' => ['user', 'passowrd', 'Basic'], + 'http_errors' => false, + 'base_uri' => 'http://foo.bar.com', + 'headers' => [ + 'something' => 'other', + ], + 'query' => [ + 'foo' => 'bar', + ], + 'timeout' => 5, + 'connection_timeout' => 10, + ], + 'new_base_uri' => null, + 'set_header' => null, + 'set_header_add' => null, + 'remove_header' => null, + 'expected_change' => null, + ], + // MARK: base url + 'setup base_uri only' => [ + 'config' => [ + 'base_uri' => 'http://bar.foo.com' + ], + 'expected_set' => [ + 'http_errors' => true, + 'base_uri' => 'http://bar.foo.com', + 'query' => [], + 'headers' => [], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + 'new_base_uri' => null, + 'set_header' => null, + 'set_header_add' => null, + 'remove_header' => null, + 'expected_change' => null, + ], + 'replace base_uri' => [ + 'config' => [ + 'base_uri' => 'http://bar.foo.com' + ], + 'expected_set' => [ + 'http_errors' => true, + 'base_uri' => 'http://bar.foo.com', + 'query' => [], + 'headers' => [], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + 'new_base_uri' => 'http://bar.baz.com', + 'set_header' => null, + 'set_header_add' => null, + 'remove_header' => null, + 'expected_change' => [ + 'http_errors' => true, + 'base_uri' => 'http://bar.baz.com', + 'query' => [], + 'headers' => [], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + ], + // MARK: set headers + 'set header new' => [ + 'config' => null, + 'expected_set' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + 'new_base_uri' => null, + 'set_header' => [ + 'new-header' => 'abc' + ], + 'set_header_add' => false, + 'remove_header' => null, + 'expected_change' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [ + 'new-header' => 'abc', + ], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + ], + 'set header overwrite' => [ + 'config' => [ + 'headers' => [ + 'existing-entry' => 'foo' + ], + ], + 'expected_set' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [ + 'existing-entry' => 'foo' + ], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + 'new_base_uri' => null, + 'set_header' => [ + 'existing-entry' => 'bar' + ], + 'set_header_add' => false, + 'remove_header' => null, + 'expected_change' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [ + 'existing-entry' => 'bar' + ], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + ], + 'set header add' => [ + 'config' => [ + 'headers' => [ + 'existing-entry' => 'foo' + ], + ], + 'expected_set' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [ + 'existing-entry' => 'foo' + ], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + 'new_base_uri' => null, + 'set_header' => [ + 'existing-entry' => 'bar' + ], + 'set_header_add' => true, + 'remove_header' => null, + 'expected_change' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [ + 'existing-entry' => ['foo', 'bar'] + ], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + ], + // MARK: test remove header + 'remove header string, full match' => [ + 'config' => [ + 'headers' => [ + 'remove-entry' => 'foo' + ], + ], + 'expected_set' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [ + 'remove-entry' => 'foo' + ], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + 'new_base_uri' => null, + 'set_header' => null, + 'set_header_add' => null, + 'remove_header' => [ + 'remove-entry' => 'foo' + ], + 'expected_change' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + ], + 'remove header string, key match only' => [ + 'config' => [ + 'headers' => [ + 'remove-entry' => 'foo' + ], + ], + 'expected_set' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [ + 'remove-entry' => 'foo' + ], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + 'new_base_uri' => null, + 'set_header' => null, + 'set_header_add' => null, + 'remove_header' => [ + 'remove-entry' => null + ], + 'expected_change' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + ], + 'remove header array, key match' => [ + 'config' => [ + 'headers' => [ + 'remove-entry' => ['foo', 'bar', 'baz'] + ], + ], + 'expected_set' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [ + 'remove-entry' => ['foo', 'bar', 'baz'] + ], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + 'new_base_uri' => null, + 'set_header' => null, + 'set_header_add' => null, + 'remove_header' => [ + 'remove-entry' => null + ], + 'expected_change' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + ], + 'remove header array, string match' => [ + 'config' => [ + 'headers' => [ + 'remove-entry' => ['foo', 'bar', 'baz'] + ], + ], + 'expected_set' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [ + 'remove-entry' => ['foo', 'bar', 'baz'] + ], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + 'new_base_uri' => null, + 'set_header' => null, + 'set_header_add' => null, + 'remove_header' => [ + 'remove-entry' => 'foo' + ], + 'expected_change' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [ + 'remove-entry' => ['bar', 'baz'] + ], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + ], + 'remove header array, array match' => [ + 'config' => [ + 'headers' => [ + 'remove-entry' => ['foo', 'bar', 'baz'] + ], + ], + 'expected_set' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [ + 'remove-entry' => ['foo', 'bar', 'baz'] + ], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + 'new_base_uri' => null, + 'set_header' => null, + 'set_header_add' => null, + 'remove_header' => [ + 'remove-entry' => ['foo', 'bar',] + ], + 'expected_change' => [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [ + 'remove-entry' => ['baz'] + ], + 'timeout' => 0, + 'connection_timeout' => 300, + ], + ], + ]; + } + + // MARK: setup/config + + /** + * set setup + header, base uri change + * + * @covers ::Curl + * @covers ::setBaseUri + * @covers ::addHeader + * @covers ::removeHEader + * @dataProvider providerUrlRequestsCurlSetup + * @testdox UrlRequests\Curl Class setup tasks [$_dataName] + * + * @param null|array $config + * @param array $expected + * @param null|string $new_base_uri + * @param null|array $set_header + * @param null|bool $set_header_add + * @param null|array $remove_header + * @param null|array $expected_change + * @return void + */ + public function testUrlRequestsCurlSetupConfig( + null|array $config, + array $expected_set, + null|string $new_base_uri, + null|array $set_header, + null|bool $set_header_add, + null|array $remove_header, + null|array $expected_change + ): void { + // empty new + if ($config === null) { + $curl = new \CoreLibs\UrlRequests\Curl(); + } else { + $curl = new \CoreLibs\UrlRequests\Curl($config); + }; + // if ($new_base_uri === null && $set_header === null && $remove_header === null) { + // } + $this->assertEquals($expected_set, $curl->getConfig(), 'Class setup config mismatch'); + if ($new_base_uri !== null) { + $curl->setBaseUri($new_base_uri); + $this->assertEquals($expected_change, $curl->getConfig(), 'new base_uri not matching'); + } + if ($set_header !== null) { + if ($set_header_add !== null) { + $curl->setHeaders($set_header, $set_header_add); + } else { + $curl->setHeaders($set_header); + } + $this->assertEquals($expected_change, $curl->getConfig(), 'new headers not matching'); + } + if ($remove_header !== null) { + $curl->removeHeaders($remove_header); + $this->assertEquals($expected_change, $curl->getConfig(), 'removed headers not matching'); + } + } + + // MARK: request call tests + + /** + * Undocumented function + * + * @return array + */ + public function providerUrlRequestsCurlRequestBuild(): array + { + return [ + // MARK: config overwrite + // this would be: + // - base url + add url + // - base url + replace url + 'base url + add url' => [ + 'type' => 'get', + 'config' => [ + "base_uri" => "URL_START" + ], + 'url' => "URL_END", + 'options' => null, + 'sent_url' => "URL_FULL", + 'sent_url_parsed' => null, + 'sent_headers' => [ + "User-Agent:CoreLibsUrlRequestCurl/1", + ], + 'return_code' => "200", + 'return_content' => '' + ], + 'base url + replace url' => [ + 'type' => 'get', + 'config' => [ + "base_uri" => "URL_START" + ], + 'url' => "URL_FULL", + 'options' => null, + 'sent_url' => "URL_FULL", + 'sent_url_parsed' => null, + 'sent_headers' => [ + "User-Agent:CoreLibsUrlRequestCurl/1", + ], + 'return_code' => "200", + 'return_content' => '' + ], + // - base header + add header + // - base header + reset header (null) + // - base query + add query + 'base header + add header' => [ + 'type' => 'get', + 'config' => [ + "headers" => [ + "header-one" => "one", + ] + ], + 'url' => null, + 'options' => [ + "headers" => [ + "header-two" => "two", + ] + ], + 'sent_url' => null, + 'sent_url_parsed' => null, + 'sent_headers' => [ + "header-two:two", + "header-one:one", + "User-Agent:CoreLibsUrlRequestCurl/1", + ], + 'return_code' => "200", + 'return_content' => '' + ], + 'base header + reset header' => [ + 'type' => 'get', + 'config' => [ + "headers" => [ + "header-one" => "one", + ] + ], + 'url' => null, + 'options' => [ + "headers" => null + ], + 'sent_url' => null, + 'sent_url_parsed' => null, + 'sent_headers' => [ + "User-Agent:CoreLibsUrlRequestCurl/1", + ], + 'return_code' => "200", + 'return_content' => '' + ], + 'base header + add header (same)' => [ + 'type' => 'get', + 'config' => [ + "headers" => [ + "header-one" => "one", + ] + ], + 'url' => null, + 'options' => [ + "headers" => [ + "header-one" => "one", + "header-two" => "two", + ] + ], + 'sent_url' => null, + 'sent_url_parsed' => null, + 'sent_headers' => [ + "header-two:two", + "header-one:one", + "User-Agent:CoreLibsUrlRequestCurl/1", + ], + 'return_code' => "200", + 'return_content' => '' + ], + 'base header + add header (replace)' => [ + 'type' => 'get', + 'config' => [ + "headers" => [ + "header-one" => "one", + ] + ], + 'url' => null, + 'options' => [ + "headers" => [ + "header-one" => "three", + "header-two" => "two", + ] + ], + 'sent_url' => null, + 'sent_url_parsed' => null, + 'sent_headers' => [ + "header-two:two", + "header-one:three", + "User-Agent:CoreLibsUrlRequestCurl/1", + ], + 'return_code' => "200", + 'return_content' => '' + ], + ]; + } + + // MARK: test call overwrite + + /** + * request build tests + * + * @covers ::request + * @dataProvider providerUrlRequestsCurlRequestBuild + * @testdox UrlRequests\Curl call with data merge [$_dataName] + * + * @param string $type + * @param array|null $config + * @param string|null $url + * @param array|null $options + * @param string|null $sent_url + * @param array|null $sent_url_parsed + * @param array $sent_headers + * @param string $return_code + * @param string $return_content + * @return void + */ + public function testUrlRequestsCurlRequestBuild( + string $type, + ?array $config, + ?string $url, + ?array $options, + ?string $sent_url, + ?array $sent_url_parsed, + array $sent_headers, + string $return_code, + string $return_content + ) { + if (!$this->url_basic) { + $this->markTestSkipped('No backend interface setup for testing: GET'); + } + if ($url) { + list($url_start, $url_end) = $this->splitUrl($this->url_basic); + $config['base_uri'] = str_replace('URL_START', $url_start, $config['base_uri']); + $url = str_replace('URL_END', $url_end, $url); + $url = str_replace('URL_FULL', $this->url_basic, $url); + $sent_url = str_replace('URL_FULL', $this->url_basic, $sent_url); + } + // init without or with config + if ($config === null) { + $curl = new \CoreLibs\UrlRequests\Curl(); + } else { + $curl = new \CoreLibs\UrlRequests\Curl($config); + }; + // set url + if ($url === null) { + $url = $this->url_basic; + } + // options + if (is_array($options)) { + $respone = $curl->request($type, $url, $options); + } else { + $respone = $curl->request($type, $url); + } + // headers + $this->assertEqualsCanonicalizing( + $sent_headers, + $curl->getHeadersSent(), + 'Headers do not metch' + ); + // url + if ($sent_url) { + $this->assertEquals( + $sent_url, + $curl->getUrlSent(), + 'Sent URL does not match' + ); + } + // check return code + $this->assertEquals( + $return_code, + $respone['code'], + 'Return code not matching' + ); + } + + // MARK: test basic call provider + + /** + * Undocumented function + * + * @return array + */ + public function providerUrlRequestsCurlRequest(): array + { + // phpcs:disable Generic.Files.LineLength + // get and delete can have null body, but only get will never have a body + $provider = []; + // MARK: get + foreach (['get'] as $type) { + $provider["basic " . $type . ", no options"] = [ + 'type' => $type, + 'options' => null, + 'return_code' => "200", + 'return_content' => '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_ACCEPT":"*\/*","HTTP_HOST":"soba.egplusww.jp"},"REQUEST_TYPE":"' . strtoupper($type) . '","PARAMS":[],"BODY":null}' + ]; + $provider["basic " . $type . ", query options"] = [ + 'type' => $type, + 'options' => [ + "query" => ["foo" => "bar"], + ], + 'return_code' => "200", + 'return_content' => '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_ACCEPT":"*\/*","HTTP_HOST":"soba.egplusww.jp"},"REQUEST_TYPE":"' . strtoupper($type) . '","PARAMS":{"foo":"bar"},"BODY":null}' + ]; + } + // MARK: delete + foreach (['delete'] as $type) { + // MARK: post + $provider["basic " . $type . ", no options"] = [ + 'type' => $type, + 'options' => null, + 'return_code' => "200", + 'return_content' => '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_ACCEPT":"*\/*","HTTP_HOST":"soba.egplusww.jp"},"REQUEST_TYPE":"' . strtoupper($type) . '","PARAMS":[],"BODY":[]}' + ]; + $provider["basic " . $type . ", query options"] = [ + 'type' => $type, + 'options' => [ + "query" => ["foo" => "bar"], + ], + 'return_code' => "200", + 'return_content' => '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_ACCEPT":"*\/*","HTTP_HOST":"soba.egplusww.jp"},"REQUEST_TYPE":"' . strtoupper($type) . '","PARAMS":{"foo":"bar"},"BODY":[]}' + ]; + $provider["basic " . $type . ", query/body options"] = [ + 'type' => $type, + 'options' => [ + "query" => ["foo" => "bar"], + "body" => ["foobar" => "barbaz"], + ], + 'return_code' => "200", + 'return_content' => '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_ACCEPT":"*\/*","HTTP_HOST":"soba.egplusww.jp"},"REQUEST_TYPE":"' . strtoupper($type) . '","PARAMS":{"foo":"bar"},"BODY":{"foobar":"barbaz"}}' + ]; + $provider["basic " . $type . ", body options"] = [ + 'type' => $type, + 'options' => [ + "body" => ["foobar" => "barbaz"], + ], + 'return_code' => "200", + 'return_content' => '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_ACCEPT":"*\/*","HTTP_HOST":"soba.egplusww.jp"},"REQUEST_TYPE":"' . strtoupper($type) . '","PARAMS":[],"BODY":{"foobar":"barbaz"}}' + ]; + $provider["basic " . $type . ", body options as string"] = [ + 'type' => $type, + 'options' => [ + "body" => "body is a string", + ], + 'return_code' => "200", + 'return_content' => '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_ACCEPT":"*\/*","HTTP_HOST":"soba.egplusww.jp"},"REQUEST_TYPE":"' . strtoupper($type) . '","PARAMS":[],"BODY":["body is a string"]}' + ]; + } + // MARK: post/put/patch + foreach (['post', 'put', 'patch'] as $type) { + // MARK: post + $provider["basic " . $type . ", no options"] = [ + 'type' => $type, + 'options' => null, + 'return_code' => "200", + 'return_content' => '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_ACCEPT":"*\/*","HTTP_HOST":"soba.egplusww.jp"},"REQUEST_TYPE":"' . strtoupper($type) . '","PARAMS":[],"BODY":[]}' + ]; + $provider["basic " . $type . ", query options"] = [ + 'type' => $type, + 'options' => [ + "query" => ["foo" => "bar"], + ], + 'return_code' => "200", + 'return_content' => '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_ACCEPT":"*\/*","HTTP_HOST":"soba.egplusww.jp"},"REQUEST_TYPE":"' . strtoupper($type) . '","PARAMS":{"foo":"bar"},"BODY":[]}' + ]; + $provider["basic " . $type . ", query/body options"] = [ + 'type' => $type, + 'options' => [ + "query" => ["foo" => "bar"], + "body" => ["foobar" => "barbaz"], + ], + 'return_code' => "200", + 'return_content' => '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_ACCEPT":"*\/*","HTTP_HOST":"soba.egplusww.jp"},"REQUEST_TYPE":"' . strtoupper($type) . '","PARAMS":{"foo":"bar"},"BODY":{"foobar":"barbaz"}}' + ]; + $provider["basic " . $type . ", body options"] = [ + 'type' => $type, + 'options' => [ + "body" => ["foobar" => "barbaz"], + ], + 'return_code' => "200", + 'return_content' => '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_ACCEPT":"*\/*","HTTP_HOST":"soba.egplusww.jp"},"REQUEST_TYPE":"' . strtoupper($type) . '","PARAMS":[],"BODY":{"foobar":"barbaz"}}' + ]; + $provider["basic " . $type . ", body option as string"] = [ + 'type' => $type, + 'options' => [ + "body" => "body is a string", + ], + 'return_code' => "200", + 'return_content' => '{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_ACCEPT":"*\/*","HTTP_HOST":"soba.egplusww.jp"},"REQUEST_TYPE":"' . strtoupper($type) . '","PARAMS":[],"BODY":["body is a string"]}' + ]; + } + // $provider['"basic post'] + return $provider; + // phpcs:enable Generic.Files.LineLength + } + + // MARK: test basic get/post/put/patch/delete + + /** + * requests tests + * + * @covers ::request + * @covers ::get + * @covers ::post + * @covers ::put + * @covers ::patch + * @covers ::delete + * @dataProvider providerUrlRequestsCurlRequest + * @testdox UrlRequests\Curl request calls [$_dataName] + * + * @param string $type + * @param null|array $options + * @param string $return_code + * @param string $return_content + * @return void + */ + public function testUrlRequestsCurlRequest( + string $type, + null|array $options, + string $return_code, + string $return_content + ) { + if (!$this->url_basic) { + $this->markTestSkipped('No backend interface setup for testing: GET'); + } + $curl = new \CoreLibs\UrlRequests\Curl(); + // options + if (is_array($options)) { + $respone = $curl->request($type, $this->url_basic, $options); + } else { + $respone = $curl->request($type, $this->url_basic); + } + // print "REP: " . print_r($respone, true) . "\n"; + // check return code + $this->assertEquals( + $return_code, + $respone['code'], + 'request: Return code not matching' + ); + $this->assertEqualsCanonicalizing( + json_decode($return_content, true), + json_decode($respone['content'], true), + 'direct call Return content not matching' + ); + switch ($type) { + case 'get': + if (is_array($options)) { + $respone = $curl->get($this->url_basic, $options); + } else { + $respone = $curl->get($this->url_basic); + } + break; + case 'post': + if (is_array($options)) { + $respone = $curl->post($this->url_basic, $options); + } else { + $respone = $curl->post($this->url_basic, []); + } + break; + case 'put': + if (is_array($options)) { + $respone = $curl->put($this->url_basic, $options); + } else { + $respone = $curl->put($this->url_basic, []); + } + break; + case 'patch': + if (is_array($options)) { + $respone = $curl->patch($this->url_basic, $options); + } else { + $respone = $curl->patch($this->url_basic, []); + } + break; + case 'delete': + if (is_array($options)) { + $respone = $curl->delete($this->url_basic, $options); + } else { + $respone = $curl->delete($this->url_basic); + } + break; + } + // check return code + $this->assertEquals( + $return_code, + $respone['code'], + 'direct call Return code not matching' + ); + $this->assertEqualsCanonicalizing( + json_decode($return_content, true), + json_decode($respone['content'], true), + 'direct call Return content not matching' + ); + } + + // MARK: multi requests with same base connection + + /** + * Undocumented function + * + * @covers ::request + * @testdox UrlRequests\Curl multiple calls + * + * @return void + */ + public function testUrlRequestsCurlRequestMultiple() + { + $curl = new \CoreLibs\UrlRequests\Curl(); + // get + $response = $curl->get($this->url_basic, [ + "headers" => ["first-call" => "get"], + "query" => ["foo-get" => "bar"] + ]); + $this->assertEquals("200", $response["code"], "multi call: get response code not matching"); + 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"); + 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"); + 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 + + /** + * Test auth settings and auth override + * + * @testdox UrlRequests\Curl auth test call + * + * @return void + */ + public function testUrlRequestsCurlAuthHeader() + { + $curl = new \CoreLibs\UrlRequests\Curl([ + "auth" => ["user", "pass", "basic"], + "http_errors" => false, + ]); + $curl->request('get', $this->url_basic); + // check that the auth header matches + $this->assertContains( + "Authorization:Basic dXNlcjpwYXNz", + $curl->getHeadersSent() + ); + // if we sent new request with auth header, this one should not be used + $curl->request('get', $this->url_basic, [ + "headers" => ["Authorization" => "Failed"] + ]); + // check that the auth header matches + $this->assertContains( + "Authorization:Basic dXNlcjpwYXNz", + $curl->getHeadersSent() + ); + // override auth: reset + $curl->request('get', $this->url_basic, [ + "auth" => null + ]); + $this->assertNotContains( + "Authorization:Basic dXNlcjpwYXNz", + $curl->getHeadersSent() + ); + // override auth: different auth + $curl->request('get', $this->url_basic, [ + "auth" => ["user2", "pass2", "basic"] + ]); + // check that the auth header matches + $this->assertContains( + "Authorization:Basic dXNlcjI6cGFzczI=", + $curl->getHeadersSent() + ); + } + + // MARK: test exceptions + + /** + * Exception:InvalidRequestType + * + * @covers ::request + * @testdox UrlRequests\Curl Exception:InvalidRequestType + * + * @return void + */ + public function testExceptionInvalidRequestType(): void + { + $curl = new \CoreLibs\UrlRequests\Curl(); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessageMatches("/InvalidRequestType/"); + $curl->request('wrong', 'http://foo.bar.com'); + } + + /** + * Exception:InvalidHeaderKey + * + * @covers ::request + * @testdox UrlRequests\Curl Exception:InvalidHeaderKey + * + * @return void + */ + public function testExceptionInvalidHeaderKey(): void + { + $curl = new \CoreLibs\UrlRequests\Curl(); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessageMatches("/InvalidHeaderKey/"); + $curl->request('get', $this->url_basic, [ + "headers" => [ + "(invalid-key)" => "key" + ] + ]); + } + + /** + * Exception:InvalidHeaderValue + * + * @covers ::request + * @testdox UrlRequests\Curl Exception:InvalidHeaderValue + * + * @return void + */ + public function testExceptionInvalidHeaderValue(): void + { + $curl = new \CoreLibs\UrlRequests\Curl(); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessageMatches("/InvalidHeaderValue/"); + $curl->request('get', $this->url_basic, [ + "headers" => [ + "invalid-value" => "\x19\x10" + ] + ]); + } + + /** + * TODO: Exception:CurlInitError + * + * @testdox UrlRequests\Curl Exception:CurlInitError + * + * @return void + */ + // public function testExceptionCurlInitError(): void + // { + // $this->markTestSkipped('Test Exception CurlInitError not implemented'); + // } + + /** + * Exception:CurlExecError + * + * @covers ::request + * @testdox UrlRequests\Curl Exception:CurlExecError + * + * @return void + */ + public function testExceptionCurlError(): void + { + $curl = new \CoreLibs\UrlRequests\Curl(); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessageMatches("/CurlExecError/"); + // invalid yrl + $curl->request('get', 'as-4939345!#$%'); + } + + /** + * Exception:ClientError + * + * @covers ::request + * @testdox UrlRequests\Curl Exception:ClientError + * + * @return void + */ + public function testExceptionBadRequest(): void + { + $curl = new \CoreLibs\UrlRequests\Curl(["http_errors" => true]); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessageMatches("/ClientError/"); + $curl->request('get', $this->url_basic, [ + "headers" => [ + "Authorization" => "schmalztiegel", + "RunAuthTest" => "yes", + ] + ]); + } + + /** + * Exception:ClientError + * + * @covers ::request + * @testdox UrlRequests\Curl Exception:ClientError on call enable + * + * @return void + */ + public function testExceptionBadRequestEnable(): void + { + $curl = new \CoreLibs\UrlRequests\Curl(["http_errors" => false]); + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessageMatches("/ClientError/"); + $curl->request('get', $this->url_basic, [ + "headers" => [ + "Authorization" => "schmalztiegel", + "RunAuthTest" => "yes", + ], + "http_errors" => true + ]); + } + + /** + * Exception:ClientError + * + * @covers ::request + * @testdox UrlRequests\Curl Exception:ClientError unset on call + * + * @return void + */ + public function testExceptionBadRequestUnset(): void + { + // if true, with false it has to be off + $curl = new \CoreLibs\UrlRequests\Curl(["http_errors" => true]); + $response = $curl->request('get', $this->url_basic, [ + "headers" => [ + "Authorization" => "schmalztiegel", + "RunAuthTest" => "yes", + ], + "http_errors" => false, + ]); + $this->assertEquals( + "401", + $response['code'], + 'Unset Exception failed with false' + ); + // if false, null should not change it + $curl = new \CoreLibs\UrlRequests\Curl(["http_errors" => false]); + $response = $curl->request('get', $this->url_basic, [ + "headers" => [ + "Authorization" => "schmalztiegel", + "RunAuthTest" => "yes", + ], + "http_errors" => null, + ]); + $this->assertEquals( + "401", + $response['code'], + 'Unset Exception failed with null' + ); + } +} + +// __END__ 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 new file mode 100644 index 00000000..5bbb9c49 --- /dev/null +++ b/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql @@ -0,0 +1,36 @@ +-- 20241203: update edit tables +ALTER TABLE edit_generic ADD cuuid UUID DEFAULT gen_random_uuid(); +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; +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 + +CREATE OR REPLACE FUNCTION set_edit_generic() +RETURNS TRIGGER AS +$$ +DECLARE + random_length INT = 25; -- that should be long enough +BEGIN + IF TG_OP = 'INSERT' THEN + NEW.date_created := clock_timestamp(); + NEW.cuid := random_string(random_length); + NEW.cuuid := gen_random_uuid(); + ELSIF TG_OP = 'UPDATE' THEN + NEW.date_updated := clock_timestamp(); + END IF; + RETURN NEW; +END; +$$ +LANGUAGE 'plpgsql'; + +-- END -- diff --git a/composer.json b/composer.json index acef5d22..9d009a99 100644 --- a/composer.json +++ b/composer.json @@ -2,10 +2,22 @@ "name": "egrajp/development-corelibs-dev", "version": "dev-master", "description": "CoreLibs: Development package", + "keywords": ["corelib", "logging", "database", "templating", "tools"], "type": "library", - "config": { - }, "require": { - "php": ">=8.1" + "php": ">=8.3" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/extension-installer": "^1.4", + "phan/phan": "^5.4", + "phpunit/phpunit": "^9", + "yamadashy/phpstan-friendly-formatter": "^1.1" + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } } } diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 1131fc81..00000000 --- a/composer.lock +++ /dev/null @@ -1,20 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "3c37bd2878b371840fc0d7d4a249ea4c", - "packages": [], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.3.0" -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..ff91f7eb --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,59 @@ +import globals from 'globals'; +import pluginJs from '@eslint/js'; + +/* +module.exports = { + // in globals block + 'extends': 'eslint:recommended', + 'parserOptions': { + 'ecmaVersion': 6 + }, + // rules copied +}; +*/ + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + {languageOptions: { + globals: { + ...globals.browser, + ...globals.jquery + } + }}, + 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 index cf0eb92f..5fdccbe6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,972 +1,1567 @@ { - "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" - } - } - } + "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 index 3a7b8349..af2d2ff1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,17 @@ { - "devDependencies": { - "@eslint/js": "^9.12.0", - "eslint": "^9.12.0" - } + "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" + } } diff --git a/phpstan.neon b/phpstan.neon index 1e81ac4b..6d3c1508 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,11 +1,22 @@ # PHP Stan Config includes: - phpstan-conditional.php + #- ./vendor/yamadashy/phpstan-friendly-formatter/extension.neon + # - phar://phpstan.phar/conf/bleedingEdge.neon parameters: tmpDir: %currentWorkingDirectory%/tmp/phpstan-corelibs - level: 8 # max is now 9 + #errorFormat: friendly + #friendly: + # lineBefore: 3 + # lineAfter: 3 + level: 8 # max is now 10 + # strictRules: + # allRules: false checkMissingCallableSignature: true treatPhpDocTypesAsCertain: false + # phpVersion: + # min: 80200 # PHP 8.2.0 + # max: 80300 # PHP latest paths: - %currentWorkingDirectory%/www bootstrapFiles: @@ -53,6 +64,6 @@ parameters: # paths: # - ... # - ... - #- - # message: "#^Call to deprecated method #" - # path: www/admin/class_test*.php + # - + # message: "#^Call to deprecated method #" + # path: www/admin/class_test*.php diff --git a/phpunit.xml b/phpunit.xml index dcaf3022..dfc1669b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,8 +1,13 @@ + + + 4dev/tests + + diff --git a/tools/phan b/tools/phan deleted file mode 120000 index 66d455fd..00000000 --- a/tools/phan +++ /dev/null @@ -1 +0,0 @@ -/home/clemens/.phive/phars/phan-5.4.3.phar \ No newline at end of file diff --git a/tools/php-cs-fixer b/tools/php-cs-fixer deleted file mode 120000 index 9f630ba7..00000000 --- a/tools/php-cs-fixer +++ /dev/null @@ -1 +0,0 @@ -/home/clemens/.phive/phars/php-cs-fixer-3.57.2.phar \ No newline at end of file diff --git a/tools/phpDocumentor b/tools/phpDocumentor deleted file mode 120000 index 1c1e04f1..00000000 --- a/tools/phpDocumentor +++ /dev/null @@ -1 +0,0 @@ -/home/clemens/.phive/phars/phpdocumentor-3.4.3.phar \ No newline at end of file diff --git a/tools/phpcbf b/tools/phpcbf deleted file mode 120000 index a592750a..00000000 --- a/tools/phpcbf +++ /dev/null @@ -1 +0,0 @@ -/home/clemens/.phive/phars/phpcbf-3.10.3.phar \ No newline at end of file diff --git a/tools/phpcs b/tools/phpcs deleted file mode 120000 index 26632b89..00000000 --- a/tools/phpcs +++ /dev/null @@ -1 +0,0 @@ -/home/clemens/.phive/phars/phpcs-3.10.3.phar \ No newline at end of file diff --git a/tools/phpdox b/tools/phpdox deleted file mode 120000 index 04ee9c4e..00000000 --- a/tools/phpdox +++ /dev/null @@ -1 +0,0 @@ -/home/clemens/.phive/phars/phpdox-0.12.0.phar \ No newline at end of file diff --git a/tools/phpstan b/tools/phpstan deleted file mode 120000 index 77d91210..00000000 --- a/tools/phpstan +++ /dev/null @@ -1 +0,0 @@ -/home/clemens/.phive/phars/phpstan-1.12.4.phar \ No newline at end of file diff --git a/tools/phpunit b/tools/phpunit deleted file mode 120000 index 3e46cd1d..00000000 --- a/tools/phpunit +++ /dev/null @@ -1 +0,0 @@ -/home/clemens/.phive/phars/phpunit-9.6.21.phar \ No newline at end of file diff --git a/tools/psalm b/tools/psalm deleted file mode 120000 index d280e102..00000000 --- a/tools/psalm +++ /dev/null @@ -1 +0,0 @@ -/home/clemens/.phive/phars/psalm-5.24.0.phar \ No newline at end of file diff --git a/vendor/autoload.php b/vendor/autoload.php deleted file mode 100644 index 6b54bcd8..00000000 --- a/vendor/autoload.php +++ /dev/null @@ -1,25 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Autoload; - -/** - * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. - * - * $loader = new \Composer\Autoload\ClassLoader(); - * - * // register classes with namespaces - * $loader->add('Symfony\Component', __DIR__.'/component'); - * $loader->add('Symfony', __DIR__.'/framework'); - * - * // activate the autoloader - * $loader->register(); - * - * // to enable searching the include path (eg. for PEAR packages) - * $loader->setUseIncludePath(true); - * - * In this example, if you try to use a class in the Symfony\Component - * namespace or one of its children (Symfony\Component\Console for instance), - * the autoloader will first look for the class under the component/ - * directory, and it will then fallback to the framework/ directory if not - * found before giving up. - * - * This class is loosely based on the Symfony UniversalClassLoader. - * - * @author Fabien Potencier - * @author Jordi Boggiano - * @see https://www.php-fig.org/psr/psr-0/ - * @see https://www.php-fig.org/psr/psr-4/ - */ -class ClassLoader -{ - /** @var \Closure(string):void */ - private static $includeFile; - - /** @var ?string */ - private $vendorDir; - - // PSR-4 - /** - * @var array[] - * @psalm-var array> - */ - private $prefixLengthsPsr4 = array(); - /** - * @var array[] - * @psalm-var array> - */ - private $prefixDirsPsr4 = array(); - /** - * @var array[] - * @psalm-var array - */ - private $fallbackDirsPsr4 = array(); - - // PSR-0 - /** - * @var array[] - * @psalm-var array> - */ - private $prefixesPsr0 = array(); - /** - * @var array[] - * @psalm-var array - */ - private $fallbackDirsPsr0 = array(); - - /** @var bool */ - private $useIncludePath = false; - - /** - * @var string[] - * @psalm-var array - */ - private $classMap = array(); - - /** @var bool */ - private $classMapAuthoritative = false; - - /** - * @var bool[] - * @psalm-var array - */ - private $missingClasses = array(); - - /** @var ?string */ - private $apcuPrefix; - - /** - * @var self[] - */ - private static $registeredLoaders = array(); - - /** - * @param ?string $vendorDir - */ - public function __construct($vendorDir = null) - { - $this->vendorDir = $vendorDir; - self::initializeIncludeClosure(); - } - - /** - * @return string[] - */ - public function getPrefixes() - { - if (!empty($this->prefixesPsr0)) { - return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); - } - - return array(); - } - - /** - * @return array[] - * @psalm-return array> - */ - public function getPrefixesPsr4() - { - return $this->prefixDirsPsr4; - } - - /** - * @return array[] - * @psalm-return array - */ - public function getFallbackDirs() - { - return $this->fallbackDirsPsr0; - } - - /** - * @return array[] - * @psalm-return array - */ - public function getFallbackDirsPsr4() - { - return $this->fallbackDirsPsr4; - } - - /** - * @return string[] Array of classname => path - * @psalm-return array - */ - public function getClassMap() - { - return $this->classMap; - } - - /** - * @param string[] $classMap Class to filename map - * @psalm-param array $classMap - * - * @return void - */ - public function addClassMap(array $classMap) - { - if ($this->classMap) { - $this->classMap = array_merge($this->classMap, $classMap); - } else { - $this->classMap = $classMap; - } - } - - /** - * Registers a set of PSR-0 directories for a given prefix, either - * appending or prepending to the ones previously set for this prefix. - * - * @param string $prefix The prefix - * @param string[]|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories - * - * @return void - */ - public function add($prefix, $paths, $prepend = false) - { - if (!$prefix) { - if ($prepend) { - $this->fallbackDirsPsr0 = array_merge( - (array) $paths, - $this->fallbackDirsPsr0 - ); - } else { - $this->fallbackDirsPsr0 = array_merge( - $this->fallbackDirsPsr0, - (array) $paths - ); - } - - return; - } - - $first = $prefix[0]; - if (!isset($this->prefixesPsr0[$first][$prefix])) { - $this->prefixesPsr0[$first][$prefix] = (array) $paths; - - return; - } - if ($prepend) { - $this->prefixesPsr0[$first][$prefix] = array_merge( - (array) $paths, - $this->prefixesPsr0[$first][$prefix] - ); - } else { - $this->prefixesPsr0[$first][$prefix] = array_merge( - $this->prefixesPsr0[$first][$prefix], - (array) $paths - ); - } - } - - /** - * Registers a set of PSR-4 directories for a given namespace, either - * appending or prepending to the ones previously set for this namespace. - * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param string[]|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories - * - * @throws \InvalidArgumentException - * - * @return void - */ - public function addPsr4($prefix, $paths, $prepend = false) - { - if (!$prefix) { - // Register directories for the root namespace. - if ($prepend) { - $this->fallbackDirsPsr4 = array_merge( - (array) $paths, - $this->fallbackDirsPsr4 - ); - } else { - $this->fallbackDirsPsr4 = array_merge( - $this->fallbackDirsPsr4, - (array) $paths - ); - } - } elseif (!isset($this->prefixDirsPsr4[$prefix])) { - // Register directories for a new namespace. - $length = strlen($prefix); - if ('\\' !== $prefix[$length - 1]) { - throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); - } - $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; - $this->prefixDirsPsr4[$prefix] = (array) $paths; - } elseif ($prepend) { - // Prepend directories for an already registered namespace. - $this->prefixDirsPsr4[$prefix] = array_merge( - (array) $paths, - $this->prefixDirsPsr4[$prefix] - ); - } else { - // Append directories for an already registered namespace. - $this->prefixDirsPsr4[$prefix] = array_merge( - $this->prefixDirsPsr4[$prefix], - (array) $paths - ); - } - } - - /** - * Registers a set of PSR-0 directories for a given prefix, - * replacing any others previously set for this prefix. - * - * @param string $prefix The prefix - * @param string[]|string $paths The PSR-0 base directories - * - * @return void - */ - public function set($prefix, $paths) - { - if (!$prefix) { - $this->fallbackDirsPsr0 = (array) $paths; - } else { - $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; - } - } - - /** - * Registers a set of PSR-4 directories for a given namespace, - * replacing any others previously set for this namespace. - * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param string[]|string $paths The PSR-4 base directories - * - * @throws \InvalidArgumentException - * - * @return void - */ - public function setPsr4($prefix, $paths) - { - if (!$prefix) { - $this->fallbackDirsPsr4 = (array) $paths; - } else { - $length = strlen($prefix); - if ('\\' !== $prefix[$length - 1]) { - throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); - } - $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; - $this->prefixDirsPsr4[$prefix] = (array) $paths; - } - } - - /** - * Turns on searching the include path for class files. - * - * @param bool $useIncludePath - * - * @return void - */ - public function setUseIncludePath($useIncludePath) - { - $this->useIncludePath = $useIncludePath; - } - - /** - * Can be used to check if the autoloader uses the include path to check - * for classes. - * - * @return bool - */ - public function getUseIncludePath() - { - return $this->useIncludePath; - } - - /** - * Turns off searching the prefix and fallback directories for classes - * that have not been registered with the class map. - * - * @param bool $classMapAuthoritative - * - * @return void - */ - public function setClassMapAuthoritative($classMapAuthoritative) - { - $this->classMapAuthoritative = $classMapAuthoritative; - } - - /** - * Should class lookup fail if not found in the current class map? - * - * @return bool - */ - public function isClassMapAuthoritative() - { - return $this->classMapAuthoritative; - } - - /** - * APCu prefix to use to cache found/not-found classes, if the extension is enabled. - * - * @param string|null $apcuPrefix - * - * @return void - */ - public function setApcuPrefix($apcuPrefix) - { - $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; - } - - /** - * The APCu prefix in use, or null if APCu caching is not enabled. - * - * @return string|null - */ - public function getApcuPrefix() - { - return $this->apcuPrefix; - } - - /** - * Registers this instance as an autoloader. - * - * @param bool $prepend Whether to prepend the autoloader or not - * - * @return void - */ - public function register($prepend = false) - { - spl_autoload_register(array($this, 'loadClass'), true, $prepend); - - if (null === $this->vendorDir) { - return; - } - - if ($prepend) { - self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; - } else { - unset(self::$registeredLoaders[$this->vendorDir]); - self::$registeredLoaders[$this->vendorDir] = $this; - } - } - - /** - * Unregisters this instance as an autoloader. - * - * @return void - */ - public function unregister() - { - spl_autoload_unregister(array($this, 'loadClass')); - - if (null !== $this->vendorDir) { - unset(self::$registeredLoaders[$this->vendorDir]); - } - } - - /** - * Loads the given class or interface. - * - * @param string $class The name of the class - * @return true|null True if loaded, null otherwise - */ - public function loadClass($class) - { - if ($file = $this->findFile($class)) { - (self::$includeFile)($file); - - return true; - } - - return null; - } - - /** - * Finds the path to the file where the class is defined. - * - * @param string $class The name of the class - * - * @return string|false The path if found, false otherwise - */ - public function findFile($class) - { - // class map lookup - if (isset($this->classMap[$class])) { - return $this->classMap[$class]; - } - if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { - return false; - } - if (null !== $this->apcuPrefix) { - $file = apcu_fetch($this->apcuPrefix.$class, $hit); - if ($hit) { - return $file; - } - } - - $file = $this->findFileWithExtension($class, '.php'); - - // Search for Hack files if we are running on HHVM - if (false === $file && defined('HHVM_VERSION')) { - $file = $this->findFileWithExtension($class, '.hh'); - } - - if (null !== $this->apcuPrefix) { - apcu_add($this->apcuPrefix.$class, $file); - } - - if (false === $file) { - // Remember that this class does not exist. - $this->missingClasses[$class] = true; - } - - return $file; - } - - /** - * Returns the currently registered loaders indexed by their corresponding vendor directories. - * - * @return self[] - */ - public static function getRegisteredLoaders() - { - return self::$registeredLoaders; - } - - /** - * @param string $class - * @param string $ext - * @return string|false - */ - private function findFileWithExtension($class, $ext) - { - // PSR-4 lookup - $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; - - $first = $class[0]; - if (isset($this->prefixLengthsPsr4[$first])) { - $subPath = $class; - while (false !== $lastPos = strrpos($subPath, '\\')) { - $subPath = substr($subPath, 0, $lastPos); - $search = $subPath . '\\'; - if (isset($this->prefixDirsPsr4[$search])) { - $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); - foreach ($this->prefixDirsPsr4[$search] as $dir) { - if (file_exists($file = $dir . $pathEnd)) { - return $file; - } - } - } - } - } - - // PSR-4 fallback dirs - foreach ($this->fallbackDirsPsr4 as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { - return $file; - } - } - - // PSR-0 lookup - if (false !== $pos = strrpos($class, '\\')) { - // namespaced class name - $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) - . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); - } else { - // PEAR-like class name - $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; - } - - if (isset($this->prefixesPsr0[$first])) { - foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { - if (0 === strpos($class, $prefix)) { - foreach ($dirs as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { - return $file; - } - } - } - } - } - - // PSR-0 fallback dirs - foreach ($this->fallbackDirsPsr0 as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { - return $file; - } - } - - // PSR-0 include paths. - if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { - return $file; - } - - return false; - } - - private static function initializeIncludeClosure(): void - { - if (self::$includeFile !== null) { - return; - } - - /** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - * - * @param string $file - * @return void - */ - self::$includeFile = static function($file) { - include $file; - }; - } -} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php deleted file mode 100644 index c6b54af7..00000000 --- a/vendor/composer/InstalledVersions.php +++ /dev/null @@ -1,352 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer; - -use Composer\Autoload\ClassLoader; -use Composer\Semver\VersionParser; - -/** - * This class is copied in every Composer installed project and available to all - * - * See also https://getcomposer.org/doc/07-runtime.md#installed-versions - * - * To require its presence, you can require `composer-runtime-api ^2.0` - * - * @final - */ -class InstalledVersions -{ - /** - * @var mixed[]|null - * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null - */ - private static $installed; - - /** - * @var bool|null - */ - private static $canGetVendors; - - /** - * @var array[] - * @psalm-var array}> - */ - private static $installedByVendor = array(); - - /** - * Returns a list of all package names which are present, either by being installed, replaced or provided - * - * @return string[] - * @psalm-return list - */ - public static function getInstalledPackages() - { - $packages = array(); - foreach (self::getInstalled() as $installed) { - $packages[] = array_keys($installed['versions']); - } - - if (1 === \count($packages)) { - return $packages[0]; - } - - return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); - } - - /** - * Returns a list of all package names with a specific type e.g. 'library' - * - * @param string $type - * @return string[] - * @psalm-return list - */ - public static function getInstalledPackagesByType($type) - { - $packagesByType = array(); - - foreach (self::getInstalled() as $installed) { - foreach ($installed['versions'] as $name => $package) { - if (isset($package['type']) && $package['type'] === $type) { - $packagesByType[] = $name; - } - } - } - - return $packagesByType; - } - - /** - * Checks whether the given package is installed - * - * This also returns true if the package name is provided or replaced by another package - * - * @param string $packageName - * @param bool $includeDevRequirements - * @return bool - */ - public static function isInstalled($packageName, $includeDevRequirements = true) - { - foreach (self::getInstalled() as $installed) { - if (isset($installed['versions'][$packageName])) { - return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); - } - } - - return false; - } - - /** - * Checks whether the given package satisfies a version constraint - * - * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: - * - * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') - * - * @param VersionParser $parser Install composer/semver to have access to this class and functionality - * @param string $packageName - * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package - * @return bool - */ - public static function satisfies(VersionParser $parser, $packageName, $constraint) - { - $constraint = $parser->parseConstraints($constraint); - $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); - - return $provided->matches($constraint); - } - - /** - * Returns a version constraint representing all the range(s) which are installed for a given package - * - * It is easier to use this via isInstalled() with the $constraint argument if you need to check - * whether a given version of a package is installed, and not just whether it exists - * - * @param string $packageName - * @return string Version constraint usable with composer/semver - */ - public static function getVersionRanges($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - $ranges = array(); - if (isset($installed['versions'][$packageName]['pretty_version'])) { - $ranges[] = $installed['versions'][$packageName]['pretty_version']; - } - if (array_key_exists('aliases', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); - } - if (array_key_exists('replaced', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); - } - if (array_key_exists('provided', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); - } - - return implode(' || ', $ranges); - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present - */ - public static function getVersion($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['version'])) { - return null; - } - - return $installed['versions'][$packageName]['version']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present - */ - public static function getPrettyVersion($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['pretty_version'])) { - return null; - } - - return $installed['versions'][$packageName]['pretty_version']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference - */ - public static function getReference($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['reference'])) { - return null; - } - - return $installed['versions'][$packageName]['reference']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. - */ - public static function getInstallPath($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @return array - * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} - */ - public static function getRootPackage() - { - $installed = self::getInstalled(); - - return $installed[0]['root']; - } - - /** - * Returns the raw installed.php data for custom implementations - * - * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. - * @return array[] - * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} - */ - public static function getRawData() - { - @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); - - if (null === self::$installed) { - // only require the installed.php file if this file is loaded from its dumped location, - // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 - if (substr(__DIR__, -8, 1) !== 'C') { - self::$installed = include __DIR__ . '/installed.php'; - } else { - self::$installed = array(); - } - } - - return self::$installed; - } - - /** - * Returns the raw data of all installed.php which are currently loaded for custom implementations - * - * @return array[] - * @psalm-return list}> - */ - public static function getAllRawData() - { - return self::getInstalled(); - } - - /** - * Lets you reload the static array from another file - * - * This is only useful for complex integrations in which a project needs to use - * this class but then also needs to execute another project's autoloader in process, - * and wants to ensure both projects have access to their version of installed.php. - * - * A typical case would be PHPUnit, where it would need to make sure it reads all - * the data it needs from this class, then call reload() with - * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure - * the project in which it runs can then also use this class safely, without - * interference between PHPUnit's dependencies and the project's dependencies. - * - * @param array[] $data A vendor/composer/installed.php data set - * @return void - * - * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data - */ - public static function reload($data) - { - self::$installed = $data; - self::$installedByVendor = array(); - } - - /** - * @return array[] - * @psalm-return list}> - */ - private static function getInstalled() - { - if (null === self::$canGetVendors) { - self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); - } - - $installed = array(); - - if (self::$canGetVendors) { - foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { - if (isset(self::$installedByVendor[$vendorDir])) { - $installed[] = self::$installedByVendor[$vendorDir]; - } elseif (is_file($vendorDir.'/composer/installed.php')) { - $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; - if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { - self::$installed = $installed[count($installed) - 1]; - } - } - } - } - - if (null === self::$installed) { - // only require the installed.php file if this file is loaded from its dumped location, - // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 - if (substr(__DIR__, -8, 1) !== 'C') { - self::$installed = require __DIR__ . '/installed.php'; - } else { - self::$installed = array(); - } - } - $installed[] = self::$installed; - - return $installed; - } -} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE deleted file mode 100644 index f27399a0..00000000 --- a/vendor/composer/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - -Copyright (c) Nils Adermann, Jordi Boggiano - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php deleted file mode 100644 index 0fb0a2c1..00000000 --- a/vendor/composer/autoload_classmap.php +++ /dev/null @@ -1,10 +0,0 @@ - $vendorDir . '/composer/InstalledVersions.php', -); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php deleted file mode 100644 index 15a2ff3a..00000000 --- a/vendor/composer/autoload_namespaces.php +++ /dev/null @@ -1,9 +0,0 @@ -register(true); - - return $loader; - } -} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php deleted file mode 100644 index 4aec4cd6..00000000 --- a/vendor/composer/autoload_static.php +++ /dev/null @@ -1,20 +0,0 @@ - __DIR__ . '/..' . '/composer/InstalledVersions.php', - ); - - public static function getInitializer(ClassLoader $loader) - { - return \Closure::bind(function () use ($loader) { - $loader->classMap = ComposerStaticInitdd705c6e8ab22e0d642372dec7767718::$classMap; - - }, null, ClassLoader::class); - } -} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json deleted file mode 100644 index 87fda747..00000000 --- a/vendor/composer/installed.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "packages": [], - "dev": true, - "dev-package-names": [] -} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php deleted file mode 100644 index 48e2c4cb..00000000 --- a/vendor/composer/installed.php +++ /dev/null @@ -1,23 +0,0 @@ - array( - 'name' => 'egrajp/development-corelibs-dev', - 'pretty_version' => 'dev-master', - 'version' => 'dev-master', - 'reference' => NULL, - 'type' => 'library', - 'install_path' => __DIR__ . '/../../', - 'aliases' => array(), - 'dev' => true, - ), - 'versions' => array( - 'egrajp/development-corelibs-dev' => array( - 'pretty_version' => 'dev-master', - 'version' => 'dev-master', - 'reference' => NULL, - 'type' => 'library', - 'install_path' => __DIR__ . '/../../', - 'aliases' => array(), - 'dev_requirement' => false, - ), - ), -); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php deleted file mode 100644 index 4c3a5d68..00000000 --- a/vendor/composer/platform_check.php +++ /dev/null @@ -1,26 +0,0 @@ -= 80100)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; -} - -if ($issues) { - if (!headers_sent()) { - header('HTTP/1.1 500 Internal Server Error'); - } - if (!ini_get('display_errors')) { - if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { - fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); - } elseif (!headers_sent()) { - echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; - } - } - trigger_error( - 'Composer detected issues in your platform: ' . implode(' ', $issues), - E_USER_ERROR - ); -} diff --git a/www/admin/UrlRequests.target.php b/www/admin/UrlRequests.target.php new file mode 100644 index 00000000..d74e32e8 --- /dev/null +++ b/www/admin/UrlRequests.target.php @@ -0,0 +1,79 @@ + BASE . LOG, + 'log_file_id' => $LOG_FILE_ID, + 'log_per_date' => true, +]); + +/** + * build return json + * + * @param array $http_headers + * @param ?string $body + * @return string + */ +function buildContent(array $http_headers, ?string $body): string +{ + if (is_string($body) && !empty($body)) { + $_body = Json::jsonConvertToArray($body); + if (Json::jsonGetLastError()) { + $body = [$body]; + } else { + $body = $_body; + } + } elseif (is_string($body)) { + $body = []; + } + return Json::jsonConvertArrayTo([ + 'HEADERS' => $http_headers, + "REQUEST_TYPE" => $_SERVER['REQUEST_METHOD'], + "PARAMS" => $_GET, + "BODY" => $body, + // "STRING_BODY" => $body, + ]); +} + +$http_headers = array_filter($_SERVER, function ($value, $key) { + if (str_starts_with($key, 'HTTP_')) { + return true; + } +}, ARRAY_FILTER_USE_BOTH); + +header("Content-Type: application/json; charset=UTF-8"); + +// if the header has Authorization and RunAuthTest then exit with 401 +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(1); +} + +// if server request type is get set file_get to null -> no body +if ($_SERVER['REQUEST_METHOD'] == "GET") { + $file_get = null; +} 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(1); +} +// str_replace('\"', '"', trim($file_get, '"')); + +$log->debug('SERVER', $log->prAr($_SERVER)); +$log->debug('HEADERS', $log->prAr($http_headers)); +$log->debug('REQUEST TYPE', $_SERVER['REQUEST_METHOD']); +$log->debug('GET', $log->prAr($_GET)); +$log->debug('POST', $log->prAr($_POST)); +$log->debug('PHP-INPUT', $log->prAr($file_get)); + +print buildContent($http_headers, $file_get); + +$log->debug('[END]', '=========================================>'); + +// __END__ diff --git a/www/admin/class_test.admin.backend.php b/www/admin/class_test.admin.backend.php index de776a25..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(); @@ -42,6 +42,20 @@ $backend = new CoreLibs\Admin\Backend( $l10n, DEFAULT_ACL_LEVEL ); +$login = new CoreLibs\ACL\Login( + $db, + $log, + $session, + [ + 'auto_login' => false, + 'default_acl_level' => DEFAULT_ACL_LEVEL, + 'logout_target' => '', + 'site_locale' => SITE_LOCALE, + 'site_domain' => SITE_DOMAIN, + 'site_encoding' => SITE_ENCODING, + 'locale_path' => BASE . INCLUDES . LOCALE, + ] +); use CoreLibs\Debug\Support; $PAGE_NAME = 'TEST CLASS: ADMIN BACKEND'; @@ -55,7 +69,45 @@ print '

' . $PAGE_NAME . '

'; print "SETACL[]:
"; $backend->setACL(['EMPTY' => 'EMPTY']); print "ADBEDITLOG:
"; -$backend->adbEditLog('CLASSTEST-ADMIN', 'Some info string'); +$login->writeLog( + 'CLASSTEST-ADMIN-BINARY', + 'Some info string', + $backend->adbGetActionSet(), + write_type:'BINARY' +); +$login->writeLog( + 'CLASSTEST-ADMIN-ZLIB', + 'Some info string', + $backend->adbGetActionSet(), + write_type:'ZLIB' +); +$login->writeLog( + 'CLASSTEST-ADMIN-SERIAL', + 'Some info string', + $backend->adbGetActionSet(), + write_type:'SERIAL' +); +$login->writeLog( + 'CLASSTEST-ADMIN-INVALID', + 'Some info string', + $backend->adbGetActionSet(), + write_type:'INVALID' +); +// test with various +$backend->action = 'TEST ACTION'; +$backend->action_id = 'TEST ACTION ID'; +$backend->action_yes = 'TEST ACTION YES'; +$backend->action_flag = 'TEST ACTION FLAG'; +$backend->action_menu = 'TEST ACTION MENU'; +$backend->action_loaded = 'TEST ACTION LOADED'; +$backend->action_value = 'TEST ACTION VALUE'; +$backend->action_type = 'TEST ACTION TYPE'; +$backend->action_error = 'TEST ACTION ERROR'; +$login->writeLog('CLASSTEST-ADMIN-JSON', [ + "_GET" => $_GET, + "_POST" => $_POST, +], $backend->adbGetActionSet(), write_type:'JSON'); + print "ADBTOPMENU(0): " . Support::printAr($backend->adbTopMenu(CONTENT_PATH)) . "
"; print "ADBMSG:
"; $backend->adbMsg('info', 'Message: %1$d', [1]); diff --git a/www/admin/class_test.array.php b/www/admin/class_test.array.php index cadf8117..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(); @@ -115,9 +115,6 @@ print "ARRAYFLATFORKEY: " . DgS::printAr(ArrayHandler::arrayFlatForKey($test_arr */ function rec(string $pre, string $cur, array $node = []) { - if (!is_array($node)) { - $node = []; - } print "
#### PRE: " . $pre . ", CUR: " . $cur . ", N-c: " . count($node) . " [" . join('|', array_keys($node)) . "]
"; if (!$pre) { @@ -253,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/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 6f809691..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(); @@ -18,7 +18,9 @@ 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; use CoreLibs\Convert\SetVarType; @@ -27,7 +29,36 @@ $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 + * + * @param string $color + * @param string $text + * @param string $text_add + * @return string + */ +function display(string $color, string $text, string $text_add): string +{ + $css = 'margin:5px;padding:50px;' + . 'width:10%;' + . 'text-align:center;' + . 'color:white;text-shadow: 0 0 5px black;font-weight:bold;'; + $template = << + {TEXT} + + HTML; + return str_replace( + ["{COLOR}", "{TEXT}", "{CSS}"], + [ + $color, + $text . (!empty($text_add) ? '
' . $text_add : ''), + $css + ], + $template + ); +} $PAGE_NAME = 'TEST CLASS: CONVERT COLORS'; print ""; @@ -36,32 +67,83 @@ print ""; print ''; print '

' . $PAGE_NAME . '

'; +// out of bounds test + // define a list of from to color sets for conversion test +$hwb = Color::hsbToHwb(new Coordinates\HSB([ + 160, + 0, + 50, +])); +print "HWB: " . DgS::printAr($hwb) . "
"; +$hsb = Color::hwbToHsb($hwb); +print "HSB: " . DgS::printAr($hsb) . "
"; + +$oklch = Color::rgbToOkLch(Coordinates\RGB::create([ + 250, + 0, + 0 +])); +print "OkLch: " . DgS::printAr($oklch) . "
"; +$rgb = Color::okLchToRgb($oklch); +print "OkLch -> RGB: " . DgS::printAr($rgb) . "
"; + +$oklab = Color::rgbToOkLab(Coordinates\RGB::create([ + 250, + 0, + 0 +])); +print "OkLab: " . DgS::printAr($oklab) . "
"; +print display($oklab->toCssString(), $oklab->toCssString(), 'Oklab'); +$rgb = Color::okLabToRgb($oklab); +print "OkLab -> RGB: " . DgS::printAr($rgb) . "
"; +print display($rgb->toCssString(), $rgb->toCssString(), 'OkLab to RGB'); + +$rgb = Coordinates\RGB::create([250, 100, 10])->toLinear(); +print "RGBlinear: " . DgS::printAr($rgb) . "
"; +$rgb = Coordinates\RGB::create([0, 0, 0])->toLinear(); +print "RGBlinear: " . DgS::printAr($rgb) . "
"; + +$cie_lab = Color::okLabToLab($oklab); +print "CieLab: " . DgS::printAr($cie_lab) . "
"; +print display($cie_lab->toCssString(), $cie_lab->toCssString(), 'OkLab to Cie Lab'); + +$rgb = Coordinates\RGB::create([0, 0, 60]); +$hsb = Color::rgbToHsb($rgb); +$rgb_b = Color::hsbToRgb($hsb); +print "RGB: " . DgS::printAr($rgb) . "
"; +print "RGB->HSB: " . DgS::printAr($hsb) . "
"; +print "HSB->RGB: " . DgS::printAr($rgb_b) . "
"; + +$hsl = Coordinates\HSL::create([0, 20, 0]); +$hsb = Coordinates\HSB::create([0, 20, 0]); +$hsl_from_hsb = Color::hsbToHsl($hsb); +print "HSL from HSB: " . DgS::printAr($hsl_from_hsb) . "
"; + +print "
"; + // A(out of bounds) try { print "C::S/COLOR invalid rgb->hex (gray 125): -1, -1, -1: " - . CoreLibs\Convert\Colors::rgb2hex(-1, -1, -1) . "
"; + . (new Coordinates\RGB([-1, -1, -1]))->returnAsHex() . "
"; } catch (\LengthException $e) { - print "*Exception: " . $e->getMessage() . "
" . $e . "
"; -} -try { - print "\$C::S/COLOR invalid rgb->hex (gray 125): -1, -1, -1: " - . $color_class::rgb2hex(-1, -1, -1) . "
"; -} catch (\LengthException $e) { - print "**Exception: " . $e->getMessage() . "
" . print_r($e, true) . "

"; + print "*Exception: " . $e->getMessage() . "
" . print_r($e, true) . "

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

LEGACY

"; // B(valid) -$rgb = [10, 20, 30]; +$rgb = [50, 20, 30]; $hex = '#0a141e'; $hsb = [210, 67, 12]; $hsb_f = [210.5, 67.5, 12.5]; -$hsl = [210, 50, 7.8]; +$hsb = [210, 50, 7.8]; print "S::COLOR rgb->hex: $rgb[0], $rgb[1], $rgb[2]: " . Colors::rgb2hex($rgb[0], $rgb[1], $rgb[2]) . "
"; print "S::COLOR hex->rgb: $hex: " . DgS::printAr(SetVarType::setArray( Colors::hex2rgb($hex) )) . "
"; -print "C::S/COLOR rgb->hext: $hex: " . DgS::printAr(SetVarType::setArray( +print "C::S/COLOR rgb->hex: $hex: " . DgS::printAr(SetVarType::setArray( CoreLibs\Convert\Colors::hex2rgb($hex) )) . "
"; // C(to hsb/hsl) @@ -82,16 +164,18 @@ print "S::COLOR hsb_f->rgb: $hsb_f[0], $hsb_f[1], $hsb_f[2]: " . DgS::printAr(SetVarType::setArray( Colors::hsb2rgb($hsb_f[0], $hsb_f[1], $hsb_f[2]) )) . "
"; -print "S::COLOR hsl->rgb: $hsl[0], $hsl[1], $hsl[2]: " +print "S::COLOR hsl->rgb: $hsb[0], $hsb[1], $hsb[2]: " . DgS::printAr(SetVarType::setArray( - Colors::hsl2rgb($hsl[0], $hsl[1], $hsl[2]) + Colors::hsl2rgb($hsb[0], $hsb[1], $hsb[2]) )) . "
"; $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 "
"; // Random text $h = rand(0, 359); @@ -99,10 +183,18 @@ $s = rand(15, 70); $b = 100; $l = 50; print "RANDOM IN: H: " . $h . ", S: " . $s . ", B/L: " . $b . "/" . $l . "
"; -print "RANDOM hsb->rgb:
" . DgS::printAr(SetVarType::setArray(Colors::hsb2rgb($h, $s, $b))) . "

"; -print "RANDOM hsl->rgb:
" . DgS::printAr(SetVarType::setArray(Colors::hsl2rgb($h, $s, $l))) . "

"; +print "RANDOM hsb->rgb:
"
+	. DgS::printAr(SetVarType::setArray(Color::hsbToRgb(new Coordinates\HSB([$h, $s, $b])))) . "

"; +print "RANDOM hsl->rgb:
"
+	. DgS::printAr(SetVarType::setArray(Color::hslToRgb(new Coordinates\HSL([$h, $s, $l])))) . "

"; -// TODO: run compare check input must match output +print "
"; + +$rgb = [0, 0, 0]; +print "rgb 0,0,0: " . Dgs::printAr($rgb) . " => " + . Dgs::printAr(Color::rgbToHsb(new Coordinates\RGB([$rgb[0], $rgb[1], $rgb[2]]))) . "
"; + +print "
"; print ""; 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..45bca5e8 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(); @@ -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/class_test.db.convert-placeholder.php b/www/admin/class_test.db.convert-placeholder.php new file mode 100644 index 00000000..6504326f --- /dev/null +++ b/www/admin/class_test.db.convert-placeholder.php @@ -0,0 +1,232 @@ + BASE . LOG, + 'log_file_id' => $LOG_FILE_ID, + 'log_per_date' => true, +]); + +$PAGE_NAME = 'TEST CLASS: DB CONVERT PLACEHOLDER'; +print ""; +print "" . $PAGE_NAME . ""; +print ""; +print ''; +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 . "
"; + +$uniqid = \CoreLibs\Create\Uids::uniqIdShort(); +// $binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: ''); +// $binary_data = file_get_contents('class_test.db.php') ?: ''; +$binary_data = ''; +$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 +]; + +$query = <<"; +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: " + . 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: " + . 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: " + . 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: " + . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) + . "
"; +echo "
"; + +print "[P-CONV]: " + . Support::printAr( + ConvertPlaceholder::updateParamList([ + 'original' => [ + 'query' => 'SELECT foo FROM bar WHERE baz = :baz AND buz = :biz AND biz = :biz AND boz = :bez', + 'params' => [':baz' => 'SETBAZ', ':bez' => 'SETBEZ', ':biz' => 'SETBIZ'], + 'empty_params' => false, + ], + 'type' => 'named', + 'found' => 3, + // 'matches' => [ + // ':baz' + // ], + // 'params_lookup' => [ + // ':baz' => '$1' + // ], + // 'query' => "SELECT foo FROM bar WHERE baz = $1", + // 'parms' => [ + // 'SETBAZ' + // ], + ]) + ); + +echo "
"; + +// test connectors: = , <> () for query detection + +// convert placeholder tests +// ? -> $n +// :name -> $n + +// other way around (just visual) +$test_queries = [ + 'skip' => [ + 'query' => << [], + 'direction' => 'pg', + ], + 'numbers' => [ + 'query' => << [\CoreLibs\Create\Uids::uniqIdShort(), 'string A-1', 1234], + 'direction' => 'pdo', + ], + 'a?' => [ + 'query' => << [\CoreLibs\Create\Uids::uniqIdShort(), 'string A-1', 1234], + 'direction' => 'pg', + ], + 'b:' => [ + 'query' => << [ + ':test' => \CoreLibs\Create\Uids::uniqIdShort(), + ':string_a' => 'string B-1', + ':number_a' => 5678 + ], + 'direction' => 'pg', + ], + 'select, compare $' => [ + 'query' => <<= $1 OR row_int <= $2 OR + row_int > $3 OR row_int < $4 + OR row_int = $5 OR row_int <> $6 + SQL, + 'params' => null, + 'direction' => 'pg' + ] +]; + + +foreach ($test_queries as $info => $data) { + $query = $data['query']; + $params = $data['params']; + $direction = $data['direction']; + print "[$info] Convert: " + . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params, $direction)) + . "
"; + echo "
"; +} + +print ""; +$log->debug('DEBUGEND', '==================================== [END]'); + +// __END__ diff --git a/www/admin/class_test.db.dbReturn.php b/www/admin/class_test.db.dbReturn.php index 6d9d141f..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(); @@ -70,8 +70,7 @@ for ($i = 1; $i <= 6; $i++) { print $i . ") " . $cache_flag . ": " . "res: " . (is_bool($res) ? "Bool: " . Support::prBl($res) : - (is_array($res) ? - "Array: " . Support::prBl(is_array($res)) : '{-}') + "Array: Yes" ) . ", " . "cursor_ext:
" . Support::printAr(
 			SetVarType::setArray($db->dbGetCursorExt($q_db_ret))
@@ -89,8 +88,7 @@ for ($i = 1; $i <= 6; $i++) {
 	print $i . ") " . $cache_flag . ": "
 		. "res: " . (is_bool($res) ?
 			"Bool: " . Support::prBl($res) :
-			(is_array($res) ?
-				"Array: " . Support::prBl(is_array($res)) : '{-}')
+			"Array: Yes"
 		) . ", "
 		. "cursor_ext: 
" . Support::printAr(
 			SetVarType::setArray($db->dbGetCursorExt($q_db_ret))
@@ -108,8 +106,7 @@ for ($i = 1; $i <= 6; $i++) {
 	print $i . ") " . $cache_flag . ": "
 		. "res: " . (is_bool($res) ?
 			"Bool: " . Support::prBl($res) :
-			(is_array($res) ?
-				"Array: " . Support::prBl(is_array($res)) : '{-}')
+			"Array: Yes"
 		) . ", "
 		. "cursor_ext: 
" . Support::printAr(
 			SetVarType::setArray($db->dbGetCursorExt($q_db_ret))
@@ -127,8 +124,7 @@ for ($i = 1; $i <= 6; $i++) {
 	print $i . ") " . $cache_flag . ": "
 		. "res: " . (is_bool($res) ?
 			"Bool: " . Support::prBl($res) :
-			(is_array($res) ?
-				"Array: " . Support::prBl(is_array($res)) : '{-}')
+			"Array: Yes"
 		) . ", "
 		. "cursor_ext: 
" . Support::printAr(
 			SetVarType::setArray($db->dbGetCursorExt($q_db_ret))
@@ -146,8 +142,7 @@ for ($i = 1; $i <= 6; $i++) {
 	print $i . ") " . $cache_flag . ": "
 		. "res: " . (is_bool($res) ?
 			"Bool: " . Support::prBl($res) :
-			(is_array($res) ?
-				"Array: " . Support::prBl(is_array($res)) : '{-}')
+			"Array: Yes"
 		) . ", "
 		. "cursor_ext: 
" . Support::printAr(
 			SetVarType::setArray($db->dbGetCursorExt($q_db_ret))
diff --git a/www/admin/class_test.db.php b/www/admin/class_test.db.php
index 31afe4a7..a5537600 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();
 
@@ -228,7 +228,7 @@ print "RETURN ROW PARAMS: " . print_r(
 $db->dbPrepare("ins_test_foo", "INSERT INTO test_foo (test) VALUES ($1) RETURNING test");
 $status = $db->dbExecute("ins_test_foo", ['BAR TEST ' . time()]);
 print "PREPARE INSERT[ins_test_foo] STATUS: " . Support::printToString($status) . " |
" - . "QUERY: " . $db->dbGetPrepareCursorValue('ins_test_foo', 'query') . " |
" + . "QUERY: " . Support::printToString($db->dbGetPrepareCursorValue('ins_test_foo', 'query')) . " |
" . "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " | " . "RETURNING EXT: " . print_r($db->dbGetReturningExt(), true) . " | " . "RETURNING RETURN: " . print_r($db->dbGetReturningArray(), true) . "
"; @@ -239,7 +239,7 @@ print "PREPARE INSERT PREVIOUS INSERTED: " print "PREPARE CURSOR RETURN:
"; foreach (['pk_name', 'count', 'query', 'returning_id'] as $key) { - print "KEY: " . $key . ': ' . $db->dbGetPrepareCursorValue('ins_test_foo', $key) . "
"; + print "KEY: " . $key . ': ' . Support::prAr($db->dbGetPrepareCursorValue('ins_test_foo', $key)) . "
"; } $query = <<dbPrepare("ins_test_foo_eom", $query); $status = $db->dbExecute("ins_test_foo_eom", ['EOM BAR TEST ' . time()]); print "EOM STRING PREPARE INSERT[ins_test_foo_eom] STATUS: " . Support::printToString($status) . " |
" - . "QUERY: " . $db->dbGetPrepareCursorValue('ins_test_foo_eom', 'query') . " |
" + . "QUERY: " . Support::printToString($db->dbGetPrepareCursorValue('ins_test_foo_eom', 'query')) . " |
" . "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " | " . "RETURNING EXT: " . print_r($db->dbGetReturningExt(), true) . " | " . "RETURNING RETURN: " . print_r($db->dbGetReturningArray(), true) . "
"; @@ -273,8 +273,8 @@ $query_insert = <<dbReturnRowParams( $query_select, [$__last_insert_id] - ) + ), + true ) . "
"; // B $status = $db->dbExecParams( @@ -345,7 +346,8 @@ print "EOM STRING EXEC RETURN TEST: " . print_r( $db->dbReturnRowParams( $query_select, [$__last_insert_id] - ) + ), + true ) . "
"; // params > 10 for debug // error catcher @@ -552,7 +554,7 @@ print "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"; @@ -616,7 +618,7 @@ $test_bar = $db->dbEscapeLiteral('SOMETHING DIFFERENT'); $q = <<"; $q = <<"; print "DB RETURN PARAMS LIKE
"; $q = <<"; print "DB RETURN PARAMS ANY
"; $q = <<"; print "COMPOSITE ELEMENT READ
"; $res = $db->dbReturnRow("SELECT item, count, (item).name, (item).price, (item).supplier_id FROM on_hand"); -print "ROW:
" . print_r($res) . "
"; +print "ROW:
" . print_r($res, true) . "
"; var_dump($res); print "Field Name/Types:
" . print_r($db->dbGetFieldNameTypes(), true) . "
"; echo "
"; @@ -705,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.db.query-placeholder.php b/www/admin/class_test.db.query-placeholder.php index ec9c2ff1..c77cff6a 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(); @@ -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,59 +65,135 @@ $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) . "
 |
" . "ERROR: " . $db->dbGetLastError(true) . "
"; echo "
"; +print "ANY call
"; +$query = <<dbReturnParams($query, [$query_value]))) { + print "Result: " . Support::prAr($res) . "
"; +} + +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 @@ -131,6 +210,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); @@ -169,11 +270,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 "
"; } @@ -188,21 +290,43 @@ 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( + <<"; +} +print "ERROR: " . $db->dbGetLastError(true) . "
"; print ""; $db->log->debug('DEBUGEND', '==================================== [END]'); 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..b302c330 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(); @@ -57,6 +57,43 @@ if (($dbh = $db->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 = << 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) . "
"; + +/** + * 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.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..e1823554 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(); @@ -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/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 6296fbd6..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(); @@ -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.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 756a5992..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(); @@ -16,6 +16,8 @@ define('USE_DATABASE', false); require 'config.php'; // define log file id $LOG_FILE_ID = 'classTest-lang'; +$SET_SESSION_NAME = EDIT_SESSION_NAME; +$session = new CoreLibs\Create\Session($SET_SESSION_NAME); ob_end_flush(); $PAGE_NAME = 'TEST CLASS: LANG'; @@ -32,22 +34,21 @@ 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
"; - +/* echo "
AUTO DETECT
"; // DEPRECATED // $get_locale = Language\GetLocale::setLocale(); // print "[AUTO, DEPRECATED]: " . Support::printAr($get_locale) . "
"; @@ -70,10 +71,12 @@ print "[OVERRIDE]: " . Support::printAr($get_locale) . "
"; // DEFAULT_DOMAIN // DEFAULT_CHARSET (should be set from DEFAULT_LOCALE) // LOCALE_PATH -$_SESSION['DEFAULT_LOCALE'] = 'ja_JP.UTF-8'; -$_SESSION['DEFAULT_CHARSET'] = 'UTF-8'; -$_SESSION['DEFAULT_DOMAIN'] = 'admin'; -$_SESSION['LOCALE_PATH'] = BASE . INCLUDES . LOCALE; +$session->setMany([ + 'DEFAULT_LOCALE' => 'ja_JP.UTF-8', + 'DEFAULT_CHARSET' => 'UTF-8', + 'DEFAULT_DOMAIN' => 'admin', + 'LOCALE_PATH' => BASE . INCLUDES . LOCALE, +]); $get_locale = Language\GetLocale::setLocaleFromSession( SITE_LOCALE, SITE_DOMAIN, @@ -86,10 +89,12 @@ print "[SESSION SET]: " . Support::printAr($get_locale) . "
"; // DEFAULT_DOMAIN // DEFAULT_CHARSET (should be set from DEFAULT_LOCALE) // LOCALE_PATH -$_SESSION['DEFAULT_LOCALE'] = '00000#####'; -$_SESSION['DEFAULT_CHARSET'] = ''; -$_SESSION['DEFAULT_DOMAIN'] = 'admin'; -$_SESSION['LOCALE_PATH'] = BASE . INCLUDES . LOCALE; +$session->setMany([ + 'DEFAULT_LOCALE' => '00000#####', + 'DEFAULT_CHARSET' => '', + 'DEFAULT_DOMAIN' => 'admin', + 'LOCALE_PATH' => BASE . INCLUDES . LOCALE, +]); $get_locale = Language\GetLocale::setLocaleFromSession( SITE_LOCALE, SITE_DOMAIN, @@ -97,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.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 af400089..5b717b0b 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(); @@ -17,8 +17,14 @@ 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); +$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, @@ -43,19 +49,108 @@ 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 "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') . "
"; 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->loginGetEuCuid() . "
"; +echo "ECUUID: " . $login->loginGetEuCuuid() . "
"; + +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_cuid'] . "
"; + } +} else { + 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) . "
"; + +$login->writeLog( + 'TEST LOG', + [ + 'test' => 'TEST A' + ], + error:'No Error', + write_type:'JSON' +); + +echo "
"; +print "

Legacy Lookups

"; + +$edit_access_id = 1; +$edit_access_cuid = $login->loginGetEditAccessCuidFromId($edit_access_id); +$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: " . $log->prAr($edit_access_cuid) . "
"; +print "REV EA CUID: " . $log->prAr($edit_access_id_rev) . "
"; print ""; diff --git a/www/admin/class_test.math.php b/www/admin/class_test.math.php index 287dc78f..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(); @@ -23,7 +23,6 @@ $log = new CoreLibs\Logging\Logging([ 'log_file_id' => $LOG_FILE_ID, 'log_per_date' => true, ]); -$_math = new CoreLibs\Convert\Math(); $math_class = 'CoreLibs\Convert\Math'; // define a list of from to color sets for conversion test @@ -35,13 +34,9 @@ print ""; print ''; print '

' . $PAGE_NAME . '

'; -print "FCEIL: " . $_math->fceil(5.1234567890, 5) . "
"; -print "FLOORP: " . $_math->floorp(5123456, -3) . "
"; -print "FLOORP: " . $_math->floorp(5123456, -10) . "
"; -print "INITNUMERIC: " . $_math->initNumeric('123') . "
"; - print "S-FCEIL: " . $math_class::fceil(5.1234567890, 5) . "
"; print "S-FLOORP: " . $math_class::floorp(5123456, -3) . "
"; +print "S-FLOORP: " . $math_class::floorp(5123456, -10) . "
"; print "S-INITNUMERIC: " . $math_class::initNumeric(123) . "
"; print "S-INITNUMERIC: " . $math_class::initNumeric(123.456) . "
"; print "S-INITNUMERIC: " . $math_class::initNumeric('123') . "
"; 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..fec7be5e 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(); @@ -29,15 +29,17 @@ $table_arrays = []; $table_arrays[\CoreLibs\Get\System::getPageName(1)] = [ // form fields mtaching up with db fields 'table_array' => [ + '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.password.php b/www/admin/class_test.password.php index 0d1c390a..7176fc80 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(); @@ -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,6 +53,20 @@ print "PASSWORD REHASH: " . (string)$password_class::passwordRehashCheck($enc_pa // direct static print "S::PASSWORD VERFIY: " . (string)PwdChk::passwordVerify($password, $enc_password) . "
"; +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 [BAD]
"; +} +if (PwdChk::passwordRehashCheck($rehash_test_throw)) { + print "Bad password [OK]
"; +} + print ""; // __END__ diff --git a/www/admin/class_test.php b/www/admin/class_test.php index 315effd3..8772e07b 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(); @@ -62,17 +62,39 @@ $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 = [ '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', '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', @@ -117,7 +139,9 @@ $test_files = [ 'class_test.config.direct.php' => 'Class Test: CONFIG DIRECT', 'class_test.class-calls.php' => 'Class Test: CLASS CALLS', '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); @@ -126,33 +150,20 @@ foreach ($test_files as $file => $name) { print ''; } + +print "
"; +print "ECUID: " . $session->get('LOGIN_EUCUID') . "
"; +print "ECUUID: " . $session->get('LOGIN_EUCUUID') . "
"; + 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 (is_object($login) && 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() . "
"; @@ -174,28 +185,7 @@ foreach ( $log->debug('SOME MARK', 'Some error output'); -// INTERNAL SET -print "EDIT ACCESS ID: " . $backend->edit_access_id . "
"; -if (is_object($login)) { - // 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 . "
"; @@ -205,6 +195,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/admin/class_test.phpv.php b/www/admin/class_test.phpv.php index 63ac0bf5..648dca85 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(); @@ -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/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 9449bee8..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(); @@ -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 ""; // ;; 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 b0a9ba46..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 @@ -45,8 +45,8 @@ $log = new CoreLibs\Logging\Logging([ 'log_file_id' => $LOG_FILE_ID, 'log_per_date' => true, ]); +use CoreLibs\Debug\Support; use CoreLibs\Create\Session; -$session = new Session(); $PAGE_NAME = 'TEST CLASS: SESSION'; print ""; @@ -56,50 +56,30 @@ print ''; print '

' . $PAGE_NAME . '

'; $session_name = 'class-test-session'; +print "Valid session name static check for '" . $session_name . "': " + . Support::prBl(Session::checkValidSessionName($session_name)) . "
"; $var = 'foo'; $value = 'bar'; +$session = new Session($session_name); foreach (['123', '123-123', '123abc'] as $_session_name) { - print "[UNSET] Session Name valid for " . $_session_name . ": " + print "[UNSET] Session Name valid for '" . $_session_name . "': " . ($session->checkValidSessionName($_session_name) ? 'Valid' : 'Invalid') . "
"; } echo "Global session name: " . ($GLOBALS['SET_SESSION_NAME'] ?? '-') . "
"; -print "[UNSET] Current session id: " . $session->getSessionId() . "
"; -print "[UNSET] Current session name: " . $session->getSessionName() . "
"; -print "[UNSET] Current session active: " . ($session->checkActiveSession() ? 'Yes' : 'No') . "
"; -print "[UNSET] Current session status: " . getSessionStatusString($session->getSessionStatus()) . "
"; -if (isset($_SESSION)) { - print "[UNSET] _SESSION is: set
"; -} else { - print "[UNSET] _SESSION is: not set
"; -} -# -print "[UNSET] To set session name valid: " - . ($session->checkValidSessionName($session_name) ? 'Valid' : 'Invalid') . "
"; -try { - $session_id = $session->startSession($session_name); - print "[SET] Current session id: " . $session_id . "
"; -} catch (\Exception $e) { - print "[FAILED] Session start failed:
" . $e->getMessage() . "
" . $e . "
"; -} -// set again -try { - $session_id = $session->startSession($session_name); - print "[2 SET] Current session id: " . $session_id . "
"; -} catch (\Exception $e) { - print "[2 FAILED] Session start failed:
" . $e->getMessage() . "
" . $e . "
"; -} print "[SET] Current session id: " . $session->getSessionId() . "
"; print "[SET] Current session name: " . $session->getSessionName() . "
"; print "[SET] Current session active: " . ($session->checkActiveSession() ? 'Yes' : 'No') . "
"; +print "[SET] Current session auto write close: " . ($session->checkAutoWriteClose() ? 'Yes' : 'No') . "
"; print "[SET] Current session status: " . getSessionStatusString($session->getSessionStatus()) . "
"; if (isset($_SESSION)) { print "[SET] _SESSION is: set
"; } else { print "[SET] _SESSION is: not set
"; } +# if (!isset($_SESSION['counter'])) { $_SESSION['counter'] = 0; } @@ -111,62 +91,85 @@ print "[READ] Confirm " . $var . " is " . $value . ": " . (($_SESSION[$var] ?? '') == $value ? 'Matching' : 'Not matching') . "
"; // test set wrappers methods -$session->setS('setwrap', 'YES, method set _SESSION var'); -print "[READ WRAP] A setwrap: " . $session->getS('setwrap') . "
"; -print "[READ WRAP] Isset: " . ($session->issetS('setwrap') ? 'Yes' : 'No') . "
"; -$session->unsetS('setwrap'); -print "[READ WRAP] unset setwrap: " . $session->getS('setwrap') . "
"; -print "[READ WRAP] unset Isset: " . ($session->issetS('setwrap') ? 'Yes' : 'No') . "
"; -// test __get/__set -$session->setwrap = 'YES, magic set _SESSION var'; /** @phpstan-ignore-line GET/SETTER */ -print "[READ MAGIC] A setwrap: " . ($session->setwrap ?? '') . "
"; -print "[READ MAGIC] Isset: " . (isset($session->setwrap) ? 'Yes' : 'No') . "
"; -unset($session->setwrap); -print "[READ MAGIC] unset setwrap: " . ($session->setwrap ?? '') . "
"; -print "[READ MAGIC] unset Isset: " . (isset($session->setwrap) ? 'Yes' : 'No') . "
"; +$session->set('setwrap', 'YES, method set _SESSION var'); +print "[READ WRAP] A setwrap: " . $session->get('setwrap') . "
"; +print "[READ WRAP] Isset: " . ($session->isset('setwrap') ? 'Yes' : 'No') . "
"; +$session->unset('setwrap'); +print "[READ WRAP] unset setwrap: " . $session->get('setwrap') . "
"; +print "[READ WRAP] unset Isset: " . ($session->isset('setwrap') ? 'Yes' : 'No') . "
"; +$session->set('foo 3', 'brause'); +// set many +$session->setMany([ + 'foo 1' => 'bar', + 'foo 2' => 'kamel', +]); +print "[READ MANY]: " . Support::printAr($session->getMany(['foo 1', 'foo 2'])) . "
"; +try { + $session->setMany([ /** @phpstan-ignore-line deliberate error */ + 'ok' => 'ok', + 'a123' => 'bar', + 1 => 'bar', + ]); +} catch (\Exception $e) { + print "FAILED] Session manySet failed:
" . $e->getMessage() . "
" . $e . "

"; +} +try { + $session->set('123', 'illigal'); +} catch (\Exception $e) { + print "FAILED] Session set failed:
" . $e->getMessage() . "
" . $e . "

"; +} +print "
"; // differnt session name $session_name = 'class-test-session-ALT'; try { - $session_id = $session->startSession($session_name); - print "[3 SET] Current session id: " . $session_id . "
"; + $session_alt = new Session($session_name); + print "[3 SET] Current session id: " . $session_alt->getSessionId() . "
"; + print "[SET AGAIN] Current session id: " . $session_alt->getSessionId() . "
"; } catch (\Exception $e) { - print "[3 FAILED] Session start failed:
" . $e->getMessage() . "
" . $e . "
"; + print "[3 FAILED] Session start failed:
" . $e->getMessage() . "
" . $e . "

"; } -print "[SET AGAIN] Current session id: " . $session->getSessionId() . "
"; -print "[ALL SESSION]: " . \CoreLibs\Debug\Support::printAr($_SESSION) . "
"; + +print "[ALL SESSION]: " . Support::printAr($_SESSION) . "
"; // close session $session->writeClose(); // will never be written $_SESSION['will_never_be_written'] = 'empty'; +// auto open session if closed to write +$session->set('auto_write_session', 'Some value'); +// restart session +$session->restartSession(); +$_SESSION['this_will_be_written'] = 'not empty'; -// open again +// open again with same name $session_name = 'class-test-session'; try { - $session_id = $session->startSession($session_name); - print "[4 SET] Current session id: " . $session_id . "
"; + $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() . "
"; + $session_alt->set('alt_write_auto_close', 'set auto'); + // below is deprecated + // $session_alt->do_not_do_this = 'foo bar auto set'; } catch (\Exception $e) { - print "[4 FAILED] Session start failed:
" . $e->getMessage() . "
" . $e . "
"; + print "[4 FAILED] Session start failed:
" . $e->getMessage() . "
" . $e . "

"; } -print "[START AGAIN] Current session id: " . $session->getSessionId() . "
"; $_SESSION['will_be_written_again'] = 'Full'; +print "[ALL SESSION]: " . Support::printAr($_SESSION) . "
"; + // close session $session->writeClose(); // invalid $session_name = '123'; try { - $session_id = $session->startSession($session_name); - print "[5 SET] Current session id: " . $session_id . "
"; + $session_bad = new Session($session_name); + print "[5 SET] Current session id: " . $session_bad->getSessionId() . "
"; } catch (\Exception $e) { - print "[5 FAILED] Session start failed:
" . $e->getMessage() . "
" . $e . "
"; + print "[5 FAILED] Session start failed:
" . $e->getMessage() . "
" . $e . "

"; } -print "[BAD NAME] Current session id: " . $session->getSessionId() . "
"; -print "[BAD NAME] Current session name: " . $session->getSessionName() . "
"; -print "[BAD NAME] Current session active: " . ($session->checkActiveSession() ? 'Yes' : 'No') . "
"; -print "[BAD NAME] Current session status: " . getSessionStatusString($session->getSessionStatus()) . "
"; print ""; diff --git a/www/admin/class_test.session.read.php b/www/admin/class_test.session.read.php index 09b2330f..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 @@ -46,7 +46,6 @@ $log = new CoreLibs\Logging\Logging([ 'log_per_date' => true, ]); use CoreLibs\Create\Session; -$session = new Session(); $PAGE_NAME = 'TEST CLASS: SESSION (READ)'; print ""; @@ -56,32 +55,22 @@ print ''; print '

' . $PAGE_NAME . '

'; $session_name = 'class-test-session'; +$session = new Session($session_name); // $session_name = ''; $var = 'foo'; $value = 'bar'; echo "Global session name: " . ($GLOBALS['SET_SESSION_NAME'] ?? '-') . "
"; -print "[UNSET] Current session id: " . $session->getSessionId() . "
"; -print "[UNSET] Current session name: " . $session->getSessionName() . "
"; -print "[UNSET] Current session active: " . ($session->checkActiveSession() ? 'Yes' : 'No') . "
"; -print "[UNSET] Current session status: " . getSessionStatusString($session->getSessionStatus()) . "
"; +print "[SET] Current session id: " . $session->getSessionId() . "
"; +print "[SET] Current session name: " . $session->getSessionName() . "
"; +print "[SET] Current session active: " . ($session->checkActiveSession() ? 'Yes' : 'No') . "
"; +print "[SET] Current session status: " . getSessionStatusString($session->getSessionStatus()) . "
"; print "[READ] " . $var . ": " . ($_SESSION[$var] ?? '{UNSET}') . "
"; -// start -try { - $session_id = $session->startSession($session_name); - print "[1] Current session id: " . $session_id . "
"; -} catch (\Exception $e) { - print "[1] Session start failed:
" . $e->getMessage() . "
" . $e . "
"; -} + // set again -try { - $session_id = $session->startSession($session_name); - print "[2] Current session id: " . $session_id . "
"; -} catch (\Exception $e) { - print "[2] Session start failed:
" . $e->getMessage() . "
" . $e . "
"; -} +print "[2] Restarted session: " . \CoreLibs\Debug\Support::prBl($session->restartSession()) . "
"; print "[SET] Current session id: " . $session->getSessionId() . "
"; print "[SET] Current session name: " . $session->getSessionName() . "
"; print "[SET] Current session active: " . ($session->checkActiveSession() ? 'Yes' : 'No') . "
"; diff --git a/www/admin/class_test.smarty.php b/www/admin/class_test.smarty.php index df44f34f..49e95ae9 100644 --- a/www/admin/class_test.smarty.php +++ b/www/admin/class_test.smarty.php @@ -4,9 +4,11 @@ * @phan-file-suppress PhanTypeSuspiciousStringExpression */ +// FIXME: Smarty Class must be updated for PHP 8.4 + 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(); @@ -33,6 +35,7 @@ $l10n = new \CoreLibs\Language\L10n( ); $smarty = new CoreLibs\Template\SmartyExtend( $l10n, + $log, CACHE_ID, COMPILE_ID, ); @@ -45,6 +48,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/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 3801eaed..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(); @@ -52,6 +52,14 @@ print "S:UNIQID (512): " . Uids::uniqId(512) . "
"; // uniq ids print "UNIQU ID SHORT : " . Uids::uniqIdShort() . "
"; print "UNIQU ID LONG : " . Uids::uniqIdLong() . "
"; +// validate +$uuidv4 = Uids::uuidv4(); +if (!Uids::validateUuuidv4($uuidv4)) { + print "Invalid UUIDv4: " . $uuidv4 . "
"; +} +if (!Uids::validateUuuidv4("foobar")) { + print "Invalid UUIDv4: hard coded
"; +} // DEPRECATED /* print "D/UUIDV4: ".$basic->uuidv4()."
"; diff --git a/www/admin/class_test.url-requests.curl.php b/www/admin/class_test.url-requests.curl.php new file mode 100644 index 00000000..b3773c23 --- /dev/null +++ b/www/admin/class_test.url-requests.curl.php @@ -0,0 +1,361 @@ + BASE . LOG, + 'log_file_id' => $LOG_FILE_ID, + 'log_per_date' => true, +]); + +$PAGE_NAME = 'TEST CLASS: URL REQUESTS CURL'; +print ""; +print "" . $PAGE_NAME . ""; +print ""; +print ''; +print '

' . $PAGE_NAME . '

'; + +$client = new Curl(); + +print "
"; +$data = $client->get( + 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/UrlRequests.target.php' + . '?other=get_a', + [ + 'headers' => [ + 'test-header' => 'ABC', + 'info-request-type' => '_GET', + 'Funk-pop' => 'Semlly god' + ], + 'query' => ['foo' => 'BAR'] + ] +); +print "_GET RESPONSE:
" . print_r($data, true) . "
"; + +print "
"; +$data = $client->request( + 'get', + 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/UrlRequests.target.php' + . '?other=get_a', +); +print "_GET RESPONSE, nothing set:
" . print_r($data, true) . "
"; + +print "
"; +try { + $data = $client->request( + 'get', + 'soba54.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/UrlRequests.target.php' + . '?other=get_a', + ); + print "_GET RESPONSE, nothing set, invalid URL:
" . print_r($data, true) . "
"; +} catch (Exception $e) { + print "Exception:
" . print_r($e, true) . "

"; +} + + +print "
"; +$data = $client->request( + "get", + 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/' + . 'trunk/www/admin/UrlRequests.target.php' + . '?other=get_a', + [ + "headers" => [ + 'test-header' => 'ABC', + 'info-request-type' => '_GET', + 'Funk-pop' => 'Semlly god' + ], + "query" => ['foo' => 'BAR'], + ], +); +print "[request] _GET RESPONSE:
" . print_r($data, true) . "
"; + +print "
"; +$data = $client->post( + 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/UrlRequests.target.php' + . '?other=post_a', + [ + 'body' => ['payload' => 'data post'], + 'headers' => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'test-header' => 'ABC', + 'info-request-type' => '_POST', + ], + 'query' => ['foo' => 'BAR post'], + ] +); +print "_POST RESPONSE:
" . print_r($data, true) . "
"; +print "
"; +$data = $client->request( + "post", + 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/UrlRequests.target.php' + . '?other=post_a', + [ + "body" => ['payload' => 'data post', 'request' => 'I am the request body'], + "headers" => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'test-header' => 'ABC', + 'info-request-type' => '_POST', + ], + "query" => ['foo' => 'BAR post'], + ] +); +print "[request] _POST RESPONSE:
" . print_r($data, true) . "
"; +print "
"; +$data = $client->request( + "post", + 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/UrlRequests.target.php' + . '?other=post_a', + [ + "body" => 'string body here', + "headers" => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'test-header' => 'ABC', + 'info-request-type' => '_POST', + ], + "query" => ['foo' => 'BAR post'], + ] +); +print "[request|string body] _POST RESPONSE:
" . print_r($data, true) . "
"; + +print "
"; +$data = $client->put( + 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/UrlRequests.target.php' + . '?other=put_a', + [ + "body" => ['payload' => 'data put'], + "headers" => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'test-header' => 'ABC', + 'info-request-type' => '_PUT', + ], + 'query' => ['foo' => 'BAR put'], + ] +); +print "_PUT RESPONSE:
" . print_r($data, true) . "
"; + +print "
"; +$data = $client->patch( + 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/UrlRequests.target.php' + . '?other=patch_a', + [ + "body" => ['payload' => 'data patch'], + "headers" => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'test-header' => 'ABC', + 'info-request-type' => '_PATCH', + ], + 'query' => ['foo' => 'BAR patch'], + ] +); +print "_PATCH RESPONSE:
" . print_r($data, true) . "
"; + +print "
"; +$data = $client->delete( + 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/UrlRequests.target.php' + . '?other=delete_no_body_a', + [ + "body" => null, + "headers" => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'test-header' => 'ABC', + 'info-request-type' => '_DELETE', + ], + "query" => ['foo' => 'BAR delete'], + ] +); +print "_DELETE RESPONSE:
" . print_r($data, true) . "
"; + +print "
"; +$data = $client->delete( + 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/UrlRequests.target.php' + . '?other=delete_body_a', + [ + "body" => ['payload' => 'data delete'], + "headers" => [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'test-header' => 'ABC', + 'info-request-type' => '_DELETE', + ], + "query" => ['foo' => 'BAR delete'], + ] +); +print "_DELETE RESPONSE BODY:
" . print_r($data, true) . "
"; + +print "
"; + +try { + $uc = new Curl([ + "base_uri" => 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/foo', + "headers" => [ + 'DEFAULT-master' => 'master-header', + 'default-header' => 'uc-get', + 'default-remove' => 'will be removed', + 'default-remove-array' => ['a', 'b'], + 'default-remove-array-part' => ['c', 'd'], + 'default-remove-array-part-alt' => ['c', 'd', 'e'], + 'default-overwrite' => 'will be overwritten', + 'default-add' => 'will be added', + ], + 'query' => [ + 'global-p' => 'glob' + ] + ]); + print "CONFIG:
" . print_r($uc->getConfig(), true) . "
"; + $uc->removeHeaders(['default-remove' => '']); + $uc->removeHeaders(['default-remove-array' => ['a', 'b']]); + $uc->removeHeaders(['default-remove-array-part' => 'c']); + $uc->removeHeaders(['default-remove-array-part-alt' => ['c', 'd']]); + $uc->setHeaders(['default-new' => 'Something new']); + $uc->setHeaders(['default-overwrite' => 'Something Overwritten']); + $uc->setHeaders(['default-add' => 'Something Added'], true); + print "CONFIG:
" . print_r($uc->getConfig(), true) . "
"; + $data = $uc->request( + 'get', + 'UrlRequests.target.php', + [ + 'headers' => [ + 'call-header' => 'call-get', + 'default-header' => 'overwrite-uc-get', + 'X-Foo' => ['bar', 'baz'], + ], + 'query' => [ + 'other' => 'get_a', + ], + ] + ); + print "[uc] _GET RESPONSE, nothing set:
" . print_r($data, true) . "
"; + print "[uc] SENT URL: " . $uc->getUrlSent() . "
"; + print "[uc] SENT URL PARSED:
" . print_r($uc->getUrlParsedSent(), true) . "
"; + print "[uc] SENT HEADERS:
" . print_r($uc->getHeadersSent(), true) . "
"; +} catch (Exception $e) { + print "Exception:
" . print_r(json_decode($e->getMessage(), true), true) . "

"; +} + +print "
"; +try { + $uc = new Curl([ + "base_uri" => 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/', + "http_errors" => false, + "headers" => [ + "Authorization" => "schmalztiegel", + "RunAuthTest" => "yes", + ] + ]); + $response = $uc->get('UrlRequests.target.php'); + print "AUTH REQUEST:
" . print_r($response, true) . "
"; + print "[uc] SENT URL: " . $uc->getUrlSent() . "
"; + print "[uc] SENT URL PARSED:
" . print_r($uc->getUrlParsedSent(), true) . "
"; + print "[uc] SENT HEADERS:
" . print_r($uc->getHeadersSent(), true) . "
"; +} catch (Exception $e) { + print "Exception:
" . print_r(json_decode($e->getMessage(), true), true) . "

"; +} +print "AUTH REQUEST WITH EXCEPTION:
"; +try { + $uc = new Curl([ + "base_uri" => 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/', + "http_errors" => true, + "headers" => [ + "Authorization" => "schmalztiegel", + "RunAuthTest" => "yes", + ] + ]); + $response = $uc->get('UrlRequests.target.php'); + print "AUTH REQUEST:
" . print_r($response, true) . "
"; + print "[uc] SENT URL: " . $uc->getUrlSent() . "
"; + print "[uc] SENT URL PARSED:
" . print_r($uc->getUrlParsedSent(), true) . "
"; + print "[uc] SENT HEADERS:
" . print_r($uc->getHeadersSent(), true) . "
"; +} catch (Exception $e) { + print "Exception:
" . print_r(json_decode($e->getMessage(), true), true) . "

"; +} +print "AUTH REQUEST WITH EXCEPTION (UNSET):
"; +try { + $uc = new Curl([ + "base_uri" => 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/', + "http_errors" => true, + "headers" => [ + "Authorization" => "schmalztiegel", + "RunAuthTest" => "yes", + ] + ]); + $response = $uc->get('UrlRequests.target.php', ['http_errors' => false]); + print "AUTH REQUEST (UNSET):
" . print_r($response, true) . "
"; + print "[uc] SENT URL: " . $uc->getUrlSent() . "
"; + print "[uc] SENT URL PARSED:
" . print_r($uc->getUrlParsedSent(), true) . "
"; + print "[uc] SENT HEADERS:
" . print_r($uc->getHeadersSent(), true) . "
"; +} catch (Exception $e) { + print "Exception:
" . print_r(json_decode($e->getMessage(), true), true) . "

"; +} +print "AUTH REQUEST HEADER SET:
"; +try { + $uc = new Curl([ + "base_uri" => 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/', + "auth" => ["user", "pass", "basic"], + "headers" => [ + "Authorization" => "schmalztiegel", + "RunAuthTest" => "yes", + ] + ]); + $response = $uc->get('UrlRequests.target.php'); + print "AUTH REQUEST (HEADER):
" . print_r($response, true) . "
"; + print "[uc] SENT URL: " . $uc->getUrlSent() . "
"; + print "[uc] SENT URL PARSED:
" . print_r($uc->getUrlParsedSent(), true) . "
"; + print "[uc] SENT HEADERS:
" . print_r($uc->getHeadersSent(), true) . "
"; +} catch (Exception $e) { + print "Exception:
" . print_r(json_decode($e->getMessage(), true), true) . "

"; +} + +print "
"; +$uc = new Curl([ + "base_uri" => 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/', + "headers" => [ + "header-one" => "one" + ] +]); +$response = $uc->get('UrlRequests.target.php', ["headers" => null, "query" => ["test" => "one-test"]]); +print "HEADER RESET REQUEST:
" . print_r($response, true) . "
"; +print "[uc] SENT URL: " . $uc->getUrlSent() . "
"; +print "[uc] SENT URL PARSED:
" . print_r($uc->getUrlParsedSent(), true) . "
"; +print "[uc] SENT HEADERS:
" . print_r($uc->getHeadersSent(), true) . "
"; + +print "
"; +$uc = new Curl([ + "base_uri" => 'https://soba.egplusww.jp/developers/clemens/core_data/php_libraries/trunk/www/admin/', + "headers" => [ + 'bar' => 'foo:bar' + ] +]); +$response = $uc->get('UrlRequests.target.php'); +print "HEADER SET TEST REQUEST:
" . print_r($response, true) . "
"; +print "[uc] SENT URL: " . $uc->getUrlSent() . "
"; +print "[uc] SENT URL PARSED:
" . print_r($uc->getUrlParsedSent(), true) . "
"; +print "[uc] SENT HEADERS:
" . print_r($uc->getHeadersSent(), true) . "
"; + +print ""; + +// __END__ 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/layout/javascript/edit.jq.js b/www/admin/layout/javascript/edit.jq.js index 52da0f1c..6c2605ed 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 @@ -18,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 @@ -156,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 @@ -402,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 { @@ -416,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 { @@ -533,22 +558,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); } } @@ -800,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 @@ -1113,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 + ); } /** @@ -1135,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 = {}; @@ -1173,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) { @@ -1184,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); } @@ -1236,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); @@ -1247,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); } } } @@ -1311,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 @@ -1323,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/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(); 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/composer.lock b/www/composer.lock deleted file mode 100644 index ece22d7f..00000000 --- a/www/composer.lock +++ /dev/null @@ -1,155 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "2c73ea6fc1eba5ffc313409ccaa3b732", - "packages": [ - { - "name": "egrajp/smarty-extended", - "version": "4.5.2", - "dist": { - "type": "zip", - "url": "https://git.egplusww.jp/api/packages/Composer/composer/files/egrajp%2Fsmarty-extended/4.5.2/egrajp-smarty-extended.4.5.2.zip", - "shasum": "a2c67a5047aad349a2cfa54240a44da449df9c4c" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "license": [ - "LGPL-3.0" - ], - "authors": [ - { - "name": "Clemens Schwaighofer", - "email": "clemens.schwaighofer@egplusww.com" - } - ], - "description": "Smarty, extended with gettext, checkbox/radio labels and index numbers", - "homepage": "https://github.com/smarty-php/smarty/", - "keywords": [ - "templating" - ], - "time": "2024-04-16T18:25:27+09:00" - }, - { - "name": "gullevek/dotenv", - "version": "v2.1.0", - "source": { - "type": "git", - "url": "https://github.com/gullevek/dotEnv.git", - "reference": "b9feacaded4e48effff9da7d1173752aef3dc27f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/gullevek/dotEnv/zipball/b9feacaded4e48effff9da7d1173752aef3dc27f", - "reference": "b9feacaded4e48effff9da7d1173752aef3dc27f", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "require-dev": { - "phan/phan": "^5.4", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9" - }, - "type": "library", - "autoload": { - "psr-4": { - "gullevek\\dotEnv\\": "src/", - "gullevek\\dotenv\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Clemens Schwaighofer", - "email": "gullevek@gullevek.org", - "homepage": "http://gullevek.org" - } - ], - "description": "Simple .env file processing and storing in _ENV array", - "homepage": "https://github.com/gullevek/dotEnv", - "keywords": [ - ".env", - "_ENV", - "dotenv", - "environment variables" - ], - "support": { - "issues": "https://github.com/gullevek/dotEnv/issues", - "source": "https://github.com/gullevek/dotEnv/tree/v2.1.0" - }, - "time": "2024-08-21T02:41:15+00:00" - }, - { - "name": "psr/log", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "time": "2021-07-14T16:46:02+00:00" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=8.1" - }, - "platform-dev": [], - "plugin-api-version": "2.6.0" -} diff --git a/www/configs/config.host.php b/www/configs/config.host.php index e95f43a5..41dcdce6 100644 --- a/www/configs/config.host.php +++ b/www/configs/config.host.php @@ -51,6 +51,7 @@ $SITE_CONFIG = [ 'soba.tequila.jp' => $__LOCAL_CONFIG, 'soba.teq.jp' => $__LOCAL_CONFIG, 'soba-local.tokyo.tequila.jp' => $__LOCAL_CONFIG, + 'localhost' => $__LOCAL_CONFIG, ]; // __END__ diff --git a/www/configs/config.master.php b/www/configs/config.master.php index 9d5b9985..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; + 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; + throw new \InvalidArgumentException( + 'No matching DB config found for: "' . HOST_NAME . '". Contact Administrator' + ); } // set SSL on $is_secure = false; diff --git a/www/includes/admin_header.php b/www/includes/admin_header.php index 50797179..b83f36d5 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) @@ -116,7 +116,7 @@ $data = [ // log action // no log if login if (!$login->loginActionRun()) { - $cms->adbEditLog('Submit', $data, 'BINARY'); + $login->writeLog('Submit', $data, action_set:$cms->adbGetActionSet(), write_type:'BINARY'); } //------------------------------ logging end diff --git a/www/includes/edit_base.php b/www/includes/edit_base.php index c5968e96..ad25a4c4 100644 --- a/www/includes/edit_base.php +++ b/www/includes/edit_base.php @@ -24,7 +24,7 @@ declare(strict_types=1); ob_start(); -require 'config.php'; +require getcwd() . DIRECTORY_SEPARATOR . 'config.php'; // should be utf8 header("Content-type: text/html; charset=" . DEFAULT_ENCODING); diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 2a38c219..890119ff 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 @@ -69,19 +70,24 @@ declare(strict_types=1); namespace CoreLibs\ACL; use CoreLibs\Security\Password; +use CoreLibs\Create\Uids; 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 $edit_user_cuid; + /** @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 = ''; /** @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 */ @@ -175,6 +181,7 @@ class Login private array $login_template = [ 'strings' => [], 'password_change' => '', + 'password_forgot' => '', 'template' => '' ]; @@ -193,6 +200,12 @@ class Login /** @var bool */ private bool $login_is_ajax_page = false; + // logging + /** @var array list of allowed types for edit log write */ + private const WRITE_TYPES = ['BINARY', 'BZIP2', 'LZIP', 'STRING', 'SERIAL', 'JSON']; + /** @var array list of available write types for log */ + private array $write_types_available = []; + // settings /** @var array options */ private array $options = []; @@ -204,6 +217,16 @@ class Login 'path' => '', ]; + /** @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 */ @@ -217,17 +240,16 @@ 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 - * $auto_login [default true] DEPRECATED, moved into options + * @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, \CoreLibs\Logging\Logging $log, \CoreLibs\Create\Session $session, - array $options = [] + array $options = [], ) { // attach db class $this->db = $db; @@ -236,148 +258,20 @@ 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' => '[EUID] came in as 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' - ], - ]; - - // init default ACL list array - $_SESSION['DEFAULT_ACL_LIST'] = []; - $_SESSION['DEFAULT_ACL_LIST_TYPE'] = []; - // 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"; - 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 - $_SESSION['DEFAULT_ACL_LIST'] = $this->default_acl_list; - $_SESSION['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 if ($this->options['auto_login'] === true) { @@ -386,7 +280,7 @@ class Login } // ************************************************************************* - // **** PROTECTED INTERNAL + // **** MARK: PROTECTED INTERNAL // ************************************************************************* /** @@ -404,6 +298,7 @@ class Login } else { $this->log->critical($message, ['code' => $code]); } + // TODO throw error and not exit exit($code); } @@ -429,7 +324,7 @@ class Login } // ************************************************************************* - // **** PRIVATE INTERNAL + // **** MARK: PRIVATE INTERNAL // ************************************************************************* /** @@ -556,6 +451,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'])) { @@ -567,7 +476,6 @@ class Login // set path $options['locale_path'] = BASE . INCLUDES . LOCALE; } - $_SESSION['LOCALE_PATH'] = $options['locale_path']; // LANG: LOCALE if (empty($options['site_locale'])) { trigger_error( @@ -602,7 +510,6 @@ class Login $options['set_domain'] = str_replace(DIRECTORY_SEPARATOR, '', CONTENT_PATH); } } - $_SESSION['DEFAULT_DOMAIN'] = $options['site_domain']; // LANG: ENCODING if (empty($options['site_encoding'])) { trigger_error( @@ -612,12 +519,212 @@ 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 + /** * Checks for all flags and sets error codes for each * In order: @@ -628,6 +735,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( @@ -635,7 +743,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) { @@ -653,6 +762,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; } @@ -736,6 +847,113 @@ class Login return $login_id_ok; } + /** + * 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, * but only if there is no preview euid set @@ -745,7 +963,11 @@ 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)) { + // 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 @@ -755,86 +977,8 @@ 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 - $q = "SELECT eu.edit_user_id, 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)) . " " - ); - // reset any query data that might exist - $this->db->dbCacheReset($q); - // never cache return data - $res = $this->db->dbReturn($q, $this->db::NO_CACHE); - // query was not run successful - if (!empty($this->db->dbGetLastError())) { - $this->login_error = 1009; - $this->permission_okay = false; - return; - } elseif (!is_array($res)) { - // username is wrong, but we throw for wrong username - // and wrong password the same error - $this->login_error = 1010; - $this->permission_okay = false; + // 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 @@ -852,7 +996,8 @@ class Login (int)$res['enabled'], (int)$res['locked'], (int)$res['locked_period'], - (int)$res['login_user_id_locked'] + (int)$res['login_user_id_locked'], + (int)$res['force_logout'] ) ) { // error set in method (104, 105, 106, 107, 108) @@ -881,226 +1026,31 @@ 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 - $_SESSION['EUID'] = $this->euid = (int)$res['edit_user_id']; - // check if user is okay - $this->loginCheckPermissions(); - if ($this->login_error == 0) { - 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); - } - // now set all session vars and read page permissions - $_SESSION['DEBUG_ALL'] = $this->db->dbBoolean($res['debug']); - $_SESSION['DB_DEBUG'] = $this->db->dbBoolean($res['db_debug']); - // general info for user logged in - $_SESSION['USER_NAME'] = $res['username']; - $_SESSION['ADMIN'] = $res['admin']; - $_SESSION['GROUP_NAME'] = $res['edit_group_name']; - $_SESSION['USER_ACL_LEVEL'] = $res['user_level']; - $_SESSION['USER_ACL_TYPE'] = $res['user_type']; - $_SESSION['USER_ADDITIONAL_ACL'] = Json::jsonConvertToArray($res['user_additional_acl']); - $_SESSION['GROUP_ACL_LEVEL'] = $res['group_level']; - $_SESSION['GROUP_ACL_TYPE'] = $res['group_type']; - $_SESSION['GROUP_ADDITIONAL_ACL'] = Json::jsonConvertToArray($res['group_additional_acl']); - // deprecated TEMPLATE setting - $_SESSION['TEMPLATE'] = $res['template'] ? $res['template'] : ''; - $_SESSION['HEADER_COLOR'] = !empty($res['second_header_color']) ? - $res['second_header_color'] : - $res['first_header_color']; - // missing # before, this is for legacy data, will be deprecated - if (preg_match("/^[\dA-Fa-f]{6,8}$/", $_SESSION['HEADER_COLOR'])) { - $_SESSION['HEADER_COLOR'] = '#' . $_SESSION['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() - // LANGUAGE/LOCALE/ENCODING: - $_SESSION['LANG'] = $res['locale'] ?? 'en'; - $_SESSION['DEFAULT_CHARSET'] = $res['encoding'] ?? 'UTF-8'; - $_SESSION['DEFAULT_LOCALE'] = $_SESSION['LANG'] - . '.' . strtoupper($_SESSION['DEFAULT_CHARSET']); - $_SESSION['DEFAULT_LANG'] = $_SESSION['LANG'] . '_' - . strtolower(str_replace('-', '', $_SESSION['DEFAULT_CHARSET'])); - // 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); - } - $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 ($res = $this->db->dbReturn($q)) { - if (!is_array($res)) { - break; - } - // 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'], - // 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 - // 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))) { - $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))) { - $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 = "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))) { - $pages[$edit_page_ids[$res['edit_page_id']]]['content'][$res['uid']] = [ - 'name' => $res['name'], - 'uid' => $res['uid'], - '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 - $_SESSION['PAGES'] = $pages; - $_SESSION['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"; - $unit_access = []; - $eauid = []; - $unit_acl = []; - while (is_array($res = $this->db->dbReturn($q))) { - // 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']; - $ea_data = []; - while (is_array($res_sub = $this->db->dbReturn($q_sub))) { - $ea_data[$res_sub['name']] = $res_sub['value']; - } - // build master unit array - $unit_access[$res['edit_access_id']] = [ - 'id' => (int)$res['edit_access_id'], - '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 - ]; - // set the default unit - if ($res['edit_default']) { - $_SESSION['UNIT_DEFAULT'] = (int)$res['edit_access_id']; - } - $_SESSION['UNIT_UID'][$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']; - } - $_SESSION['UNIT'] = $unit_access; - $_SESSION['UNIT_ACL_LEVEL'] = $unit_acl; - $_SESSION['EAID'] = $eauid; - } // user has permission to THIS page + $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 - 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 - $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); - // 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 = "UPDATE edit_user SET locked = 1 WHERE edit_user_id = " . $res['edit_user_id']; - } - } - } + // 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 @@ -1108,6 +1058,399 @@ class Login } } + /** + * 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 = <<= 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, + -- 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 = ''; + // 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]; + } 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, $params); + // never cache return data + $res = $this->db->dbReturnParams($q, $params, $this->db::NO_CACHE); + // query was not run successful + if (!empty($this->db->dbGetLastError())) { + $this->login_error = 1009; + $this->permission_okay = false; + return false; + } elseif (!is_array($res)) { + // username is wrong, but we throw for wrong username + // and wrong password the same error + // 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; + } + // 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 ( + !empty($res['login_user_id']) && + !empty($this->username) && !empty($this->password) + ) { + $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')) + ) { + $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, [$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 + ]; + // LEGACY LOOKUP + $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']; + } + $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 + /** * sets all the basic ACLs * init set the basic acl the user has, based on the following rules @@ -1133,18 +1476,23 @@ 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']; + // DEPRECATED + $this->acl['euid'] = $_SESSION['LOGIN_EUID']; + // edit user cuid + $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 { @@ -1152,80 +1500,86 @@ 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']; } } - $_SESSION['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; + $this->acl['unit_cuid'] = null; $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['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_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] = [ + 'id' => $unit['id'], 'name' => $unit['name'], 'uid' => $unit['uid'], - 'level' => $this->default_acl_list[$this->acl['unit'][$ea_id]]['name'] ?? -1, + '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'] ]; // 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']; } } // 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; } // 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) @@ -1234,6 +1588,8 @@ class Login // $this->debug('ACL', $this->print_ar($this->acl)); } + // MARK: login set locale + /** * set locale * if invalid, set to empty string @@ -1293,6 +1649,8 @@ class Login ]; } + // MARK: password handling + /** * checks if the password is in a valid format * @@ -1303,11 +1661,9 @@ class Login { $is_valid_password = true; // check for valid in regex arrays in list - if (is_array($this->password_valid_chars)) { - foreach ($this->password_valid_chars as $password_valid_chars) { - if (!preg_match("/$password_valid_chars/", $password)) { - $is_valid_password = false; - } + foreach ($this->password_valid_chars as $password_valid_chars) { + if (!preg_match("/$password_valid_chars/", $password)) { + $is_valid_password = false; } } // check for min length @@ -1430,9 +1786,11 @@ class Login $data = 'Illegal user for password change: ' . $this->pw_username; } // log this password change attempt - $this->writeLog($event, $data, $this->login_error, $this->pw_username); + $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 @@ -1461,6 +1819,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']; @@ -1477,14 +1839,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 ); } @@ -1492,8 +1854,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 { @@ -1520,18 +1885,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 @@ -1542,6 +1911,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 @@ -1563,15 +1934,19 @@ class Login $event = 'No Permission'; } // prepare for log - if ($this->euid) { + if ($this->edit_user_cuuid) { // 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->edit_user_cuuid]))) { $username = $res['username']; } } // if euid is set, get username (or try) - $this->writeLog($event, '', $this->login_error, $username); + $this->writeEditLog($event, '', $this->login_error, $username); } // write log under certain settings // now close DB connection // $this->error_msg = $this->_login(); @@ -1582,6 +1957,8 @@ class Login } } + // MARK: set template for login page + /** * checks if there are external templates, if not uses internal fallback ones * @@ -1608,39 +1985,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, [ @@ -1659,74 +2090,167 @@ 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 } } + // MARK: LOGGING + /** * writes detailed data into the edit user log table (keep log what user does) * @@ -1736,7 +2260,7 @@ HTML; * @param string $username login user username * @return void has no return */ - private function writeLog( + private function writeEditLog( string $event, string $data, string|int $error = '', @@ -1754,50 +2278,210 @@ HTML; '_GET' => $_GET, '_POST' => $_POST, '_FILES' => $_FILES, - 'error' => $this->login_error + 'error' => $this->login_error, + 'data' => $data, ]; - $data_binary = $this->db->dbEscapeBytea((string)bzcompress(serialize($_data_binary))); - // SQL querie for log entry - $q = "INSERT INTO edit_log " - . "(username, password, euid, event_date, event, error, data, data_binary, page, " - . "ip, user_agent, referer, script_name, query_string, server_name, http_host, " - . "http_accept, http_accept_charset, http_accept_encoding, session_id, " - . "action, action_id, action_yes, action_flag, action_menu, action_loaded, " - . "action_value, action_error) " - . "VALUES ('" . $this->db->dbEscapeString($username) . "', 'PASSWORD', " - . ($this->euid ? $this->euid : 'NULL') . ", " - . "NOW(), '" . $this->db->dbEscapeString($event) . "', " - . "'" . $this->db->dbEscapeString((string)$error) . "', " - . "'" . $this->db->dbEscapeString($data) . "', '" . $data_binary . "', " - . "'" . $this->page_name . "', "; - foreach ( - [ - 'REMOTE_ADDR', 'HTTP_USER_AGENT', 'HTTP_REFERER', 'SCRIPT_FILENAME', - 'QUERY_STRING', 'SERVER_NAME', 'HTTP_HOST', 'HTTP_ACCEPT', - 'HTTP_ACCEPT_CHARSET', 'HTTP_ACCEPT_ENCODING' - ] as $server_code - ) { - if (array_key_exists($server_code, $_SERVER)) { - $q .= "'" . $this->db->dbEscapeString($_SERVER[$server_code]) . "', "; - } else { - $q .= "NULL, "; - } + $_action_set = [ + 'action' => $this->action, + 'action_id' => $this->username, + 'action_flag' => (string)$this->login_error, + 'action_value' => (string)$this->permission_okay, + ]; + + $this->writeLog($event, $_data_binary, $_action_set, $error, $username); + } + + /** + * writes all action vars plus other info into edit_log table + * this is for public class + * + * phpcs:disable Generic.Files.LineLength + * @param string $event [default=''] any kind of event description, + * @param string|array $data [default=''] any kind of data related to that event + * @param array{action?:?string,action_id?:null|string|int,action_sub_id?:null|string|int,action_yes?:null|string|int|bool,action_flag?:?string,action_menu?:?string,action_loaded?:?string,action_value?:?string,action_type?:?string,action_error?:?string} $action_set [default=[]] action set names + * @param string|int $error error id (mostly an int) + * @param string $write_type [default=JSON] write type can be + * JSON, STRING/SERIEAL, BINARY/BZIP or ZLIB + * @param string|null $db_schema [default=null] override target schema + * @return void + * phpcs:enable Generic.Files.LineLength + */ + public function writeLog( + string $event = '', + string|array $data = '', + array $action_set = [], + string|int $error = '', + string $username = '', + string $write_type = 'JSON', + ?string $db_schema = null + ): void { + $data_binary = ''; + $data_write = ''; + + // check if write type is valid, if not fallback to JSON + if (!in_array(strtoupper($write_type), $this->write_types_available)) { + $this->log->warning('Write type not in allowed array, fallback to JSON', context:[ + "write_type" => $write_type, + "write_list" => $this->write_types_available, + ]); + $write_type = 'JSON'; + } + switch ($write_type) { + case 'BINARY': + case 'BZIP': + $data_binary = $this->db->dbEscapeBytea((string)bzcompress(serialize($data))); + $data_write = Json::jsonConvertArrayTo([ + 'type' => 'BZIP', + 'message' => 'see bzip compressed data_binary field' + ]); + break; + case 'ZLIB': + $data_binary = $this->db->dbEscapeBytea((string)gzcompress(serialize($data))); + $data_write = Json::jsonConvertArrayTo([ + 'type' => 'ZLIB', + 'message' => 'see zlib compressed data_binary field' + ]); + break; + case 'STRING': + case 'SERIAL': + $data_binary = $this->db->dbEscapeBytea(Json::jsonConvertArrayTo([ + 'type' => 'SERIAL', + 'message' => 'see serial string data field' + ])); + $data_write = serialize($data); + break; + case 'JSON': + $data_binary = $this->db->dbEscapeBytea(Json::jsonConvertArrayTo([ + 'type' => 'JSON', + 'message' => 'see json string data field' + ])); + // must be converted to array + if (!is_array($data)) { + $data = ["data" => $data]; + } + $data_write = Json::jsonConvertArrayTo($data); + break; + default: + $this->log->alert('Invalid type for data compression was set', context:[ + "write_type" => $write_type + ]); + break; + } + + /** @var string $DB_SCHEMA check schema */ + $DB_SCHEMA = 'public'; + if ($db_schema !== null) { + $DB_SCHEMA = $db_schema; + } elseif (!empty($this->db->dbGetSchema())) { + $DB_SCHEMA = $this->db->dbGetSchema(); + } + $q = <<db->dbExecParams( + str_replace( + ['{DB_SCHEMA}'], + [$DB_SCHEMA], + $q + ), + [ + // row 1 + 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, + $data_binary, + (string)$this->page_name, + // row 2 + $_SERVER["REMOTE_ADDR"] ?? null, + Json::jsonConvertArrayTo([ + 'REMOTE_ADDR' => $_SERVER["REMOTE_ADDR"] ?? null, + '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, + $_SERVER['QUERY_STRING'] ?? null, + $_SERVER['REQUEST_SCHEME'] ?? null, + $_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, + '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' + ); + } + + /** + * set the write types that are allowed + * + * @return void + */ + private function loginSetEditLogWriteTypeAvailable() + { + // check what edit log data write types are allowed + $this->write_types_available = self::WRITE_TYPES; + if (!function_exists('bzcompress')) { + $this->write_types_available = array_diff($this->write_types_available, ['BINARY', 'BZIP']); + } + if (!function_exists('gzcompress')) { + $this->write_types_available = array_diff($this->write_types_available, ['LZIP']); } - $q .= "'" . $this->session->getSessionId() . "', "; - $q .= "'" . $this->db->dbEscapeString($this->action) . "', "; - $q .= "'" . $this->db->dbEscapeString($this->username) . "', "; - $q .= "NULL, "; - $q .= "'" . $this->db->dbEscapeString((string)$this->login_error) . "', "; - $q .= "NULL, NULL, "; - $q .= "'" . $this->db->dbEscapeString((string)$this->permission_okay) . "', "; - $q .= "NULL)"; - $this->db->dbExec($q, 'NULL'); } // ************************************************************************* // **** PUBLIC INTERNAL // ************************************************************************* + // MARK: MASTER PUBLIC LOGIN CALL + /** * Main call that needs to be run to actaully check for login * If this is not called, no login checks are done, unless the class @@ -1855,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; @@ -1866,7 +2550,7 @@ HTML; } } // if there is none, there is none, saves me POST/GET check - $this->euid = array_key_exists('EUID', $_SESSION) ? (int)$_SESSION['EUID'] : 0; + $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 @@ -1875,11 +2559,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 @@ -1889,10 +2573,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 @@ -1947,6 +2633,8 @@ HTML; $this->loginSetAcl(); } + // MARK: setters/getters + /** * Returns current set login_html content * @@ -1977,6 +2665,119 @@ 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('LOGIN_HEADER_COLOR'); + } + + /** + * Return the current loaded list of pages the user can access + * + * @return array + */ + public function loginGetPages(): array + { + + return $this->session->get('LOGIN_PAGES'); + } + + // MARK: logged in uid(pk)/eucuid/eucuuid + + /** + * Get the current set EUID (edit user id) + * + * @return string EUID as string + */ + public function loginGetEuid(): string + { + return (string)$this->edit_user_id; + } + + /** + * Get the current set EUCUID (edit user cuid) + * + * @return string EUCUID as string + */ + public function loginGetEuCuid(): string + { + return (string)$this->edit_user_cuid; + } + + /** + * Get the current set EUCUUID (edit user cuuid) + * + * @return string EUCUUID as string + * @deprecated Wrong name, use ->loginGetEuCuuid + */ + public function loginGetEcuuid(): string + { + 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; + } + + // MARK: get error messages + /** * returns the last set error code * @@ -2023,6 +2824,8 @@ HTML; return $string; } + // MARK: password checks + /** * Sets the minium length and checks on valid. * Current max length is 255 characters @@ -2075,6 +2878,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 @@ -2101,6 +2906,8 @@ HTML; return $this->max_login_error_count; } + // MARK: LGOUT USER + /** * if a user pressed on logout, destroyes session and unsets all global vars * @@ -2115,11 +2922,15 @@ HTML; // unset session vars set/used in this login $this->session->sessionDestroy(); // unset euid - $this->euid = 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; } + // MARK: logged in user permssion check + /** * for every page the user access this script checks if he is allowed to do so * @@ -2130,46 +2941,61 @@ 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 eucuid and eucuuid // bail for previous wrong page match, eg if method is called twice if ($this->login_error == 103) { return $this->permission_okay; } - $q = "SELECT ep.filename, " - // 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.cuuid = $1 + AND ep.filename = $2 + SQL; + $res = $this->db->dbReturnRowParams($q, [$this->edit_user_cuuid, $this->page_name]); if (!is_array($res)) { $this->login_error = 109; return $this->permission_okay; @@ -2180,7 +3006,8 @@ HTML; (int)$res['enabled'], (int)$res['locked'], (int)$res['locked_period'], - (int)$res['login_user_id_locked'] + (int)$res['login_user_id_locked'], + (int)$res['force_logout'] ) ) { // errors set in method @@ -2203,6 +3030,8 @@ HTML; } else { $this->login_error = 103; } + // set all the internal vars + $this->loginSetEditUserUidData($res); // if called from public, so we can check if the permissions are ok return $this->permission_okay; } @@ -2217,6 +3046,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) @@ -2317,24 +3148,68 @@ HTML; return (int)$this->default_acl_list_type[$type]; } + // MARK: edit access helpers + /** * checks if this edit access id is valid * * @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('LOGIN_UNIT')) && + !array_key_exists($cuid, $this->session->get('LOGIN_UNIT')) + ) { + $cuid = null; + if (!empty($this->session->get('LOGIN_UNIT_DEFAULT_EACUID'))) { + $cuid = $this->session->get('LOGIN_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 @@ -2343,39 +3218,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 && - isset($_SESSION['UNIT']) && - is_array($_SESSION['UNIT']) && - !array_key_exists($edit_access_id, $_SESSION['UNIT']) + 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 (is_numeric($_SESSION['UNIT_DEFAULT'])) { - $edit_access_id = (int)$_SESSION['UNIT_DEFAULT']; + if (!empty($this->session->get('LOGIN_UNIT_DEFAULT_EAID'))) { + $edit_access_id = (int)$this->session->get('LOGIN_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['LOGIN_UNIT'][$cuid]['data'][$data_key])) { return false; } - return $_SESSION['UNIT'][$edit_access_id]['data'][$data_key]; + return $_SESSION['LOGIN_UNIT'][$cuid]['data'][$data_key]; } /** @@ -2383,14 +3258,57 @@ 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])) { + if (!isset($_SESSION['LOGIN_UNIT_UID'][$uid])) { return false; } - return (int)$_SESSION['UNIT_UID'][$uid]; + return (int)$_SESSION['LOGIN_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['LOGIN_UNIT_CUID'][$uid])) { + return false; + } + 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_LEGACY'][$id])) { + return false; + } + return (string)$_SESSION['LOGIN_UNIT_LEGACY'][$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'][$cuid])) { + return false; + } + return $_SESSION['LOGIN_UNIT'][$cuid]['id']; } /** @@ -2406,6 +3324,8 @@ HTML; return false; } + // MARK: various basic login id checks + /** * Returns true if login button was pressed * @@ -2415,99 +3335,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 $_SESSION['HEADER_COLOR'] ?? null; - } - - /** - * Return the current loaded list of pages the user can access - * - * @return array - */ - public function loginGetPages(): array - { - - return $_SESSION['PAGES'] ?? []; - } - - /** - * Get the current set EUID (edit user id) - * - * @return string EUID as string - */ - public function loginGetEuid(): string - { - return (string)$this->euid; - } } // __END__ diff --git a/www/lib/CoreLibs/ACL/LoginUserStatus.php b/www/lib/CoreLibs/ACL/LoginUserStatus.php new file mode 100644 index 00000000..ddc5a84f --- /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__ diff --git a/www/lib/CoreLibs/Admin/Backend.php b/www/lib/CoreLibs/Admin/Backend.php index d35ccdc1..cd64e2e1 100644 --- a/www/lib/CoreLibs/Admin/Backend.php +++ b/www/lib/CoreLibs/Admin/Backend.php @@ -31,6 +31,9 @@ declare(strict_types=1); namespace CoreLibs\Admin; +use CoreLibs\Create\Uids; +use CoreLibs\Convert\Json; + class Backend { // page name @@ -42,7 +45,7 @@ class Backend /** @var array */ public array $action_list = [ 'action', 'action_id', 'action_sub_id', 'action_yes', 'action_flag', - 'action_menu', 'action_value', 'action_error', 'action_loaded' + 'action_menu', 'action_value', 'action_type', 'action_error', 'action_loaded' ]; /** @var string */ public string $action; @@ -61,20 +64,31 @@ class Backend /** @var string */ public string $action_value; /** @var string */ + public string $action_type; + /** @var string */ public string $action_error; + // ACL array variable if we want to set acl data from outisde /** @var array */ public array $acl = []; /** @var int */ public int $default_acl; + // queue key /** @var string */ public string $queue_key; + + /** @var array list of allowed types for edit log write */ + private const WRITE_TYPES = ['BINARY', 'BZIP2', 'LZIP', 'STRING', 'SERIAL', 'JSON']; + /** @var array list of available write types for log */ + private array $write_types_available = []; + // the current active edit access id /** @var int|null */ public int|null $edit_access_id; /** @var string */ public string $page_name; + // error/warning/info messages /** @var array */ public array $messages = []; @@ -84,6 +98,7 @@ class Backend public bool $warning = false; /** @var bool */ public bool $info = false; + // internal lang & encoding vars /** @var string */ public string $lang_dir = ''; @@ -95,6 +110,7 @@ class Backend public string $domain; /** @var string */ public string $encoding; + /** @var \CoreLibs\Logging\Logging logger */ public \CoreLibs\Logging\Logging $log; /** @var \CoreLibs\DB\IO database */ @@ -103,6 +119,7 @@ class Backend public \CoreLibs\Language\L10n $l; /** @var \CoreLibs\Create\Session session class */ public \CoreLibs\Create\Session $session; + // smarty publics [end processing in smarty class] /** @var array */ public array $DATA = []; @@ -172,9 +189,12 @@ class Backend } // queue key - if (preg_match("/^(add|save|delete|remove|move|up|down|push_live)$/", $this->action)) { + if (preg_match("/^(add|save|delete|remove|move|up|down|push_live)$/", $this->action ?? '')) { $this->queue_key = \CoreLibs\Create\RandomKey::randomKeyGen(3); } + + // check what edit log data write types are allowed + $this->adbSetEditLogWriteTypeAvailable(); } /** @@ -185,7 +205,26 @@ class Backend // NO OP } - // PUBLIC METHODS |=================================================> + // MARK: PRIVATE METHODS + + /** + * set the write types that are allowed + * + * @return void + */ + private function adbSetEditLogWriteTypeAvailable() + { + // check what edit log data write types are allowed + $this->write_types_available = self::WRITE_TYPES; + if (!function_exists('bzcompress')) { + $this->write_types_available = array_diff($this->write_types_available, ['BINARY', 'BZIP']); + } + if (!function_exists('gzcompress')) { + $this->write_types_available = array_diff($this->write_types_available, ['LZIP']); + } + } + + // MARK: PUBLIC METHODS |=================================================> /** * set internal ACL from login ACL @@ -220,30 +259,94 @@ class Backend } } + /** + * return all the action data, if not set, sets entry to null + * + * @return array{action:?string,action_id:null|string|int,action_sub_id:null|string|int,action_yes:null|string|int|bool,action_flag:?string,action_menu:?string,action_loaded:?string,action_value:?string,action_type:?string,action_error:?string} + */ + public function adbGetActionSet(): array + { + return [ + 'action' => $this->action ?? null, + 'action_id' => $this->action_id ?? null, + 'action_sub_id' => $this->action_sub_id ?? null, + 'action_yes' => $this->action_yes ?? null, + 'action_flag' => $this->action_flag ?? null, + 'action_menu' => $this->action_menu ?? null, + 'action_loaded' => $this->action_loaded ?? null, + 'action_value' => $this->action_value ?? null, + 'action_type' => $this->action_type ?? null, + 'action_error' => $this->action_error ?? null, + ]; + } + /** * writes all action vars plus other info into edit_log table * - * @param string $event any kind of event description, - * @param string|array $data any kind of data related to that event - * @param string $write_type write type can bei STRING or BINARY - * @param string|null $db_schema override target schema + * @param string $event [default=''] any kind of event description, + * @param string|array $data [default=''] any kind of data related to that event + * @param string $write_type [default=JSON] write type can be + * JSON, STRING/SERIEAL, BINARY/BZIP or ZLIB + * @param string|null $db_schema [default=null] override target schema * @return void + * @deprecated Use $login->writeLog($event, $data, action_set:$cms->adbGetActionSet(), write_type:$write_type) */ public function adbEditLog( string $event = '', string|array $data = '', - string $write_type = 'STRING', + string $write_type = 'JSON', ?string $db_schema = null ): void { $data_binary = ''; $data_write = ''; - if ($write_type == 'BINARY') { - $data_binary = $this->db->dbEscapeBytea((string)bzcompress(serialize($data))); - $data_write = 'see bzip compressed data_binary field'; + // check if write type is valid, if not fallback to JSON + if (!in_array($write_type, $this->write_types_available)) { + $this->log->warning('Write type not in allowed array, fallback to JSON', context:[ + "write_type" => $write_type, + "write_list" => $this->write_types_available, + ]); + $write_type = 'JSON'; } - if ($write_type == 'STRING') { - $data_binary = ''; - $data_write = $this->db->dbEscapeString(serialize($data)); + switch ($write_type) { + case 'BINARY': + case 'BZIP': + $data_binary = $this->db->dbEscapeBytea((string)bzcompress(serialize($data))); + $data_write = Json::jsonConvertArrayTo([ + 'type' => 'BZIP', + 'message' => 'see bzip compressed data_binary field' + ]); + break; + case 'ZLIB': + $data_binary = $this->db->dbEscapeBytea((string)gzcompress(serialize($data))); + $data_write = Json::jsonConvertArrayTo([ + 'type' => 'ZLIB', + 'message' => 'see zlib compressed data_binary field' + ]); + break; + case 'STRING': + case 'SERIAL': + $data_binary = $this->db->dbEscapeBytea(Json::jsonConvertArrayTo([ + 'type' => 'SERIAL', + 'message' => 'see serial string data field' + ])); + $data_write = serialize($data); + break; + case 'JSON': + $data_binary = $this->db->dbEscapeBytea(Json::jsonConvertArrayTo([ + 'type' => 'JSON', + 'message' => 'see json string data field' + ])); + // must be converted to array + if (!is_array($data)) { + $data = ["data" => $data]; + } + $data_write = Json::jsonConvertArrayTo($data); + break; + default: + $this->log->alert('Invalid type for data compression was set', context:[ + "write_type" => $write_type + ]); + break; } /** @var string $DB_SCHEMA check schema */ @@ -253,44 +356,69 @@ class Backend } elseif (!empty($this->db->dbGetSchema())) { $DB_SCHEMA = $this->db->dbGetSchema(); } - $q = "INSERT INTO " . $DB_SCHEMA . ".edit_log " - . "(euid, event_date, event, data, data_binary, page, " - . "ip, user_agent, referer, script_name, query_string, server_name, http_host, " - . "http_accept, http_accept_charset, http_accept_encoding, session_id, " - . "action, action_id, action_yes, action_flag, action_menu, action_loaded, action_value, action_error) " - . "VALUES " - . "(" . $this->db->dbEscapeString(isset($_SESSION['EUID']) && is_numeric($_SESSION['EUID']) ? - $_SESSION['EUID'] : - 'NULL') - . ", " - . "NOW(), " - . "'" . $this->db->dbEscapeString((string)$event) . "', " - . "'" . $data_write . "', " - . "'" . $data_binary . "', " - . "'" . $this->db->dbEscapeString((string)$this->page_name) . "', " - . "'" . ($_SERVER["REMOTE_ADDR"] ?? '') . "', " - . "'" . $this->db->dbEscapeString($_SERVER['HTTP_USER_AGENT'] ?? '') . "', " - . "'" . $this->db->dbEscapeString($_SERVER['HTTP_REFERER'] ?? '') . "', " - . "'" . $this->db->dbEscapeString($_SERVER['SCRIPT_FILENAME'] ?? '') . "', " - . "'" . $this->db->dbEscapeString($_SERVER['QUERY_STRING'] ?? '') . "', " - . "'" . $this->db->dbEscapeString($_SERVER['SERVER_NAME'] ?? '') . "', " - . "'" . $this->db->dbEscapeString($_SERVER['HTTP_HOST'] ?? '') . "', " - . "'" . $this->db->dbEscapeString($_SERVER['HTTP_ACCEPT'] ?? '') . "', " - . "'" . $this->db->dbEscapeString($_SERVER['HTTP_ACCEPT_CHARSET'] ?? '') . "', " - . "'" . $this->db->dbEscapeString($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '') . "', " - . ($this->session->getSessionId() === false ? - "NULL" : - "'" . $this->session->getSessionId() . "'") - . ", " - . "'" . $this->db->dbEscapeString($this->action) . "', " - . "'" . $this->db->dbEscapeString($this->action_id) . "', " - . "'" . $this->db->dbEscapeString($this->action_yes) . "', " - . "'" . $this->db->dbEscapeString($this->action_flag) . "', " - . "'" . $this->db->dbEscapeString($this->action_menu) . "', " - . "'" . $this->db->dbEscapeString($this->action_loaded) . "', " - . "'" . $this->db->dbEscapeString($this->action_value) . "', " - . "'" . $this->db->dbEscapeString($this->action_error) . "')"; - $this->db->dbExec($q, 'NULL'); + $q = <<db->dbExecParams( + str_replace( + ['{DB_SCHEMA}'], + [$DB_SCHEMA], + $q + ), + [ + // row 1 + '', + 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('ECUID')) ? + $this->session->get('ECUID') : null, + (string)$event, + '', + $data_write, + $data_binary, + (string)$this->page_name, + // row 2 + $_SERVER["REMOTE_ADDR"] ?? '', + $_SERVER['HTTP_USER_AGENT'] ?? '', + $_SERVER['HTTP_REFERER'] ?? '', + $_SERVER['SCRIPT_FILENAME'] ?? '', + $_SERVER['QUERY_STRING'] ?? '', + $_SERVER['SERVER_NAME'] ?? '', + $_SERVER['HTTP_HOST'] ?? '', + // row 3 + $_SERVER['HTTP_ACCEPT'] ?? '', + $_SERVER['HTTP_ACCEPT_CHARSET'] ?? '', + $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '', + $this->session->getSessionId() !== '' ? + $this->session->getSessionId() : null, + // row 4 + $this->action ?? '', + $this->action_id ?? '', + $this->action_sub_id ?? '', + $this->action_yes ?? '', + $this->action_flag ?? '', + $this->action_menu ?? '', + $this->action_loaded ?? '', + $this->action_value ?? '', + $this->action_type ?? '', + $this->action_error ?? '', + ], + 'NULL' + ); } /** @@ -327,10 +455,7 @@ class Backend ?string $set_content_path = null, int $flag = 0, ): array { - if ( - $set_content_path === null || - !is_string($set_content_path) - ) { + if ($set_content_path === null) { /** @deprecated adbTopMenu missing set_content_path parameter */ trigger_error( 'Calling adbTopMenu without set_content_path parameter is deprecated', @@ -343,7 +468,7 @@ class Backend } // get the session pages array - $PAGES = $_SESSION['PAGES'] ?? null; + $PAGES = $this->session->get('PAGES'); if (!isset($PAGES) || !is_array($PAGES)) { $PAGES = []; } @@ -540,16 +665,30 @@ class Backend } elseif (!empty($this->db->dbGetSchema())) { $DB_SCHEMA = $this->db->dbGetSchema(); } - $q = "INSERT INTO " . $DB_SCHEMA . ".live_queue (" - . "queue_key, key_value, key_name, type, target, data, group_key, action, associate, file" - . ") VALUES (" - . "'" . $this->db->dbEscapeString($queue_key) . "', '" . $this->db->dbEscapeString($key_value) . "', " - . "'" . $this->db->dbEscapeString($key_name) . "', '" . $this->db->dbEscapeString($type) . "', " - . "'" . $this->db->dbEscapeString($target) . "', '" . $this->db->dbEscapeString($data) . "', " - . "'" . $this->queue_key . "', '" . $this->action . "', " - . "'" . $this->db->dbEscapeString((string)$associate) . "', " - . "'" . $this->db->dbEscapeString((string)$file) . "')"; - $this->db->dbExec($q); + $q = <<db->dbExec($q); + $this->db->dbExecParams( + str_replace( + ['{DB_SCHEMA}'], + [$DB_SCHEMA], + $q + ), + [ + $queue_key, $key_value, + $key_name, $type, + $target, $data, + $this->queue_key, $this->action, + (string)$associate, (string)$file + ] + ); } /** diff --git a/www/lib/CoreLibs/Admin/EditBase.php b/www/lib/CoreLibs/Admin/EditBase.php index f740b53a..754c33b5 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 */ @@ -63,6 +60,7 @@ class EditBase // smarty template engine (extended Translation version) $this->smarty = new \CoreLibs\Template\SmartyExtend( $l10n, + $log, $options['cache_id'] ?? '', $options['compile_id'] ?? '', ); @@ -78,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)); } @@ -415,8 +413,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'); @@ -540,8 +536,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/Basic.php b/www/lib/CoreLibs/Basic.php index 065ec63e..db57672a 100644 --- a/www/lib/CoreLibs/Basic.php +++ b/www/lib/CoreLibs/Basic.php @@ -1139,118 +1139,6 @@ class Basic // *** BETTER PASSWORD OPTIONS END *** - // *** COLORS *** - // [!!! DEPRECATED !!!] - // moved to \CoreLibs\Convert\Colors - - /** - * converts a hex RGB color to the int numbers - * @param string $hexStr RGB hexstring - * @param bool $returnAsString flag to return as string - * @param string $seperator string seperator: default: "," - * @return string|array|bool false on error or array with RGB or - * a string with the seperator - * @deprecated use \CoreLibs\Convert\Colors::hex2rgb() instead - */ - public static function hex2rgb(string $hexStr, bool $returnAsString = false, string $seperator = ',') - { - trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::hex2rgb()', E_USER_DEPRECATED); - return \CoreLibs\Convert\Colors::hex2rgb($hexStr, $returnAsString, $seperator); - } - - /** - * converts the rgb values from int data to the valid rgb html hex string - * optional can turn of leading # - * @param int $red red 0-255 - * @param int $green green 0-255 - * @param int $blue blue 0-255 - * @param bool $hex_prefix default true, prefix with "#" - * @return string|bool rgb in hex values with leading # if set - * @deprecated use \CoreLibs\Convert\Colors::rgb2hex() instead - */ - public static function rgb2hex(int $red, int $green, int $blue, bool $hex_prefix = true) - { - trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::rgb2hex()', E_USER_DEPRECATED); - return \CoreLibs\Convert\Colors::rgb2hex($red, $green, $blue, $hex_prefix); - } - - /** - * converts and int RGB to the HTML color string in hex format - * @param int $red red 0-255 - * @param int $green green 0-255 - * @param int $blue blue 0-255 - * @return string|bool hex rgb string - * @deprecated use rgb2hex instead - */ - public static function rgb2html(int $red, int $green, int $blue) - { - trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::rgb2hex()', E_USER_DEPRECATED); - // check that each color is between 0 and 255 - return \CoreLibs\Convert\Colors::rgb2hex($red, $green, $blue, true); - } - - /** - * converts RGB to HSB/V values - * returns: - * array with hue (0-360), sat (0-100%), brightness/value (0-100%) - * @param int $red red 0-255 - * @param int $green green 0-255 - * @param int $blue blue 0-255 - * @return array|bool Hue, Sat, Brightness/Value - * @deprecated use \CoreLibs\Convert\Colors::rgb2hsb() instead - */ - public static function rgb2hsb(int $red, int $green, int $blue) - { - trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::rgb2hsb()', E_USER_DEPRECATED); - return \CoreLibs\Convert\Colors::rgb2hsb($red, $green, $blue); - } - - /** - * converts HSB/V to RGB values RGB is full INT - * @param int $H hue 0-360 - * @param float $S saturation 0-1 (float) - * @param float $V brightness/value 0-1 (float) - * @return array|bool 0 red/1 green/2 blue array - * @deprecated use \CoreLibs\Convert\Colors::hsb2rgb() instead - */ - public static function hsb2rgb(int $H, float $S, float $V) - { - trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::hsb2rgb()', E_USER_DEPRECATED); - return \CoreLibs\Convert\Colors::hsb2rgb($H, (int)round($S * 100), (int)round($V * 100)); - } - - /** - * converts a RGB (0-255) to HSL - * return: - * array with hue (0-360), saturation (0-100%) and luminance (0-100%) - * @param int $r red 0-255 - * @param int $g green 0-255 - * @param int $b blue 0-255 - * @return array|bool hue/sat/luminance - * @deprecated use \CoreLibs\Convert\Colors::rgb2hsl() instead - */ - public static function rgb2hsl(int $r, int $g, int $b) - { - trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::rgb2hsl()', E_USER_DEPRECATED); - return \CoreLibs\Convert\Colors::rgb2hsb($r, $g, $b); - } - - /** - * converts an HSL to RGB - * @param int $h hue: 0-360 (degrees) - * @param float $s saturation: 0-1 - * @param float $l luminance: 0-1 - * @return array|bool red/blue/green 0-255 each - * @deprecated use \CoreLibs\Convert\Colors::hsl2rgb() instead - */ - public static function hsl2rgb(int $h, float $s, float $l) - { - trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::hsl2rgb()', E_USER_DEPRECATED); - return \CoreLibs\Convert\Colors::hsl2rgb($h, $s * 100, $l * 100); - } - - // *** COLORS END *** - // *** EMAIL FUNCTIONS *** // [!!! DEPRECATED !!!] // Moved to \CoreLibs\Check\Email diff --git a/www/lib/CoreLibs/Check/Colors.php b/www/lib/CoreLibs/Check/Colors.php index d896664a..8630dd12 100644 --- a/www/lib/CoreLibs/Check/Colors.php +++ b/www/lib/CoreLibs/Check/Colors.php @@ -119,6 +119,13 @@ class Colors /** * check if html/css color string is valid + * + * TODO: update check for correct validate values + * - space instead of "," + * - / opcatiy checks + * - loose numeric values + * - lab/lch,oklab/oklch validation too + * * @param string $color A color string of any format * @param int $flags defaults to ALL, else use | to combined from * HEX_RGB, HEX_RGBA, RGB, RGBA, HSL, HSLA @@ -168,9 +175,9 @@ class Colors if (preg_match("/$regex/", $color)) { // if valid regex, we now need to check if the content is actually valid // only for rgb/hsl type - /** @var int|false */ + /** @var int<0, max>|false */ $rgb_flag = strpos($color, 'rgb'); - /** @var int|false */ + /** @var int<0, max>|false */ $hsl_flag = strpos($color, 'hsl'); // if both not match, return true if ( diff --git a/www/lib/CoreLibs/Combined/ArrayHandler.php b/www/lib/CoreLibs/Combined/ArrayHandler.php index bbe3943f..bd9a3b58 100644 --- a/www/lib/CoreLibs/Combined/ArrayHandler.php +++ b/www/lib/CoreLibs/Combined/ArrayHandler.php @@ -509,6 +509,48 @@ class ArrayHandler } return $array; } + + /** + * Remove entries from a simple array, will not keep key order + * + * any array content is allowed + * + * https://stackoverflow.com/a/369608 + * + * @param array $array Array where elements are located + * @param array $remove Elements to remove + * @return array Array with $remove elements removed + */ + public static function arrayRemoveEntry(array $array, array $remove): array + { + return array_diff($array, $remove); + } + + /** + * 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 + * + * @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__ 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/Convert/Color/CieXyz.php b/www/lib/CoreLibs/Convert/Color/CieXyz.php new file mode 100644 index 00000000..40c90bb0 --- /dev/null +++ b/www/lib/CoreLibs/Convert/Color/CieXyz.php @@ -0,0 +1,359 @@ +fromLinear(); + } + + /** + * Convert RGB to CIE Lab + * via xyz D65 to xyz D50 + * + * @param RGB $rgb + * @return Lab + */ + public static function rgbViaXyzD65ViaXyzD50ToLab(RGB $rgb): Lab + { + return self::xyzD50ToLab( + self::xyzD65ToXyzD50( + self::linRgbToXyzD65($rgb) + ) + ); + } + + /** + * Convert CIE Lab to RGB + * via xyz D50 to xyz D65 + * + * @param Lab $lab + * @return RGB + */ + public static function labViaXyzD50ViaXyzD65ToRgb(Lab $lab): RGB + { + return self::xyzD65ToLinRgb( + self::xyzD50ToXyxD65( + self::labToXyzD50($lab) + ) + )->fromLinear(); + } + + /** + * Convert from oklab to cie lab + * + * @param Lab $lab + * @return Lab + */ + public static function okLabViaXyzD65ViaXyzD50ToLab(Lab $lab): Lab + { + return self::xyzD50ToLab( + self::xyzD65ToXyzD50( + self::okLabToXyzD65($lab) + ) + ); + } + + /** + * Convert from cie lab to oklab + * + * @param Lab $lab + * @return Lab + */ + public static function labViaXyzD50ViaXyzD65ToOkLab(Lab $lab): Lab + { + return self::xyzD65ToOkLab( + self::xyzD50ToXyxD65( + self::labToXyzD50($lab) + ) + ); + } + + // MARK: helper convert any array to array{float, float, float} + + /** + * This is a hack for phpstan until we write a proper matrix to class + * conversion wrapper function + * + * @param array|float|int> $_array + * @return array{0:float,1:float,2:float} + */ + private static function convertArray(array $_array): array + { + /** @var array{0:float,1:float,2:float} */ + return [$_array[0], $_array[1], $_array[2]]; + } + + // MARK: xyzD65 <-> xyzD50 + + /** + * xyzD65 to xyzD50 whitepoint + * + * @param XYZ $xyz + * @return XYZ + */ + private static function xyzD65ToXyzD50(XYZ $xyz): XYZ + { + return new XYZ(self::convertArray(Math::multiplyMatrices( + a: [ + [1.0479298208405488, 0.022946793341019088, -0.05019222954313557], + [0.029627815688159344, 0.990434484573249, -0.01707382502938514], + [-0.009243058152591178, 0.015055144896577895, 0.7518742899580008], + ], + b: $xyz->returnAsArray(), + )), options: ["whitepoint" => 'D50']); + } + + /** + * xyzD50 to xyzD65 whitepoint + * + * @param XYZ $xyz + * @return XYZ + */ + private static function xyzD50ToXyxD65(XYZ $xyz): XYZ + { + return new XYZ(self::convertArray(Math::multiplyMatrices( + a: [ + [0.9554734527042182, -0.023098536874261423, 0.0632593086610217], + [-0.028369706963208136, 1.0099954580058226, 0.021041398966943008], + [0.012314001688319899, -0.020507696433477912, 1.3303659366080753], + ], + b: $xyz->returnAsArray() + )), options: ["whitepoint" => 'D65']); + } + + // MARK: xyzD50 <-> Lab + + /** + * Convert xyzD50 to Lab (Cie) + * + * @param XYZ $xyz + * @return Lab + */ + private static function xyzD50ToLab(XYZ $xyz): Lab + { + $_xyz = $xyz->returnAsArray(); + $d50 = [ + 0.3457 / 0.3585, + 1.00000, + (1.0 - 0.3457 - 0.3585) / 0.3585, + ]; + + $a = 216 / 24389; + $b = 24389 / 27; + + $_xyz = array_map( + fn ($k, $v) => $v / $d50[$k], + array_keys($_xyz), + array_values($_xyz), + ); + + $f = array_map( + fn ($v) => (($v > $a) ? + pow($v, 1 / 3) : + (($b * $v + 16) / 116) + ), + $_xyz, + ); + + return new Lab([ + (116 * $f[1]) - 16, + 500 * ($f[0] - $f[1]), + 200 * ($f[1] - $f[2]), + ], colorspace: 'CIELab'); + } + + /** + * Convert Lab (Cie) to xyz D50 + * + * @param Lab $lab + * @return XYZ + */ + private static function labToXyzD50(Lab $lab): XYZ + { + $_lab = $lab->returnAsArray(); + $a = 24389 / 27; + $b = 216 / 24389; + $f = []; + $f[1] = ($_lab[0] + 16) / 116; + $f[0] = $_lab[1] / 500 + $f[1]; + $f[2] = $f[1] - $_lab[2] / 200; + $xyz = [ + // x + pow($f[0], 3) > $b ? + pow($f[0], 3) : + (116 * $f[0] - 16) / $a, + // y + $_lab[0] > $a * $b ? + pow(($_lab[0] + 16) / 116, 3) : + $_lab[0] / $a, + // z + pow($f[2], 3) > $b ? + pow($f[2], 3) : + (116 * $f[2] - 16) / $a, + ]; + + $d50 = [ + 0.3457 / 0.3585, + 1.00000, + (1.0 - 0.3457 - 0.3585) / 0.3585, + ]; + + return new XYZ( + self::convertArray(array_map( + fn ($k, $v) => $v * $d50[$k], + array_keys($xyz), + $xyz, + )), + options: ["whitepoint" => 'D50'] + ); + } + + // MARK: xyzD65 <-> (linear)RGB + + /** + * convert linear RGB to xyz D65 + * if rgb is not flagged linear, it will be auto converted + * + * @param RGB $rgb + * @return XYZ + */ + private static function linRgbToXyzD65(RGB $rgb): XYZ + { + // if not linear, convert to linear + if (!(bool)$rgb->get('linear')) { + $rgb = (new RGB($rgb->returnAsArray()))->toLinear(); + } + return new XYZ(self::convertArray(Math::multiplyMatrices( + [ + [0.41239079926595934, 0.357584339383878, 0.1804807884018343], + [0.21263900587151027, 0.715168678767756, 0.07219231536073371], + [0.01933081871559182, 0.11919477979462598, 0.9505321522496607], + ], + $rgb->returnAsArray() + )), options: ["whitepoint" => 'D65']); + } + + /** + * Convert xyz D65 to linear RGB + * + * @param XYZ $xyz + * @return RGB + */ + private static function xyzD65ToLinRgb(XYZ $xyz): RGB + { + // xyz D65 to linrgb + return new RGB(self::convertArray(Math::multiplyMatrices( + a : [ + [ 3.2409699419045226, -1.537383177570094, -0.4986107602930034 ], + [ -0.9692436362808796, 1.8759675015077202, 0.04155505740717559 ], + [ 0.05563007969699366, -0.20397695888897652, 1.0569715142428786 ], + ], + b : $xyz->returnAsArray() + )), options: ["linear" => true]); + } + + // MARK: xyzD65 <-> OkLab + + /** + * xyz D65 to OkLab + * + * @param XYZ $xyz + * @return Lab + */ + private static function xyzD65ToOkLab(XYZ $xyz): Lab + { + return new Lab(self::convertArray(Math::multiplyMatrices( + [ + [0.2104542553, 0.7936177850, -0.0040720468], + [1.9779984951, -2.4285922050, 0.4505937099], + [0.0259040371, 0.7827717662, -0.8086757660], + ], + array_map( + callback: fn ($v) => pow((float)$v, 1 / 3), + array: Math::multiplyMatrices( + a: [ + [0.8190224432164319, 0.3619062562801221, -0.12887378261216414], + [0.0329836671980271, 0.9292868468965546, 0.03614466816999844], + [0.048177199566046255, 0.26423952494422764, 0.6335478258136937], + ], + b: $xyz->returnAsArray(), + ), + ) + )), colorspace: 'OkLab'); + } + + /** + * xyz D65 to OkLab + * + * @param Lab $lab + * @return XYZ + */ + private static function okLabToXyzD65(Lab $lab): XYZ + { + return new XYZ(self::convertArray(Math::multiplyMatrices( + a: [ + [1.2268798733741557, -0.5578149965554813, 0.28139105017721583], + [-0.04057576262431372, 1.1122868293970594, -0.07171106666151701], + [-0.07637294974672142, -0.4214933239627914, 1.5869240244272418], + ], + b: array_map( + callback: fn ($v) => is_numeric($v) ? $v ** 3 : 0, + array: Math::multiplyMatrices( + a: [ + [0.99999999845051981432, 0.39633779217376785678, 0.21580375806075880339], + [1.0000000088817607767, -0.1055613423236563494, -0.063854174771705903402], + [1.0000000546724109177, -0.089484182094965759684, -1.2914855378640917399], + ], + // Divide $lightness by 100 to convert from CSS OkLab + b: $lab->returnAsArray(), + ), + ), + )), options: ["whitepoint" => 'D65']); + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Color.php b/www/lib/CoreLibs/Convert/Color/Color.php new file mode 100644 index 00000000..016d71af --- /dev/null +++ b/www/lib/CoreLibs/Convert/Color/Color.php @@ -0,0 +1,1103 @@ +get('a'); + $b = (float)$lab->get('b'); + + $hue = atan2($b, $a) * 180 / pi(); + + return [ + (float)$lab->get('L'), + sqrt($a ** 2 + $b ** 2), + $hue >= 0 ? $hue : $hue + 360, + ]; + } + + /** + * general LCH to Lab convert + * + * @param LCH $lch + * @return array{0:float,1:float,2:float} Lab values as array + */ + private static function __lchToLab(LCH $lch): array + { + return [ + (float)$lch->get('L'), + (float)$lch->get('C') * cos((float)$lch->get('H') * pi() / 180), // a + (float)$lch->get('C') * sin((float)$lch->get('H') * pi() / 180), // b + ]; + } + + // MARK: RGB <-> HSL + + /** + * converts a RGB (0-255) to HSL + * return: + * class with hue (0-360), saturation (0-100%) and luminance (0-100%) + * + * @param RGB $rgb Class for rgb + * @return HSL Class hue/sat/luminance + */ + public static function rgbToHsl(RGB $rgb): HSL + { + $red = (float)$rgb->get('R') / 255; + $green = (float)$rgb->get('G') / 255; + $blue = (float)$rgb->get('B') / 255; + + $min = min($red, $green, $blue); + $max = max($red, $green, $blue); + $chroma = $max - $min; + $sat = 0; + $hue = 0; + // luminance + $lum = ($max + $min) / 2; + + // achromatic + if ($chroma == 0) { + // H, S, L + return new HSL([ + 0.0, + 0.0, + $lum * 100, + ]); + } else { + $sat = $chroma / (1 - abs(2 * $lum - 1)); + if ($max == $red) { + $hue = fmod((($green - $blue) / $chroma), 6); + if ($hue < 0) { + $hue = (6 - fmod(abs($hue), 6)); + } + } elseif ($max == $green) { + $hue = ($blue - $red) / $chroma + 2; + } elseif ($max == $blue) { + $hue = ($red - $green) / $chroma + 4; + } + $hue = $hue * 60; + // $sat = 1 - abs(2 * $lum - 1); + return new HSL([ + $hue, + $sat * 100, + $lum * 100, + ]); + } + } + + /** + * converts an HSL to RGB + * if HSL value is invalid, set this value to 0 + * + * @param HSL $hsl Class with hue: 0-360 (degrees), + * saturation: 0-100, + * luminance: 0-100 + * @return RGB Class for rgb + */ + public static function hslToRgb(HSL $hsl): RGB + { + $hue = (float)$hsl->get('H'); + $sat = (float)$hsl->get('S'); + $lum = (float)$hsl->get('L'); + // calc to internal convert value for hue + $hue = (1 / 360) * $hue; + // convert to internal 0-1 format + $sat /= 100; + $lum /= 100; + // if saturation is 0 + if ($sat == 0) { + $lum = round($lum * 255); + return new RGB([$lum, $lum, $lum]); + } else { + $m2 = $lum < 0.5 ? $lum * ($sat + 1) : ($lum + $sat) - ($lum * $sat); + $m1 = $lum * 2 - $m2; + $hueue = function ($base) use ($m1, $m2) { + // base = hue, hue > 360 (1) - 360 (1), else < 0 + 360 (1) + $base = $base < 0 ? $base + 1 : ($base > 1 ? $base - 1 : $base); + // 6: 60, 2: 180, 3: 240 + // 2/3 = 240 + // 1/3 = 120 (all from 360) + if ($base * 6 < 1) { + return $m1 + ($m2 - $m1) * $base * 6; + } + if ($base * 2 < 1) { + return $m2; + } + if ($base * 3 < 2) { + return $m1 + ($m2 - $m1) * ((2 / 3) - $base) * 6; + } + return $m1; + }; + + return new RGB([ + 255 * $hueue($hue + (1 / 3)), + 255 * $hueue($hue), + 255 * $hueue($hue - (1 / 3)), + ]); + } + } + + // MARK: RGB <-> HSB + + /** + * rgb2hsb does not clean convert back to rgb in a round trip + * converts RGB to HSB/V values + * returns: + * Class with hue (0-360), sat (0-100%), brightness/value (0-100%) + * + * @param RGB $rgb Class for rgb + * @return HSB Class Hue, Sat, Brightness/Value + */ + public static function rgbToHsb(RGB $rgb): HSB + { + $red = (float)$rgb->get('R') / 255; + $green = (float)$rgb->get('G') / 255; + $blue = (float)$rgb->get('B') / 255; + + $MAX = max($red, $green, $blue); + $MIN = min($red, $green, $blue); + $HUE = 0; + $DELTA = $MAX - $MIN; + + // achromatic + if ($MAX == $MIN) { + return new HSB([0, 0, $MAX * 100]); + } + if ($red == $MAX) { + $HUE = fmod(($green - $blue) / $DELTA, 6); + } elseif ($green == $MAX) { + $HUE = (($blue - $red) / $DELTA) + 2; + } elseif ($blue == $MAX) { + $HUE = (($red - $green) / $DELTA) + 4; + } + $HUE *= 60; + // avoid negative + if ($HUE < 0) { + $HUE += 360; + } + + return new HSB([ + $HUE, // Hue + ($DELTA / $MAX) * 100, // Saturation + $MAX * 100, // Brightness + ]); + } + + /** + * hsb2rgb does not clean convert back to hsb in a round trip + * converts HSB/V to RGB values RGB is full INT + * if HSB/V value is invalid, sets this value to 0 + * + * @param HSB $hsb hue 0-360 (int), + * saturation 0-100 (int), + * brightness/value 0-100 (int) + * @return RGB Class for RGB + */ + public static function hsbToRgb(HSB $hsb): RGB + { + $H = (float)$hsb->get('H'); + $S = (float)$hsb->get('S'); + $V = (float)$hsb->get('B'); + // convert to internal 0-1 format + $S /= 100; + $V /= 100; + + if ($S == 0) { + $V = $V * 255; + return new RGB([$V, $V, $V]); + } + + $Hi = floor($H / 60); + $f = ($H / 60) - $Hi; + $p = $V * (1 - $S); + $q = $V * (1 - ($S * $f)); + $t = $V * (1 - ($S * (1 - $f))); + + switch ($Hi) { + case 0: + $red = $V; + $green = $t; + $blue = $p; + break; + case 1: + $red = $q; + $green = $V; + $blue = $p; + break; + case 2: + $red = $p; + $green = $V; + $blue = $t; + break; + case 3: + $red = $p; + $green = $q; + $blue = $V; + break; + case 4: + $red = $t; + $green = $p; + $blue = $V; + break; + case 5: + $red = $V; + $green = $p; + $blue = $q; + break; + default: + $red = 0; + $green = 0; + $blue = 0; + } + + return new RGB([ + $red * 255, + $green * 255, + $blue * 255, + ]); + } + + // MARK: RGB <-> HWB + + /** + * Convert RGB to HWB + * via rgb -> hsl -> hsb -> hwb + * + * @param RGB $rgb + * @return HWB + */ + public static function rgbToHwb(RGB $rgb): HWB + { + return self::hsbToHwb( + self::hslToHsb( + self::rgbToHsl($rgb) + ) + ); + } + + /** + * Convert HWB to RGB + * via hwb -> hsb -> hsl -> rgb + * + * @param HWB $hwb + * @return RGB + */ + public static function hwbToRgb(HWB $hwb): RGB + { + return self::hslToRgb( + self::hsbToHsl( + self::hwbToHsb($hwb) + ) + ); + } + + // MARK: HSL <-> HSB + + /** + * Convert HSL to HSB + * + * @param HSL $hsl + * @return HSB + */ + public static function hslToHsb(HSL $hsl): HSB + { + $saturation = (float)$hsl->get('S') / 100; + $lightness = (float)$hsl->get('L') / 100; + // if lightness is 0, then we cannot return convert to hsb + $value = $lightness + $saturation * min($lightness, 1 - $lightness); + // print "Orig: " . print_r($hsl, true) . "\n"; + // print "SAT: " . $saturation . ", Lightness: " . $lightness . ", Value: " . $value . "\n"; + // var_dump($value); + + // check for black and white + $saturation = $value == 0 ? + 0 : + 200 * (1 - $lightness / $value); + $value *= 100; + return new HSB([ + (float)$hsl->get('H'), + $saturation, + $value, + ]); + } + + /** + * Convert HSB to HSL + * + * @param HSB $hsb + * @return HSL + */ + public static function hsbToHsl(HSB $hsb): HSL + { + // hsv/toHsl + $hue = (float)$hsb->get('H'); + $saturation = (float)$hsb->get('S') / 100; + $value = (float)$hsb->get('B') / 100; + + $lightness = $value * (1 - $saturation / 2); + // check for B/W + $saturation = in_array($lightness, [0, 1]) ? + 0 : + 100 * ($value - $lightness) / min($lightness, 1 - $lightness) + ; + + return new HSL([ + $hue, + $saturation, + $lightness * 100, + ]); + } + + // MARK: HSL <-> HWB + + /** + * Convert HSL to HWB + * via hsl -> hsb -> hwb + * + * @param HSL $hsl + * @return HWB + */ + public static function hslToHwb(HSL $hsl): HWB + { + return self::hsbToHwb( + self::hslToHsb( + $hsl + ) + ); + } + + /** + * Convert HWB to HSL + * via hwb -> hsb -> hsl + * + * @param HWB $hwb + * @return HSL + */ + public static function hwbToHsl(HWB $hwb): HSL + { + return self::hsbToHsl( + self::hwbToHsb($hwb) + ); + } + + // MARK: HSB <-> HWB + + /** + * convert HSB to HWB + * + * @param HSB $hsb + * @return HWB + */ + public static function hsbToHwb(HSB $hsb): HWB + { + // hsv\Hwb + return new HWB([ + (float)$hsb->get('H'), // hue, + (float)$hsb->get('B') * (100 - (float)$hsb->get('S')) / 100, // 2: brightness, 1: saturation + 100 - (float)$hsb->get('B'), + ]); + } + + /** + * convert HWB to HSB + * + * @param HWB $hwb + * @return HSB + */ + public static function hwbToHsb(HWB $hwb): HSB + { + $hue = (float)$hwb->get('H'); + $whiteness = (float)$hwb->get('W') / 100; + $blackness = (float)$hwb->get('B') / 100; + + $sum = $whiteness + $blackness; + // print "S: B/W: " . $sum . " /W: " . $whiteness . " /B: " . $blackness . "\n"; + // for black and white + if ($sum >= 1) { + $saturation = 0; + $value = $whiteness / $sum * 100; + } else { + $value = 1 - $blackness; + $saturation = $value === 0.0 ? 0 : (1 - $whiteness / $value) * 100; + $value *= 100; + } + + return new HSB([ + $hue, + $saturation, + $value, + ]); + } + + // MARK: LAB <-> LCH + + /** + * CIE Lab to LCH + * + * @param Lab $lab + * @return LCH + */ + public static function labToLch(Lab $lab): LCH + { + // cieLab to cieLch + return new LCH(self::__labToLch($lab), colorspace: 'CIELab'); + } + + /** + * Convert CIE LCH to Lab + * + * @param LCH $lch + * @return Lab + */ + public static function lchToLab(LCH $lch): Lab + { + return new Lab(self::__lchToLab($lch), colorspace: 'CIELab'); + } + + // MARK: OkLch <-> OkLab + + /** + * okLAab to okLCH + * + * @param Lab $lab + * @return LCH + */ + public static function okLabToOkLch(Lab $lab): LCH + { + // okLab\toOkLch + return new LCH(self::__labToLch($lab), colorspace: 'OkLab'); + } + + /** + * okLCH to okLab + * + * @param LCH $lch + * @return Lab + */ + public static function okLchToOkLab(LCH $lch): Lab + { + // oklch/toOkLab + // oklch to oklab + return new Lab(self::__lchToLab($lch), colorspace: 'OkLab'); + } + + // MARK: rgb <-> oklab + + /** + * Undocumented function + * + * @param RGB $rgb + * @return Lab + */ + public static function rgbToOkLab(RGB $rgb): Lab + { + return CieXyz::rgbViaXyzD65ToOkLab($rgb); + } + + /** + * Undocumented function + * + * @param Lab $lab + * @return RGB + */ + public static function okLabToRgb(Lab $lab): RGB + { + return CieXyz::okLabViaXyzD65ToRgb($lab); + } + + // MARK: rgb <-> oklch + + /** + * convert rgb to OkLch + * via rgb -> linear rgb -> xyz D65 -> OkLab -> OkLch + * + * @param RGB $rgb + * @return LCH + */ + public static function rgbToOkLch(RGB $rgb): LCH + { + return self::okLabToOkLch( + self::rgbToOkLab($rgb) + ); + } + + /** + * Convert OkLch to rgb + * via OkLab -> OkLch -> xyz D65 -> linear rgb -> rgb + * + * @param LCH $lch + * @return RGB + */ + public static function okLchToRgb(LCH $lch): RGB + { + return self::okLabToRgb( + self::okLchToOkLab($lch) + ); + } + + // MARK: HSL <-> OKLab + + /** + * Undocumented function + * + * @param HSL $hsl + * @return Lab + */ + public static function hslToOkLab(HSL $hsl): Lab + { + return self::rgbToOkLab( + self::hslToRgb($hsl) + ); + } + + /** + * Undocumented function + * + * @param Lab $lab + * @return HSL + */ + public static function okLabToHsl(Lab $lab): HSL + { + return self::rgbToHsl( + self::okLabToRgb($lab) + ); + } + + // MARK: HSL <-> OKLCH + + /** + * Undocumented function + * + * @param HSL $hsl + * @return LCH + */ + public static function hslToOkLch(HSL $hsl): LCH + { + return self::rgbToOkLch( + self::hslToRgb($hsl) + ); + } + + /** + * Undocumented function + * + * @param LCH $lch + * @return HSL + */ + public static function okLchToHsl(LCH $lch): HSL + { + return self::rgbToHsl( + self::okLchToRgb($lch) + ); + } + + // MARK: HSB <-> OKLab + + /** + * Undocumented function + * + * @param HSB $hsb + * @return Lab + */ + public static function hsbToOkLab(HSB $hsb): Lab + { + return self::rgbToOkLab( + self::hsbToRgb($hsb) + ); + } + + /** + * Undocumented function + * + * @param Lab $lab + * @return HSB + */ + public static function okLabToHsb(Lab $lab): HSB + { + return self::rgbToHsb( + self::okLabToRgb($lab) + ); + } + + // MARK: HSB <-> OKLCH + + /** + * Undocumented function + * + * @param HSB $hsb + * @return LCH + */ + public static function hsbToOkLch(HSB $hsb): LCH + { + return self::rgbToOkLch( + self::hsbToRgb($hsb) + ); + } + + /** + * Undocumented function + * + * @param LCH $lch + * @return HSB + */ + public static function okLchToHsb(LCH $lch): HSB + { + return self::rgbToHsb( + self::okLchToRgb($lch) + ); + } + + // MARK: HWB <-> OKLab + + /** + * Undocumented function + * + * @param HWB $hwb + * @return Lab + */ + public static function hwbToOkLab(HWB $hwb): Lab + { + return self::rgbToOkLab( + self::hwbToRgb($hwb) + ); + } + + /** + * Undocumented function + * + * @param Lab $lab + * @return HWB + */ + public static function okLabToHwb(Lab $lab): HWB + { + return self::rgbToHwb( + self::okLabToRgb($lab) + ); + } + + // MARK: HWB <-> OKLCH + + /** + * Undocumented function + * + * @param HWB $hwb + * @return LCH + */ + public static function hwbToOkLch(HWB $hwb): LCH + { + return self::rgbToOkLch( + self::hwbToRgb($hwb) + ); + } + + /** + * Undocumented function + * + * @param LCH $lch + * @return HWB + */ + public static function okLchToHwb(LCH $lch): HWB + { + return self::rgbToHwb( + self::okLchToRgb($lch) + ); + } + + // MARK: RGB <-> Lab (Cie) + + /** + * RGB to Lab + * via RGB -> linRgb -> xyz D65 -> xyz D50 -> Lab + * + * @param RGB $rgb + * @return Lab + */ + public static function rgbToLab(RGB $rgb): Lab + { + return CieXyz::rgbViaXyzD65ViaXyzD50ToLab($rgb); + } + + /** + * Lab to RGB + * via Lab -> xyz D50 -> xyz D65 -> lin RGB -> RGB + * + * @param Lab $lab + * @return RGB + */ + public static function labToRgb(Lab $lab): RGB + { + return CieXyz::labViaXyzD50ViaXyzD65ToRgb($lab); + } + + // MARK: RGB <-> Lch (Cie) + + /** + * Convert RGB to LCH (Cie) + * via RGB to Lab + * + * @param RGB $rgb + * @return LCH + */ + public static function rgbToLch(RGB $rgb): LCH + { + // return self::rgbToL + return self::labToLch( + self::rgbToLab($rgb) + ); + } + + /** + * Convert LCH (Cie) to RGB + * via Lab to RGB + * + * @param LCH $lch + * @return RGB + */ + public static function lchToRgb(LCH $lch): RGB + { + return self::labToRgb( + self::lchToLab($lch) + ); + } + + // MARK: HSL <-> Lab (CIE) + + /** + * HSL to Lab (CIE) + * + * @param HSL $hsl + * @return Lab + */ + public static function hslToLab(HSL $hsl): Lab + { + return self::rgbToLab( + self::hslToRgb($hsl) + ); + } + + /** + * Lab (CIE) to HSL + * + * @param Lab $lab + * @return HSL + */ + public static function labToHsl(Lab $lab): HSL + { + return self::rgbToHsl( + self::labToRgb($lab) + ); + } + + // MARK: HSL <-> Lch (CIE) + + /** + * Undocumented function + * + * @param HSL $hsl + * @return LCH + */ + public static function hslToLch(HSL $hsl): LCH + { + return self::rgbToLch( + self::hslToRgb($hsl) + ); + } + + /** + * Undocumented function + * + * @param LCH $lch + * @return HSL + */ + public static function lchToHsl(LCH $lch): HSL + { + return self::rgbToHsl( + self::lchToRgb($lch) + ); + } + + // MARK: HSB <-> Lab (CIE) + + /** + * Undocumented function + * + * @param HSB $hsb + * @return Lab + */ + public static function hsbToLab(HSB $hsb): Lab + { + return self::rgbToLab( + self::hsbToRgb($hsb) + ); + } + + /** + * Undocumented function + * + * @param Lab $lab + * @return HSB + */ + public static function labToHsb(Lab $lab): HSB + { + return self::rgbToHsb( + self::labToRgb($lab) + ); + } + + // MARK: HSB <-> Lch (CIE) + + /** + * Undocumented function + * + * @param HSB $hsb + * @return LCH + */ + public static function hsbToLch(HSB $hsb): LCH + { + return self::rgbToLch( + self::hsbToRgb($hsb) + ); + } + + /** + * Undocumented function + * + * @param LCH $lch + * @return HSB + */ + public static function lchToHsb(LCH $lch): HSB + { + return self::rgbToHsb( + self::lchToRgb($lch) + ); + } + + // MARK: HWB <-> Lab (CIE) + + /** + * Undocumented function + * + * @param HWB $hwb + * @return Lab + */ + public static function hwbToLab(HWB $hwb): Lab + { + return self::rgbToLab( + self::hwbToRgb($hwb) + ); + } + + /** + * Undocumented function + * + * @param Lab $lab + * @return HWB + */ + public static function labToHwb(Lab $lab): HWB + { + return self::rgbToHwb( + self::labToRgb($lab) + ); + } + + // MARK: HWB <-> Lch (CIE) + + /** + * Undocumented function + * + * @param HWB $hwb + * @return Lch + */ + public static function hwbToLch(HWB $hwb): Lch + { + return self::rgbToLch( + self::hwbToRgb($hwb) + ); + } + + /** + * Undocumented function + * + * @param LCH $lch + * @return HWB + */ + public static function lchToHweb(LCH $lch): HWB + { + return self::rgbToHwb( + self::lchToRgb($lch) + ); + } + + // MARK: Lab (Cie) <-> OkLab + + /** + * okLab to Lab (Cie) + * + * @param Lab $lab + * @return Lab + */ + public static function okLabToLab(Lab $lab): Lab + { + return CieXyz::okLabViaXyzD65ViaXyzD50ToLab($lab); + /* return CieXyz::xyzD50ToLab( + CieXyz::xyzD65ToXyzD50( + CieXyz::okLabToXyzD65($lab) + ) + ); */ + } + + /** + * Lab (Cie) to okLab + * + * @param Lab $lab + * @return Lab + */ + public static function labToOkLab(Lab $lab): Lab + { + return CieXyz::labViaXyzD50ViaXyzD65ToOkLab($lab); + /* return CieXyz::xyzD65ToOkLab( + CieXyz::xyzD50ToXyxD65( + CieXyz::labToXyzD50($lab) + ) + ); */ + } + + // MARK: Lab (Cie) <-> Oklch + + /** + * OkLch to Lab (CIE) + * + * @param LCH $lch + * @return Lab + */ + public static function okLchToLab(LCH $lch): Lab + { + return self::okLabToLab( + self::okLchToOkLab($lch) + ); + } + + /** + * Lab (CIE) to OkLch + * + * @param Lab $lab + * @return LCH + */ + public static function labToOkLch(Lab $lab): LCH + { + return self::okLabToOkLch( + self::labToOkLab($lab) + ); + } + + // MARK: Lch (Cie) <-> OkLch + + /** + * okLch to Lch (Cie) + * via okLabToLab + * + * @param LCH $lch + * @return LCH + */ + public static function okLchToLch(LCH $lch): LCH + { + return self::labToLch( + self::okLabToLab( + self::okLchToOkLab($lch) + ) + ); + } + + /** + * Lch (Cie) to OkLch + * via labToOkLab + * + * @param LCH $lch + * @return LCH + */ + public static function lchToOkLch(LCH $lch): LCH + { + return self::labToOkLch( + self::lchToLab($lch) + ); + } + + // MARK: Lch (Cie) to OkLab + + /** + * OkLab to Lch (CIE) + * + * @param LAB $lab + * @return LCH + */ + public static function okLabToLch(LAB $lab): LCH + { + return self::labToLch( + self::okLabToLab($lab) + ); + } + + /** + * Lch (CIE) to OkLab + * + * @param LCH $lch + * @return LAB + */ + public static function lchToOkLab(LCH $lch): LAB + { + return self::labToOkLab( + self::lchToLab($lch) + ); + } +} diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php b/www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php new file mode 100644 index 00000000..09bd57e9 --- /dev/null +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php @@ -0,0 +1,190 @@ + allowed colorspaces */ + private const COLORSPACES = ['sRGB']; + + /** @var float hue */ + private float $H = 0.0; + /** @var float saturation */ + private float $S = 0.0; + /** @var float brightness / value */ + private float $B = 0.0; + + /** @var string color space: either ok or cie */ + private string $colorspace = ''; /** @phpstan-ignore-line */ + + /** + * HSB (HSV) color coordinates + * Hue/Saturation/Brightness or Value + * + * @param string|array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=sRGB] + * @param array $options [default=[]] + * @throws \InvalidArgumentException only array colors allowed + */ + public function __construct(string|array $colors, string $colorspace = 'sRGB', array $options = []) + { + if (!is_array($colors)) { + throw new \InvalidArgumentException('Only array colors allowed', 0); + } + $this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors); + } + + /** + * set from array + * where 0: Hue, 1: Saturation, 2: Brightness + * + * @param string|array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=sRGB] + * @param array $options [default=[]] + * @return self + */ + public static function create(string|array $colors, string $colorspace = 'sRGB', array $options = []): self + { + return new HSB($colors, $colorspace, $options); + } + + /** + * parse options + * + * @param array $options + * @return self + */ + private function parseOptions(array $options): self + { + return $this; + } + + /** + * set color + * + * @param string $name + * @param float $value + * @return void + */ + private function set(string $name, float $value): void + { + $name = strtoupper($name); + if (!property_exists($this, $name)) { + throw new \ErrorException('Creation of dynamic property is not allowed', 0); + } + switch ($name) { + case 'H': + if ($value == 360.0) { + $value = 0; + } + // if ($value < 0 || $value > 360) { + if (Utils::compare(0.0, $value, 360.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for hue is not in the range of 0 to 360', + 1 + ); + } + break; + case 'S': + // if ($value < 0 || $value > 100) { + if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for saturation is not in the range of 0 to 100', + 2 + ); + } + break; + case 'B': + // if ($value < 0 || $value > 100) { + if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for brightness is not in the range of 0 to 100', + 3 + ); + } + break; + } + $this->$name = $value; + } + + /** + * get color + * + * @param string $name + * @return float + */ + public function get(string $name): float|string|bool + { + $name = strtoupper($name); + if (!property_exists($this, $name)) { + throw new \ErrorException('Creation of dynamic property is not allowed', 0); + } + return $this->$name; + } + + /** + * set the colorspace + * + * @param string $colorspace + * @return self + */ + private function setColorspace(string $colorspace): self + { + if (!in_array($colorspace, $this::COLORSPACES)) { + throw new \InvalidArgumentException('Not allowed colorspace', 0); + } + $this->colorspace = $colorspace; + return $this; + } + + /** + * Returns the color as array + * where 0: Hue, 1: Saturation, 2: Brightness + * + * @return array{0:float,1:float,2:float} + */ + public function returnAsArray(): array + { + return [$this->H, $this->S, $this->B]; + } + + /** + * set color as array + * where 0: Hue, 1: Saturation, 2: Brightness + * + * @param array{0:float,1:float,2:float} $colors + * @return self + */ + private function setFromArray(array $colors): self + { + $this->set('H', $colors[0]); + $this->set('S', $colors[1]); + $this->set('B', $colors[2]); + return $this; + } + + /** + * no hsb in css + * + * @param float|string|null $opacity + * @return string + * @throws \ErrorException + */ + public function toCssString(null|float|string $opacity = null): string + { + throw new \ErrorException('HSB is not available as CSS color string', 0); + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php b/www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php new file mode 100644 index 00000000..5fbcf29f --- /dev/null +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php @@ -0,0 +1,195 @@ + allowed colorspaces */ + private const COLORSPACES = ['sRGB']; + + /** @var float hue */ + private float $H = 0.0; + /** @var float saturation */ + private float $S = 0.0; + /** @var float lightness (luminance) */ + private float $L = 0.0; + + /** @var string color space: either sRGB */ + private string $colorspace = ''; + + /** + * Color Coordinate HSL + * Hue/Saturation/Lightness + * + * @param string|array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=sRGB] + * @param array $options [default=[]] + * @throws \InvalidArgumentException only array colors allowed + */ + public function __construct(string|array $colors, string $colorspace = 'sRGB', array $options = []) + { + if (!is_array($colors)) { + throw new \InvalidArgumentException('Only array colors allowed', 0); + } + $this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors); + } + + /** + * set from array + * where 0: Hue, 1: Saturation, 2: Lightness + * + * @param string|array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=sRGB] + * @param array $options [default=[]] + * @return self + */ + public static function create(string|array $colors, string $colorspace = 'sRGB', array $options = []): self + { + return new HSL($colors, $colorspace, $options); + } + + /** + * parse options + * + * @param array $options + * @return self + */ + private function parseOptions(array $options): self + { + return $this; + } + + /** + * set color + * + * @param string $name + * @param float $value + * @return void + */ + private function set(string $name, float $value): void + { + if (!property_exists($this, $name)) { + throw new \ErrorException('Creation of dynamic property is not allowed', 0); + } + switch ($name) { + case 'H': + if ($value == 360.0) { + $value = 0; + } + // if ($value < 0 || $value > 360) { + if (Utils::compare(0.0, $value, 360.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for hue is not in the range of 0 to 360', + 1 + ); + } + break; + case 'S': + // if ($value < 0 || $value > 100) { + if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for saturation is not in the range of 0 to 100', + 2 + ); + } + break; + case 'L': + // if ($value < 0 || $value > 100) { + if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for lightness is not in the range of 0 to 100', + 3 + ); + } + break; + } + $this->$name = $value; + } + + /** + * get color + * + * @param string $name + * @return float + */ + public function get(string $name): float|string|bool + { + if (!property_exists($this, $name)) { + throw new \ErrorException('Creation of dynamic property is not allowed', 0); + } + return $this->$name; + } + + /** + * set the colorspace + * + * @param string $colorspace + * @return self + */ + private function setColorspace(string $colorspace): self + { + if (!in_array($colorspace, $this::COLORSPACES)) { + throw new \InvalidArgumentException('Not allowed colorspace', 0); + } + $this->colorspace = $colorspace; + return $this; + } + + /** + * Returns the color as array + * where 0: Hue, 1: Saturation, 2: Lightness + * + * @return array{0:float,1:float,2:float} + */ + public function returnAsArray(): array + { + return [$this->H, $this->S, $this->L]; + } + + /** + * set color as array + * where 0: Hue, 1: Saturation, 2: Lightness + * + * @param array{0:float,1:float,2:float} $colors + * @return self + */ + private function setFromArray(array $colors): self + { + $this->set('H', $colors[0]); + $this->set('S', $colors[1]); + $this->set('L', $colors[2]); + return $this; + } + + /** + * convert to css string with optional opacityt + * + * @param float|string|null $opacity + * @return string + */ + public function toCssString(null|float|string $opacity = null): string + { + $string = 'hsl(' + . $this->H + . ' ' + . $this->S + . ' ' + . $this->L + . Utils::setOpacity($opacity) + . ')'; + return $string; + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/HWB.php b/www/lib/CoreLibs/Convert/Color/Coordinates/HWB.php new file mode 100644 index 00000000..898cf082 --- /dev/null +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/HWB.php @@ -0,0 +1,195 @@ + allowed colorspaces */ + private const COLORSPACES = ['sRGB']; + + /** @var float Hue */ + private float $H = 0.0; + /** @var float Whiteness */ + private float $W = 0.0; + /** @var float Blackness */ + private float $B = 0.0; + + /** @var string color space: either ok or cie */ + private string $colorspace = ''; + + /** + * Color Coordinate: HWB + * Hue/Whiteness/Blackness + * + * @param string|array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=sRGB] + * @param array $options [default=[]] + * @throws \InvalidArgumentException only array colors allowed + */ + public function __construct(string|array $colors, string $colorspace = 'sRGB', array $options = []) + { + if (!is_array($colors)) { + throw new \InvalidArgumentException('Only array colors allowed', 0); + } + $this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors); + } + + /** + * set from array + * where 0: Hue, 1: Whiteness, 2: Blackness + * + * @param string|array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=sRGB] + * @param array $options [default=[]] + * @return self + */ + public static function create(string|array $colors, string $colorspace = 'sRGB', array $options = []): self + { + return new HWB($colors, $colorspace, $options); + } + + /** + * parse options + * + * @param array $options + * @return self + */ + private function parseOptions(array $options): self + { + return $this; + } + + /** + * set color + * + * @param string $name + * @param float $value + * @return void + */ + private function set(string $name, float $value): void + { + if (!property_exists($this, $name)) { + throw new \ErrorException('Creation of dynamic property is not allowed', 0); + } + switch ($name) { + case 'H': + if ($value == 360.0) { + $value = 0; + } + // if ($value < 0 || $value > 360) { + if (Utils::compare(0.0, $value, 360.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for hue is not in the range of 0 to 360', + 1 + ); + } + break; + case 'W': + // if ($value < 0 || $value > 100) { + if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for whiteness is not in the range of 0 to 100', + 2 + ); + } + break; + case 'B': + // if ($value < 0 || $value > 100) { + if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for blackness is not in the range of 0 to 100', + 3 + ); + } + break; + } + $this->$name = $value; + } + + /** + * get color + * + * @param string $name + * @return float + */ + public function get(string $name): float|string|bool + { + if (!property_exists($this, $name)) { + throw new \ErrorException('Creation of dynamic property is not allowed', 0); + } + return $this->$name; + } + + /** + * set the colorspace + * + * @param string $colorspace + * @return self + */ + private function setColorspace(string $colorspace): self + { + if (!in_array($colorspace, $this::COLORSPACES)) { + throw new \InvalidArgumentException('Not allowed colorspace', 0); + } + $this->colorspace = $colorspace; + return $this; + } + + /** + * Returns the color as array + * where 0: Hue, 1: Whiteness, 2: Blackness + * + * @return array{0:float,1:float,2:float} + */ + public function returnAsArray(): array + { + return [$this->H, $this->W, $this->B]; + } + + /** + * set color as array + * where 0: Hue, 1: Whiteness, 2: Blackness + * + * @param array{0:float,1:float,2:float} $colors + * @return self + */ + private function setFromArray(array $colors): self + { + $this->set('H', $colors[0]); + $this->set('W', $colors[1]); + $this->set('B', $colors[2]); + return $this; + } + + /** + * convert to css string with optional opacityt + * + * @param float|string|null $opacity + * @return string + */ + public function toCssString(null|float|string $opacity = null): string + { + $string = 'hwb(' + . $this->H + . ' ' + . $this->W + . ' ' + . $this->B + . Utils::setOpacity($opacity) + . ')'; + return $string; + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/Interface/CoordinatesInterface.php b/www/lib/CoreLibs/Convert/Color/Coordinates/Interface/CoordinatesInterface.php new file mode 100644 index 00000000..4606de42 --- /dev/null +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/Interface/CoordinatesInterface.php @@ -0,0 +1,53 @@ + $options [default=[]] + * @return self + */ + public static function create(string|array $colors, string $colorspace = '', array $options = []): self; + + /** + * get color + * + * @param string $name + * @return float + */ + public function get(string $name): float|string|bool; + + /** + * Returns the color as array + * where 0: Lightness, 1: a, 2: b + * + * @return array{0:float,1:float,2:float} + */ + public function returnAsArray(): array; + + /** + * Convert into css string with optional opacity + * + * @param null|float|string|null $opacity + * @return string + */ + public function toCssString(null|float|string $opacity = null): string; +} + +// __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/LCH.php b/www/lib/CoreLibs/Convert/Color/Coordinates/LCH.php new file mode 100644 index 00000000..ac57bdaa --- /dev/null +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/LCH.php @@ -0,0 +1,227 @@ + allowed colorspaces */ + private const COLORSPACES = ['OkLab', 'CIELab']; + + /** @var float Lightness/Luminance + * CIE: 0 to 100 + * OKlch: 0.0 to 1.0 + * BOTH: 0% to 100% + */ + private float $L = 0.0; + /** @var float Chroma + * CIE: 0 to 150, cannot be more than 230 + * OkLch: 0 to 0.4, does not exceed 0.5 + * BOTH: 0% to 100% (0 to 150, 0 to 0.4) + */ + private float $C = 0.0; + /** @var float Hue + * 0 to 360 deg + */ + private float $H = 0.0; + + /** @var string color space: either ok or cie */ + private string $colorspace = ''; + + /** + * Color Coordinate Lch + * for oklch + * + * @param string|array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=''] + * @param array $options [default=[]] + * @throws \InvalidArgumentException only array colors allowed + */ + public function __construct(string|array $colors, string $colorspace = '', array $options = []) + { + if (!is_array($colors)) { + throw new \InvalidArgumentException('Only array colors allowed', 0); + } + $this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors); + } + + /** + * set from array + * where 0: Lightness, 1: Chroma, 2: Hue + * + * @param string|array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=''] + * @param array $options [default=[]] + * @return self + */ + public static function create(string|array $colors, string $colorspace = '', array $options = []): self + { + return new LCH($colors, $colorspace, $options); + } + + /** + * parse options + * + * @param array $options + * @return self + */ + private function parseOptions(array $options): self + { + return $this; + } + + /** + * set color + * + * @param string $name + * @param float $value + * @return void + */ + private function set(string $name, float $value): void + { + if (!property_exists($this, $name)) { + throw new \ErrorException('Creation of dynamic property is not allowed', 0); + } + switch ($name) { + case 'L': + // if ($this->colorspace == 'CIELab' && ($value < 0 || $value > 100)) { + if ($this->colorspace == 'CIELab' && Utils::compare(0.0, $value, 100.0, Utils::ESPILON_BIG)) { + throw new \LengthException( + 'Argument value ' . $value . ' for lightness is not in the range of 0 to 100 for CIE Lab', + 1 + ); + // } elseif ($this->colorspace == 'OkLab' && ($value < 0 || $value > 1)) { + } elseif ($this->colorspace == 'OkLab' && Utils::compare(0.0, $value, 1.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for lightness is not in the range of 0.0 to 1.0 for OkLab', + 1 + ); + } + break; + case 'C': + // if ($this->colorspace == 'CIELab' && ($value < 0 || $value > 230)) { + if ($this->colorspace == 'CIELab' && Utils::compare(0.0, $value, 230.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for chroma is not in the range of ' + . '0 to 150 and a maximum of 230 for CIE Lab', + 1 + ); + // } elseif ($this->colorspace == 'OkLab' && ($value < 0 || $value > 0.55)) { + } elseif ($this->colorspace == 'OkLab' && Utils::compare(0.0, $value, 0.55, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for lightness is not in the range of ' + . '0.0 to 0.4 and a maximum of 0.5 for OkLab', + 1 + ); + } + break; + case 'H': + // if ($value < 0 || $value > 360) { + if (Utils::compare(0.0, $value, 360.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for hue is not in the range of 0.0 to 360.0', + 1 + ); + } + break; + } + $this->$name = $value; + } + + /** + * get color + * + * @param string $name + * @return float + */ + public function get(string $name): float|string|bool + { + if (!property_exists($this, $name)) { + throw new \ErrorException('Creation of dynamic property is not allowed', 0); + } + return $this->$name; + } + + /** + * set the colorspace + * + * @param string $colorspace + * @return self + */ + private function setColorspace(string $colorspace): self + { + if (!in_array($colorspace, $this::COLORSPACES)) { + throw new \InvalidArgumentException('Not allowed colorspace', 0); + } + $this->colorspace = $colorspace; + return $this; + } + + /** + * Returns the color as array + * where 0: Lightness, 1: Chroma, 2: Hue + * + * @return array{0:float,1:float,2:float} + */ + public function returnAsArray(): array + { + return [$this->L, $this->C, $this->H]; + } + + /** + * set color as array + * where 0: Lightness, 1: Chroma, 2: Hue + * + * @param array{0:float,1:float,2:float} $colors + * @return self + */ + private function setFromArray(array $colors): self + { + $this->set('L', $colors[0]); + $this->set('C', $colors[1]); + $this->set('H', $colors[2]); + return $this; + } + + /** + * Convert into css string with optional opacity + * + * @param null|float|string|null $opacity + * @return string + */ + public function toCssString(null|float|string $opacity = null): string + { + $string = ''; + switch ($this->colorspace) { + case 'CIELab': + $string = 'lch'; + break; + case 'OkLab': + $string = 'oklch'; + break; + } + $string .= '(' + . $this->L + . ' ' + . $this->C + . ' ' + . $this->H + . Utils::setOpacity($opacity) + . ');'; + + return $string; + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/Lab.php b/www/lib/CoreLibs/Convert/Color/Coordinates/Lab.php new file mode 100644 index 00000000..3652187b --- /dev/null +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/Lab.php @@ -0,0 +1,233 @@ + allowed colorspaces */ + private const COLORSPACES = ['OkLab', 'CIELab']; + + /** @var float lightness/luminance + * CIE: 0f to 100f + * OKlab: 0.0 to 1.0 + * BOTH: 0% to 100% + */ + private float $L = 0.0; + /** @var float a axis distance + * CIE: -125 to 125, cannot be more than +/- 160 + * OKlab: -0.4 to 0.4, cannot exceed +/- 0.5 + * BOTH: -100% to 100% (+/-125 or 0.4) + */ + private float $a = 0.0; + /** @var float b axis distance + * CIE: -125 to 125, cannot be more than +/- 160 + * OKlab: -0.4 to 0.4, cannot exceed +/- 0.5 + * BOTH: -100% to 100% (+/-125 or 0.4) + */ + private float $b = 0.0; + + /** @var string color space: either ok or cie */ + private string $colorspace = ''; + + /** + * Color Coordinate: Lab + * for oklab or cie + * + * @param string|array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=''] + * @param array $options [default=[]] + * @throws \InvalidArgumentException only array colors allowed + */ + public function __construct(string|array $colors, string $colorspace = '', array $options = []) + { + if (!is_array($colors)) { + throw new \InvalidArgumentException('Only array colors allowed', 0); + } + $this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors); + } + + /** + * set from array + * where 0: Lightness, 1: a, 2: b + * + * @param array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=''] + * @param array $options [default=[]] + * @return self + */ + public static function create(string|array $colors, string $colorspace = '', array $options = []): self + { + return new Lab($colors, $colorspace, $options); + } + + /** + * parse options + * + * @param array $options + * @return self + */ + private function parseOptions(array $options): self + { + return $this; + } + + /** + * set color + * + * @param string $name + * @param float $value + * @return void + */ + private function set(string $name, float $value): void + { + if (!property_exists($this, $name)) { + throw new \ErrorException('Creation of dynamic property is not allowed', 0); + } + switch ($name) { + case 'L': + // if ($this->colorspace == 'CIELab' && ($value < 0 || $value > 100)) { + if ($this->colorspace == 'CIELab' && Utils::compare(0.0, $value, 100.0, Utils::ESPILON_BIG)) { + throw new \LengthException( + 'Argument value ' . $value . ' for lightness is not in the range of 0 to 100 for CIE Lab', + 1 + ); + // } elseif ($this->colorspace == 'OkLab' && ($value < 0 || $value > 1)) { + } elseif ($this->colorspace == 'OkLab' && Utils::compare(0.0, $value, 1.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for lightness is not in the range of 0.0 to 1.0 for OkLab', + 1 + ); + } + break; + case 'a': + // if ($this->colorspace == 'CIELab' && ($value < -125 || $value > 125)) { + if ($this->colorspace == 'CIELab' && Utils::compare(-125.0, $value, 125.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for a is not in the range of -125 to 125 for CIE Lab', + 2 + ); + // } elseif ($this->colorspace == 'OkLab' && ($value < -0.55 || $value > 0.55)) { + } elseif ($this->colorspace == 'OkLab' && Utils::compare(-0.55, $value, 0.55, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for a is not in the range of -0.5 to 0.5 for OkLab', + 2 + ); + } + break; + case 'b': + // if ($this->colorspace == 'CIELab' && ($value < -125 || $value > 125)) { + if ($this->colorspace == 'CIELab' && Utils::compare(-125.0, $value, 125.0, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for b is not in the range of -125 to 125 for CIE Lab', + 3 + ); + // } elseif ($this->colorspace == 'OkLab' && ($value < -0.55 || $value > 0.55)) { + } elseif ($this->colorspace == 'OkLab' && Utils::compare(-0.55, $value, 0.55, Utils::EPSILON_SMALL)) { + throw new \LengthException( + 'Argument value ' . $value . ' for b is not in the range of -0.5 to 0.5 for OkLab', + 3 + ); + } + break; + } + $this->$name = $value; + } + + /** + * get color + * + * @param string $name + * @return float + */ + public function get(string $name): float|string|bool + { + if (!property_exists($this, $name)) { + throw new \ErrorException('Creation of dynamic property is not allowed', 0); + } + return $this->$name; + } + + /** + * set the colorspace + * + * @param string $colorspace + * @return self + */ + private function setColorspace(string $colorspace): self + { + if (!in_array($colorspace, $this::COLORSPACES)) { + throw new \InvalidArgumentException('Not allowed colorspace', 0); + } + $this->colorspace = $colorspace; + return $this; + } + + /** + * Returns the color as array + * where 0: Lightness, 1: a, 2: b + * + * @return array{0:float,1:float,2:float} + */ + public function returnAsArray(): array + { + return [$this->L, $this->a, $this->b]; + } + + /** + * set color as array + * where 0: Lightness, 1: a, 2: b + * + * @param array{0:float,1:float,2:float} $colors + * @return self + */ + private function setFromArray(array $colors): self + { + $this->set('L', $colors[0]); + $this->set('a', $colors[1]); + $this->set('b', $colors[2]); + return $this; + } + + /** + * Convert into css string with optional opacity + * + * @param null|float|string|null $opacity + * @return string + */ + public function toCssString(null|float|string $opacity = null): string + { + $string = ''; + switch ($this->colorspace) { + case 'CIELab': + $string = 'lab'; + break; + case 'OkLab': + $string = 'oklab'; + break; + } + $string .= '(' + . $this->L + . ' ' + . $this->a + . ' ' + . $this->b + . Utils::setOpacity($opacity) + . ');'; + + return $string; + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/RGB.php b/www/lib/CoreLibs/Convert/Color/Coordinates/RGB.php new file mode 100644 index 00000000..51d6a2b2 --- /dev/null +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/RGB.php @@ -0,0 +1,329 @@ + allowed colorspaces */ + private const COLORSPACES = ['sRGB']; + + /** @var float red 0 to 255 or 0.0f to 1.0f for linear RGB */ + private float $R = 0.0; + /** @var float green 0 to 255 or 0.0f to 1.0f for linear RGB */ + private float $G = 0.0; + /** @var float blue 0 to 255 or 0.0f to 1.0f for linear RGB */ + private float $B = 0.0; + + /** @var string color space: either ok or cie */ + private string $colorspace = ''; + + /** @var bool set if this is linear */ + private bool $linear = false; + + /** + * Color Coordinate RGB + * @param array{0:float,1:float,2:float}|string $colors RGB color array or hex string + * @param string $colorspace [default=sRGB] + * @param array $options [default=[]] only "linear" allowed at the moment + */ + public function __construct(string|array $colors, string $colorspace = 'sRGB', array $options = []) + { + $this->setColorspace($colorspace)->parseOptions($options); + if (is_array($colors)) { + $this->setFromArray($colors); + } else { + $this->setFromHex($colors); + } + } + + /** + * set from array or string + * where 0: Red, 1: Green, 2: Blue + * OR #ffffff or ffffff + * + * @param array{0:float,1:float,2:float}|string $colors RGB color array or hex string + * @param string $colorspace [default=sRGB] + * @param array $options [default=[]] only "linear" allowed at the moment + * @return self + */ + public static function create(string|array $colors, string $colorspace = 'sRGB', array $options = []): self + { + return new RGB($colors, $colorspace, $options); + } + + /** + * parse options + * + * @param array $options + * @return self + */ + private function parseOptions(array $options): self + { + $this->flagLinear($options['linear'] ?? false); + return $this; + } + + + /** + * set color + * + * @param string $name + * @param float $value + * @return void + */ + private function set(string $name, float $value): void + { + // do not allow setting linear from outside + if ($name == 'linear') { + return; + } + if (!property_exists($this, $name)) { + throw new \ErrorException('Creation of dynamic property is not allowed', 0); + } + // if not linear + if (!$this->linear && ((int)$value < 0 || (int)$value > 255)) { + throw new \LengthException('Argument value ' . $value . ' for color ' . $name + . ' is not in the range of 0 to 255', 1); + } elseif ( + // $this->linear && ($value < 0.0 || $value > 1.0) + $this->linear && Utils::compare(0.0, $value, 1.0, 0.000001) + ) { + throw new \LengthException('Argument value ' . $value . ' for color ' . $name + . ' is not in the range of 0 to 1 for linear rgb', 2); + } + $this->$name = $value; + } + + /** + * get color + * + * @param string $name + * @return float|bool + */ + public function get(string $name): float|string|bool + { + if (!property_exists($this, $name)) { + throw new \ErrorException('Creation of dynamic property is not allowed', 0); + } + return $this->$name; + } + + /** + * set the colorspace + * + * @param string $colorspace + * @return self + */ + private function setColorspace(string $colorspace): self + { + if (!in_array($colorspace, $this::COLORSPACES)) { + throw new \InvalidArgumentException('Not allowed colorspace', 0); + } + $this->colorspace = $colorspace; + return $this; + } + + /** + * Returns the color as array + * where 0: Red, 1: Green, 2: Blue + * + * @return array{0:float,1:float,2:float} + */ + public function returnAsArray(): array + { + return [$this->R, $this->G, $this->B]; + } + + /** + * set color as array + * where 0: Red, 1: Green, 2: Blue + * + * @param array{0:float,1:float,2:float} $colors + * @return self + */ + private function setFromArray(array $colors): self + { + $this->set('R', $colors[0]); + $this->set('G', $colors[1]); + $this->set('B', $colors[2]); + return $this; + } + + /** + * Return current set RGB as hex string. with or without # prefix + * + * @param bool $hex_prefix + * @return string + */ + public function returnAsHex(bool $hex_prefix = true): string + { + // prefix + $hex_color = ''; + if ($hex_prefix === true) { + $hex_color = '#'; + } + // convert if in linear + if ($this->linear) { + $this->fromLinear(); + } + foreach ($this->returnAsArray() as $color) { + $hex_color .= str_pad(dechex((int)$color), 2, '0', STR_PAD_LEFT); + } + return $hex_color; + } + + /** + * set colors RGB from hex string + * + * @param string $hex_string + * @return self + */ + private function setFromHex(string $hex_string): self + { + $hex_string = preg_replace("/[^0-9A-Fa-f]/", '', $hex_string); // Gets a proper hex string + if (empty($hex_string) || !is_string($hex_string)) { + throw new \InvalidArgumentException('hex_string argument cannot be empty', 3); + } + $rgbArray = []; + if (strlen($hex_string) == 6) { + // If a proper hex code, convert using bitwise operation. + // No overhead... faster + $colorVal = hexdec($hex_string); + $rgbArray = [ + 0xFF & ($colorVal >> 0x10), + 0xFF & ($colorVal >> 0x8), + 0xFF & $colorVal + ]; + } elseif (strlen($hex_string) == 3) { + // If shorthand notation, need some string manipulations + $rgbArray = [ + hexdec(str_repeat(substr($hex_string, 0, 1), 2)), + hexdec(str_repeat(substr($hex_string, 1, 1), 2)), + hexdec(str_repeat(substr($hex_string, 2, 1), 2)) + ]; + } else { + // Invalid hex color code + throw new \UnexpectedValueException('Invalid hex_string: ' . $hex_string, 4); + } + return $this->setFromArray($rgbArray); + } + + /** + * set as linear + * can be used as chain call on create if input is linear RGB + * RGB::__construct**(...)->flagLinear(); + * as it returns self + * + * @return self + */ + private function flagLinear(bool $linear): self + { + $this->linear = $linear; + return $this; + } + + /** + * Both function source: + * https://bottosson.github.io/posts/colorwrong/#what-can-we-do%3F + * but reverse f: fromLinear and f_inv for toLinear + * Code copied from here: + * https://stackoverflow.com/a/12894053 + * + * converts RGB to linear + * We come from 0-255 so we need to divide by 255 + * + * @return self + */ + public function toLinear(): self + { + // if linear, as is + if ($this->linear) { + return $this; + } + $this->flagLinear(true)->setFromArray(array_map( + callback: function (int|float $v) { + $v = (float)($v / 255); + $abs = abs($v); + $sign = ($v < 0) ? -1 : 1; + return (float)( + $abs <= 0.04045 ? + $v / 12.92 : + $sign * pow(($abs + 0.055) / 1.055, 2.4) + ); + }, + array: $this->returnAsArray(), + )); + return $this; + } + + /** + * convert back to normal sRGB from linear RGB + * we go to 0-255 rgb so we multiply by 255 + * + * @return self + */ + public function fromLinear(): self + { + // if not linear, as is + if (!$this->linear) { + return $this; + } + $this->flagLinear(false)->setFromArray(array_map( + callback: function (int|float $v) { + $abs = abs($v); + $sign = ($v < 0) ? -1 : 1; + // during reverse in some situations the values can become negative in very small ways + // like -...E16 and ...E17 + return ($ret = (float)(255 * ( + $abs <= 0.0031308 ? + $v * 12.92 : + $sign * (1.055 * pow($abs, 1.0 / 2.4) - 0.055) + ))) < 0 ? 0 : $ret; + }, + array: $this->returnAsArray(), + )); + return $this; + } + + /** + * convert to css string with optional opacity + * Note: if this is a linear RGB, the data will converted during this operation and the converted back + * + * @param float|string|null $opacity + * @return string + */ + public function toCssString(null|float|string $opacity = null): string + { + // if we are in linear mode, convert to normal mode temporary + $was_linear = false; + if ($this->linear) { + $this->fromLinear(); + $was_linear = true; + } + $string = 'rgb(' + . (int)round($this->R, 0) + . ' ' + . (int)round($this->G, 0) + . ' ' + . (int)round($this->B, 0) + . Utils::setOpacity($opacity) + . ')'; + if ($was_linear) { + $this->toLinear(); + } + return $string; + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/XYZ.php b/www/lib/CoreLibs/Convert/Color/Coordinates/XYZ.php new file mode 100644 index 00000000..3b0b9f9b --- /dev/null +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/XYZ.php @@ -0,0 +1,202 @@ + allowed colorspaces */ + private const COLORSPACES = ['CIEXYZ']; + /** @var array allowed whitepoints + * D50: ICC profile PCS (horizon light) <-> CieLab + * D65: RGB color space (noon) <-> linear RGB + */ + private const ILLUMINANT = ['D50', 'D65']; + + /** @var float X coordinate */ + private float $X = 0.0; + /** @var float Y coordinate (Luminance) */ + private float $Y = 0.0; + /** @var float Z coordinate (blue) */ + private float $Z = 0.0; + + /** @var string color space: either ok or cie */ + private string $colorspace = ''; + + /** @var string illuminat white point: only D50 and D65 are allowed */ + private string $whitepoint = ''; + + /** + * Color Coordinate Lch + * for oklch conversion + * + * @param string|array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=CIEXYZ] + * @param array $options [default=[]] Only "whitepoint" option allowed + * @throws \InvalidArgumentException only array colors allowed + */ + public function __construct( + string|array $colors, + string $colorspace = 'CIEXYZ', + array $options = [], + ) { + if (!is_array($colors)) { + throw new \InvalidArgumentException('Only array colors allowed', 0); + } + $this->setColorspace($colorspace) + ->parseOptions($options) + ->setFromArray($colors); + } + + /** + * set from array + * where 0: X, 1: Y, 2: Z + * + * @param array{0:float,1:float,2:float} $colors + * @param string $colorspace [default=CIEXYZ] + * @param array $options [default=[]] Only "whitepoint" option allowed + * @return self + */ + public static function create( + string|array $colors, + string $colorspace = 'CIEXYZ', + array $options = [], + ): self { + return new XYZ($colors, $colorspace, $options); + } + + /** + * parse options + * + * @param array $options + * @return self + */ + private function parseOptions(array $options): self + { + $this->setWhitepoint($options['whitepoint'] ?? ''); + return $this; + } + + /** + * set color + * + * @param string $name + * @param float $value + * @return void + */ + private function set(string $name, float $value): void + { + if (!property_exists($this, $name)) { + throw new \ErrorException('Creation of dynamic property is not allowed', 0); + } + // TODO: setup XYZ value limits + // X: 0 to 95.047, Y: 0 to 100, Z: 0 to 108.88 + // if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL))) { + // throw new \LengthException('Argument value ' . $value . ' for color ' . $name + // . ' is not in the range of 0 to 100.0', 1); + // } + $this->$name = $value; + } + + /** + * get color + * + * @param string $name + * @return float + */ + public function get(string $name): float|string|bool + { + if (!property_exists($this, $name)) { + throw new \ErrorException('Creation of dynamic property is not allowed', 0); + } + return $this->$name; + } + + /** + * set the colorspace + * + * @param string $colorspace + * @return self + */ + private function setColorspace(string $colorspace): self + { + if (!in_array($colorspace, $this::COLORSPACES)) { + throw new \InvalidArgumentException('Not allowed colorspace', 0); + } + $this->colorspace = $colorspace; + return $this; + } + + /** + * set the whitepoint flag + * + * @param string $whitepoint + * @return self + */ + private function setWhitepoint(string $whitepoint): self + { + if (empty($whitepoint)) { + $this->whitepoint = ''; + return $this; + } + if (!in_array($whitepoint, $this::ILLUMINANT)) { + throw new \InvalidArgumentException('Not allowed whitepoint', 0); + } + $this->whitepoint = $whitepoint; + return $this; + } + + /** + * Returns the color as array + * where 0: X, 1: Y, 2: Z + * + * @return array{0:float,1:float,2:float} + */ + public function returnAsArray(): array + { + return [$this->X, $this->Y, $this->Z]; + } + + /** + * set color as array + * where 0: X, 1: Y, 2: Z + * + * @param array{0:float,1:float,2:float} $colors + * @return self + */ + private function setFromArray(array $colors): self + { + $this->set('X', $colors[0]); + $this->set('Y', $colors[1]); + $this->set('Z', $colors[2]); + return $this; + } + + /** + * no hsb in css + * + * @param float|string|null $opacity + * @return string + * @throws \ErrorException + */ + public function toCssString(null|float|string $opacity = null): string + { + throw new \ErrorException('XYZ is not available as CSS color string', 0); + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Stringify.php b/www/lib/CoreLibs/Convert/Color/Stringify.php new file mode 100644 index 00000000..b2de79b9 --- /dev/null +++ b/www/lib/CoreLibs/Convert/Color/Stringify.php @@ -0,0 +1,35 @@ +toCssString($opacity); + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Convert/Color/Utils.php b/www/lib/CoreLibs/Convert/Color/Utils.php new file mode 100644 index 00000000..d4f10c37 --- /dev/null +++ b/www/lib/CoreLibs/Convert/Color/Utils.php @@ -0,0 +1,56 @@ +', $upper, $epslion) + ) { + return true; + } + return false; + } + + /** + * Build the opactiy sub string part and return it + * + * @param null|float|string|null $opacity + * @return string + */ + public static function setOpacity(null|float|string $opacity = null): string + { + // set opacity, either a string or float + if (is_string($opacity)) { + $opacity = ' / ' . $opacity; + } elseif ($opacity !== null) { + $opacity = ' / ' . $opacity; + } else { + $opacity = ''; + } + return $opacity; + } +} + +// __END__ diff --git a/www/lib/CoreLibs/Convert/Colors.php b/www/lib/CoreLibs/Convert/Colors.php index f9f56171..b24747be 100644 --- a/www/lib/CoreLibs/Convert/Colors.php +++ b/www/lib/CoreLibs/Convert/Colors.php @@ -17,6 +17,9 @@ declare(strict_types=1); namespace CoreLibs\Convert; +use CoreLibs\Convert\Color\Color; +use CoreLibs\Convert\Color\Coordinates; + class Colors { /** @@ -30,6 +33,7 @@ class Colors * @param bool $hex_prefix default true, prefix with "#" * @return string rgb in hex values with leading # if set, * @throws \LengthException If any argument is not in the range of 0~255 + * @deprecated v9.20.0 use: new Coordinates\RGB([$red, $green, $blue]))->returnAsHex(true/false for #) */ public static function rgb2hex( int $red, @@ -37,20 +41,7 @@ class Colors int $blue, bool $hex_prefix = true ): string { - $hex_color = ''; - if ($hex_prefix === true) { - $hex_color = '#'; - } - foreach (['red', 'green', 'blue'] as $color) { - // if not valid, abort - if ($$color < 0 || $$color > 255) { - throw new \LengthException('Argument value ' . $$color . ' for color ' . $color - . ' is not in the range of 0 to 255', 1); - } - // pad left with 0 - $hex_color .= str_pad(dechex($$color), 2, '0', STR_PAD_LEFT); - } - return $hex_color; + return (new Coordinates\RGB([$red, $green, $blue]))->returnAsHex($hex_prefix); } /** @@ -63,32 +54,29 @@ class Colors * or a string with the seperator * @throws \InvalidArgumentException if hex string is empty * @throws \UnexpectedValueException if the hex string value is not valid + * @deprecated v9.20.0 use: new Coordinates\RGB($hex_string) (build string/array from return data) */ public static function hex2rgb( string $hex_string, bool $return_as_string = false, string $seperator = ',' ): string|array { - $hex_string = preg_replace("/[^0-9A-Fa-f]/", '', $hex_string); // Gets a proper hex string - if (!is_string($hex_string)) { - throw new \InvalidArgumentException('hex_string argument cannot be empty', 1); - } $rgbArray = []; - if (strlen($hex_string) == 6) { - // If a proper hex code, convert using bitwise operation. - // No overhead... faster - $colorVal = hexdec($hex_string); - $rgbArray['r'] = 0xFF & ($colorVal >> 0x10); - $rgbArray['g'] = 0xFF & ($colorVal >> 0x8); - $rgbArray['b'] = 0xFF & $colorVal; - } elseif (strlen($hex_string) == 3) { - // If shorthand notation, need some string manipulations - $rgbArray['r'] = hexdec(str_repeat(substr($hex_string, 0, 1), 2)); - $rgbArray['g'] = hexdec(str_repeat(substr($hex_string, 1, 1), 2)); - $rgbArray['b'] = hexdec(str_repeat(substr($hex_string, 2, 1), 2)); - } else { - // Invalid hex color code - throw new \UnexpectedValueException('Invalid hex_string: ' . $hex_string, 2); + // rewrite to previous r/g/b key output + foreach ((new Coordinates\RGB($hex_string))->returnAsArray() as $p => $el) { + $k = ''; + switch ($p) { + case 0: + $k = 'r'; + break; + case 1: + $k = 'g'; + break; + case 2: + $k = 'b'; + break; + } + $rgbArray[$k] = (int)round($el); } // returns the rgb string or the associative array return $return_as_string ? implode($seperator, $rgbArray) : $rgbArray; @@ -105,42 +93,16 @@ class Colors * @param int $blue blue 0-255 * @return array Hue, Sat, Brightness/Value * @throws \LengthException If any argument is not in the range of 0~255 + * @deprecated v9.20.0 use: Color::rgbToHsb(...)->returnAsArray() will return float unrounded */ public static function rgb2hsb(int $red, int $green, int $blue): array { - // check that rgb is from 0 to 255 - foreach (['red', 'green', 'blue'] as $color) { - if ($$color < 0 || $$color > 255) { - throw new \LengthException('Argument value ' . $$color . ' for color ' . $color - . ' is not in the range of 0 to 255', 1); - } - $$color = $$color / 255; - } - - $MAX = max($red, $green, $blue); - $MIN = min($red, $green, $blue); - $HUE = 0; - - if ($MAX == $MIN) { - return [0, 0, round($MAX * 100)]; - } - if ($red == $MAX) { - $HUE = ($green - $blue) / ($MAX - $MIN); - } elseif ($green == $MAX) { - $HUE = 2 + (($blue - $red) / ($MAX - $MIN)); - } elseif ($blue == $MAX) { - $HUE = 4 + (($red - $green) / ($MAX - $MIN)); - } - $HUE *= 60; - if ($HUE < 0) { - $HUE += 360; - } - - return [ - (int)round($HUE), - (int)round((($MAX - $MIN) / $MAX) * 100), - (int)round($MAX * 100) - ]; + return array_map( + fn ($v) => (int)round($v), + Color::rgbToHsb( + new Coordinates\RGB([$red, $green, $blue]) + )->returnAsArray() + ); } /** @@ -153,80 +115,16 @@ class Colors * @param float $V brightness/value 0-100 (int) * @return array 0 red/1 green/2 blue array as 0-255 * @throws \LengthException If any argument is not in the valid range + * @deprecated v9.20.0 use: Color::hsbToRgb(...)->returnAsArray() will return float unrounded */ public static function hsb2rgb(float $H, float $S, float $V): array { - // check that H is 0 to 359, 360 = 0 - // and S and V are 0 to 1 - if ($H == 360) { - $H = 0; - } - if ($H < 0 || $H > 359) { - throw new \LengthException('Argument value ' . $H . ' for hue is not in the range of 0 to 359', 1); - } - if ($S < 0 || $S > 100) { - throw new \LengthException('Argument value ' . $S . ' for saturation is not in the range of 0 to 100', 2); - } - if ($V < 0 || $V > 100) { - throw new \LengthException('Argument value ' . $V . ' for brightness is not in the range of 0 to 100', 3); - } - // convert to internal 0-1 format - $S /= 100; - $V /= 100; - - if ($S == 0) { - $V = (int)round($V * 255); - return [$V, $V, $V]; - } - - $Hi = floor($H / 60); - $f = ($H / 60) - $Hi; - $p = $V * (1 - $S); - $q = $V * (1 - ($S * $f)); - $t = $V * (1 - ($S * (1 - $f))); - - switch ($Hi) { - case 0: - $red = $V; - $green = $t; - $blue = $p; - break; - case 1: - $red = $q; - $green = $V; - $blue = $p; - break; - case 2: - $red = $p; - $green = $V; - $blue = $t; - break; - case 3: - $red = $p; - $green = $q; - $blue = $V; - break; - case 4: - $red = $t; - $green = $p; - $blue = $V; - break; - case 5: - $red = $V; - $green = $p; - $blue = $q; - break; - default: - $red = 0; - $green = 0; - $blue = 0; - } - - return [ - (int)round($red * 255), - (int)round($green * 255), - (int)round($blue * 255) - ]; + return array_map( + fn ($v) => (int)round($v), + Color::hsbToRgb( + new Coordinates\HSB([$H, $S, $V]) + )->returnAsArray() + ); } /** @@ -239,50 +137,16 @@ class Colors * @param int $blue blue 0-255 * @return array hue/sat/luminance * @throws \LengthException If any argument is not in the range of 0~255 + * @deprecated v9.20.0 use: Color::rgbToHsl(...)->returnAsArray() will return float unrounded */ public static function rgb2hsl(int $red, int $green, int $blue): array { - // check that rgb is from 0 to 255 - foreach (['red', 'green', 'blue'] as $color) { - if ($$color < 0 || $$color > 255) { - throw new \LengthException('Argument value ' . $$color . ' for color ' . $color - . ' is not in the range of 0 to 255', 1); - } - $$color = $$color / 255; - } - - $min = min($red, $green, $blue); - $max = max($red, $green, $blue); - $chroma = $max - $min; - $sat = 0; - $hue = 0; - // luminance - $lum = ($max + $min) / 2; - - // achromatic - if ($chroma == 0) { - // H, S, L - return [0.0, 0.0, round($lum * 100, 1)]; - } else { - $sat = $chroma / (1 - abs(2 * $lum - 1)); - if ($max == $red) { - $hue = fmod((($green - $blue) / $chroma), 6); - if ($hue < 0) { - $hue = (6 - fmod(abs($hue), 6)); - } - } elseif ($max == $green) { - $hue = ($blue - $red) / $chroma + 2; - } elseif ($max == $blue) { - $hue = ($red - $green) / $chroma + 4; - } - $hue = $hue * 60; - // $sat = 1 - abs(2 * $lum - 1); - return [ - round($hue, 1), - round($sat * 100, 1), - round($lum * 100, 1) - ]; - } + return array_map( + fn ($v) => round($v, 1), + Color::rgbToHsl( + new Coordinates\RGB([$red, $green, $blue]) + )->returnAsArray() + ); } /** @@ -294,57 +158,16 @@ class Colors * @param float $lum luminance: 0-100 * @return array red/blue/green 0-255 each * @throws \LengthException If any argument is not in the valid range + * @deprecated v9.20.0 use: Color::hslToRgb(...)->returnAsArray() will return float unrounded */ public static function hsl2rgb(float $hue, float $sat, float $lum): array { - if ($hue == 360) { - $hue = 0; - } - if ($hue < 0 || $hue > 359) { - throw new \LengthException('Argument value ' . $hue . ' for hue is not in the range of 0 to 359', 1); - } - if ($sat < 0 || $sat > 100) { - throw new \LengthException('Argument value ' . $sat . ' for saturation is not in the range of 0 to 100', 2); - } - if ($lum < 0 || $lum > 100) { - throw new \LengthException('Argument value ' . $lum . ' for luminance is not in the range of 0 to 100', 3); - } - // calc to internal convert value for hue - $hue = (1 / 360) * $hue; - // convert to internal 0-1 format - $sat /= 100; - $lum /= 100; - // if saturation is 0 - if ($sat == 0) { - $lum = (int)round($lum * 255); - return [$lum, $lum, $lum]; - } else { - $m2 = $lum < 0.5 ? $lum * ($sat + 1) : ($lum + $sat) - ($lum * $sat); - $m1 = $lum * 2 - $m2; - $hueue = function ($base) use ($m1, $m2) { - // base = hue, hue > 360 (1) - 360 (1), else < 0 + 360 (1) - $base = $base < 0 ? $base + 1 : ($base > 1 ? $base - 1 : $base); - // 6: 60, 2: 180, 3: 240 - // 2/3 = 240 - // 1/3 = 120 (all from 360) - if ($base * 6 < 1) { - return $m1 + ($m2 - $m1) * $base * 6; - } - if ($base * 2 < 1) { - return $m2; - } - if ($base * 3 < 2) { - return $m1 + ($m2 - $m1) * ((2 / 3) - $base) * 6; - } - return $m1; - }; - - return [ - (int)round(255 * $hueue($hue + (1 / 3))), - (int)round(255 * $hueue($hue)), - (int)round(255 * $hueue($hue - (1 / 3))) - ]; - } + return array_map( + fn ($v) => round($v), + Color::hslToRgb( + new Coordinates\HSL([$hue, $sat, $lum]) + )->returnAsArray() + ); } } diff --git a/www/lib/CoreLibs/Convert/Math.php b/www/lib/CoreLibs/Convert/Math.php index 205abbf1..daf4bf07 100644 --- a/www/lib/CoreLibs/Convert/Math.php +++ b/www/lib/CoreLibs/Convert/Math.php @@ -56,6 +56,180 @@ class Math return (float)$number; } } + + /** + * calc cube root + * + * @param float $number Number to cubic root + * @return float Calculated value + */ + public static function cbrt(float|int $number): float + { + return pow((float)$number, 1.0 / 3); + } + + /** + * use PHP_FLOAT_EPSILON to compare if two float numbers are matching + * + * @param float $x + * @param float $y + * @param float $epsilon [default=PHP_FLOAT_EPSILON] + * @return bool True equal + */ + public static function equalWithEpsilon(float $x, float $y, float $epsilon = PHP_FLOAT_EPSILON): bool + { + if (abs($x - $y) < $epsilon) { + return true; + } + return false; + } + + /** + * Compare two value base on direction given + * The default delta is PHP_FLOAT_EPSILON + * + * @param float $value + * @param string $compare + * @param float $limit + * @param float $epsilon [default=PHP_FLOAT_EPSILON] + * @return bool True on smaller/large or equal + */ + public static function compareWithEpsilon( + float $value, + string $compare, + float $limit, + float $epsilon = PHP_FLOAT_EPSILON + ): bool { + switch ($compare) { + case '<': + if ($value < ($limit - $epsilon)) { + return true; + } + break; + case '<=': + if ($value <= ($limit - $epsilon)) { + return true; + } + break; + case '==': + return self::equalWithEpsilon($value, $limit, $epsilon); + case '>': + if ($value > ($limit + $epsilon)) { + return true; + } + break; + case '>=': + if ($value >= ($limit + $epsilon)) { + return true; + } + break; + } + return false; + } + + /** + * This function is directly inspired by the multiplyMatrices() function in color.js + * form Lea Verou and Chris Lilley. + * (see https://github.com/LeaVerou/color.js/blob/main/src/multiply-matrices.js) + * From: + * https://github.com/matthieumastadenis/couleur/blob/3842cf51c9517e77afaa0a36ec78643a0c258e0b/src/utils/utils.php#L507 + * + * It returns an array which is the product of the two number matrices passed as parameters. + * + * NOTE: + * if the right side (B matrix) has a missing row, this row will be fillwed with 0 instead of + * throwing an error: + * A: + * [ + * [1, 2, 3], + * [4, 5, 6], + * ] + * B: + * [ + * [7, 8, 9], + * [10, 11, 12], + * ] + * The B will get a third row with [0, 0, 0] added to make the multiplication work as it will be + * rewritten as + * B-rewrite: + * [ + * [7, 10, 0], + * [8, 11, 12], + * [0, 0, 0] <- automatically added + * ] + * + * The same is done for unbalanced entries, they are filled with 0 + * + * @param array> $a m x n matrice + * @param array> $b n x p matrice + * + * @return array> m x p product + */ + public static function multiplyMatrices(array $a, array $b): array + { + $m = count($a); + + if (!is_array($a[0] ?? null)) { + // $a is vector, convert to [[a, b, c, ...]] + $a = [$a]; + } + + if (!is_array($b[0])) { + // $b is vector, convert to [[a], [b], [c], ...]] + $b = array_map( + callback: fn ($v) => [ $v ], + array: $b, + ); + } + + $p = count($b[0]); + + // transpose $b: + // so that we can multiply row by row + $bCols = array_map( + callback: fn ($k) => array_map( + (fn ($i) => is_array($i) ? $i[$k] ?? 0 : 0), + $b, + ), + array: array_keys($b[0]), + ); + + $product = array_map( + callback: fn ($row) => array_map( + callback: fn ($col) => is_array($row) ? + array_reduce( + array: $row, + callback: fn ($a, $v, $i = null) => $a + $v * ( + // if last entry missing for full copy add a 0 to it + $col[$i ?? array_search($v, $row, true)] ?? 0 /** @phpstan-ignore-line */ + ), + initial: 0, + ) : + array_reduce( + array: $col, + callback: fn ($a, $v) => $a + $v * $row, + initial: 0, + ), + array: $bCols, + ), + array: $a, + ); + + if ($m === 1) { + // Avoid [[a, b, c, ...]]: + return $product[0]; + } + + if ($p === 1) { + // Avoid [[a], [b], [c], ...]]: + return array_map( + callback: fn ($v) => $v[0] ?? 0, + array: $product, + ); + } + + return $product; + } } // __END__ diff --git a/www/lib/CoreLibs/Create/Session.php b/www/lib/CoreLibs/Create/Session.php index ca3607e6..4440b47e 100644 --- a/www/lib/CoreLibs/Create/Session.php +++ b/www/lib/CoreLibs/Create/Session.php @@ -15,17 +15,111 @@ namespace CoreLibs\Create; class Session { + /** @var string current session name */ + private string $session_name = ''; + /** @var string current session id */ + 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{auto_write_close?:bool,session_strict?:bool,regenerate?:string,regenerate_interval?:int} $options */ - public function __construct(string $session_name = '') + public function __construct( + string $session_name, + array $options = [] + ) { + $this->setOptions($options); + $this->initSession($session_name); + } + + // MARK: private methods + + /** + * set session class options + * + * @param array{auto_write_close?:bool,session_strict?:bool,regenerate?:string,regenerate_interval?:int} $options + * @return void + */ + private function setOptions(array $options): void { - if (!empty($session_name)) { - $this->startSession($session_name); + 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); } /** @@ -36,38 +130,100 @@ class Session * * @return void */ - protected function startSessionCall(): void + private function startSessionCall(): void { session_start(); } /** - * check if we are in CLI, we set this, so we can mock this - * Not this is just a wrapper for the static System::checkCLI call + * get current set session id or false if none started * - * @return bool True if we are in a CLI enviroment, or false for everything else + * @return string|false */ - public function checkCliStatus(): bool + public function getSessionIdCall(): string|false { - return \CoreLibs\Get\System::checkCLI(); + return session_id(); } /** - * Set session name call. If not valid session name, will return false + * automatically closes a session if the auto write close flag is set * - * @param string $session_name A valid string for session name - * @return bool True if session name is valid, - * False if not + * @return bool */ - public function setSessionName(string $session_name): bool + private function closeSessionCall(): bool { - if (!$this->checkValidSessionName($session_name)) { - return false; + if ($this->auto_write_close) { + return $this->writeClose(); } - session_name($session_name); - return true; + 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 * @@ -94,15 +250,34 @@ class Session } /** - * start session with given session name if set + * validate _SESSION key, must be valid variable + * + * @param int|float|string $key + * @return true + */ + private function checkValidSessionEntryKey(int|float|string $key): true + { + if (!is_string($key) || is_numeric($key)) { + throw new \UnexpectedValueException( + '[SESSION] Given key for _SESSION is not a valid value for a varaible: ' . $key, + 1 + ); + } + return true; + } + + // MARK: init session (on class start) + + /** + * stinitart session with given session name if set * aborts on command line or if sessions are not enabled * also aborts if session cannot be started * On sucess returns the session id * - * @param string|null $session_name - * @return string|bool + * @param string $session_name + * @return void */ - public function startSession(?string $session_name = null): string|bool + private function initSession(string $session_name): void { // we can't start sessions on command line if ($this->checkCliStatus()) { @@ -115,39 +290,95 @@ class Session // session_status // initial the session if there is no session running already if (!$this->checkActiveSession()) { - // if session name is emtpy, check if there is a global set - // this is a deprecated fallback - $session_name = $session_name ?? $GLOBALS['SET_SESSION_NAME'] ?? ''; - // DEPRECTED: constant SET_SESSION_NAME is no longer used - // if set, set special session name - if (!empty($session_name)) { - // invalid session name, abort - if (!$this->checkValidSessionName($session_name)) { - throw new \UnexpectedValueException('[SESSION] Invalid session name: ' . $session_name, 3); - } - $this->setSessionName($session_name); + // invalid session name, abort + if (!$this->checkValidSessionName($session_name)) { + throw new \UnexpectedValueException('[SESSION] Invalid session name: ' . $this->session_name, 3); } + // set session name + $this->session_name = $session_name; + session_name($this->session_name); // start session $this->startSessionCall(); + // if we faild to start the 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(), + 4 + ); } - // if we still have no active session + // check session id + if (false === ($session_id = $this->getSessionIdCall())) { + 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(); + } + } + + // MARK: public set/get status + + /** + * start session, will only run after initSession + * + * @return bool True if started, False if alrady running + */ + public function restartSession(): bool + { if (!$this->checkActiveSession()) { - throw new \RuntimeException('[SESSION] Failed to activate session', 4); + if (empty($this->session_name)) { + throw new \RuntimeException('[SESSION] Cannot restart session without a session name', 1); + } + $this->startSessionCall(); + return true; } - if (false === ($session_id = $this->getSessionId())) { - throw new \UnexpectedValueException('[SESSION] getSessionId did not return a session id', 5); - } - return $session_id; + return false; } /** - * get current set session id or false if none started + * current set session id * - * @return string|bool + * @return string */ - public function getSessionId(): string|bool + public function getSessionId(): string { - return session_id(); + return $this->session_id; + } + + /** + * set the auto write close flag + * + * @param bool $flag + * @return Session + */ + public function setAutoWriteClose(bool $flag): Session + { + $this->auto_write_close = $flag; + return $this; + } + + /** + * return the auto write close flag + * + * @return bool + */ + public function checkAutoWriteClose(): bool + { + return $this->auto_write_close; } /** @@ -175,6 +406,34 @@ class Session } } + /** + * check if we are in CLI, we set this, so we can mock this + * Not this is just a wrapper for the static System::checkCLI call + * + * @return bool True if we are in a CLI enviroment, or false for everything else + */ + public function checkCliStatus(): bool + { + return \CoreLibs\Get\System::checkCLI(); + } + + /** + * get session status + * PHP_SESSION_DISABLED if sessions are disabled. + * PHP_SESSION_NONE if sessions are enabled, but none exists. + * PHP_SESSION_ACTIVE if sessions are enabled, and one exists. + * + * https://www.php.net/manual/en/function.session-status.php + * + * @return int See possible return int values above + */ + public function getSessionStatus(): int + { + return session_status(); + } + + // MARK: write close session + /** * unlock the session file, so concurrent AJAX requests can be done * NOTE: after this has been called, no changes in _SESSION will be stored @@ -188,17 +447,24 @@ class Session return session_write_close(); } + // MARK: session close and clean up + /** * Proper destroy a session * - unset the _SESSION array * - unset cookie if cookie on and we have not strict mode + * - unset session_name and session_id internal vars * - destroy session * - * @return bool + * @return bool True on successful session destroy */ public function sessionDestroy(): bool { - $_SESSION = []; + // abort to false if not unsetable + if (!session_unset()) { + return false; + } + $this->clear(); if ( ini_get('session.use_cookies') && !ini_get('session.use_strict_mode') @@ -218,68 +484,93 @@ class Session $params['httponly'] ); } + // unset internal vars + $this->session_name = ''; + $this->session_id = ''; return session_destroy(); } - /** - * get session status - * PHP_SESSION_DISABLED if sessions are disabled. - * PHP_SESSION_NONE if sessions are enabled, but none exists. - * PHP_SESSION_ACTIVE if sessions are enabled, and one exists. - * - * https://www.php.net/manual/en/function.session-status.php - * - * @return int See possible return int values above - */ - public function getSessionStatus(): int - { - return session_status(); - } - - // _SESSION set/unset methods + // MARK: _SESSION set/unset methods /** * unset all _SESSION entries * * @return void */ - public function unsetAllS(): void + public function clear(): void { - foreach (array_keys($_SESSION ?? []) as $name) { - unset($_SESSION[$name]); + $this->restartSession(); + if (!session_unset()) { + throw new \RuntimeException('[SESSION] Cannot unset session vars', 1); } + if (!empty($_SESSION)) { + $_SESSION = []; + } + $this->closeSessionCall(); } /** * set _SESSION entry 'name' with any value * - * @param string|int $name array name in _SESSION - * @param mixed $value value to set (can be anything) + * @param string $name array name in _SESSION + * @param mixed $value value to set (can be anything) + * @return Session + */ + public function set(string $name, mixed $value): Session + { + $this->checkValidSessionEntryKey($name); + $this->restartSession(); + $_SESSION[$name] = $value; + $this->closeSessionCall(); + return $this; + } + + /** + * set many session entries in one set + * + * @param array $set key is the key in the _SESSION, value is any data to set * @return void */ - public function setS(string|int $name, mixed $value): void + public function setMany(array $set): void { - $_SESSION[$name] = $value; + $this->restartSession(); + // skip any that are not valid + foreach ($set as $key => $value) { + $this->checkValidSessionEntryKey($key); + $_SESSION[$key] = $value; + } + $this->closeSessionCall(); } /** * get _SESSION 'name' entry or empty string if not set * - * @param string|int $name value key to get from _SESSION - * @return mixed value stored in _SESSION + * @param string $name value key to get from _SESSION + * @return mixed value stored in _SESSION, if not found set to null */ - public function getS(string|int $name): mixed + public function get(string $name): mixed { - return $_SESSION[$name] ?? ''; + return $_SESSION[$name] ?? null; + } + + /** + * get multiple session entries + * + * @param array $set + * @return array + */ + public function getMany(array $set): array + { + return array_intersect_key($_SESSION, array_flip($set)); } /** * Check if a name is set in the _SESSION array * - * @param string|int $name Name to check for - * @return bool True for set, False fornot set + * @param string $name Name to check for + * @return bool True for set, False fornot set */ - public function issetS(string|int $name): bool + public function isset(string $name): bool { return isset($_SESSION[$name]); } @@ -287,67 +578,36 @@ class Session /** * unset one _SESSION entry 'name' if exists * - * @param string|int $name _SESSION key name to remove + * @param string $name _SESSION key name to remove + * @return Session + */ + public function unset(string $name): Session + { + if (!isset($_SESSION[$name])) { + return $this; + } + $this->restartSession(); + unset($_SESSION[$name]); + $this->closeSessionCall(); + return $this; + } + + /** + * reset many session entry + * + * @param array $set list of session keys to reset * @return void */ - public function unsetS(string|int $name): void + public function unsetMany(array $set): void { - if (isset($_SESSION[$name])) { - unset($_SESSION[$name]); - } - } - - // set/get below - // ->var = value; - - /** - * Undocumented function - * - * @param string|int $name - * @param mixed $value - * @return void - */ - public function __set(string|int $name, mixed $value): void - { - $_SESSION[$name] = $value; - } - - /** - * Undocumented function - * - * @param string|int $name - * @return mixed If name is not found, it will return null - */ - public function __get(string|int $name): mixed - { - if (isset($_SESSION[$name])) { - return $_SESSION[$name]; - } - return null; - } - - /** - * Undocumented function - * - * @param string|int $name - * @return bool - */ - public function __isset(string|int $name): bool - { - return isset($_SESSION[$name]); - } - - /** - * Undocumented function - * - * @param string|int $name - * @return void - */ - public function __unset(string|int $name): void - { - if (isset($_SESSION[$name])) { - unset($_SESSION[$name]); + $this->restartSession(); + foreach ($set as $key) { + if (!isset($_SESSION[$key])) { + continue; + } + unset($_SESSION[$key]); } + $this->closeSessionCall(); } } diff --git a/www/lib/CoreLibs/Create/Uids.php b/www/lib/CoreLibs/Create/Uids.php index 47691338..09bdc86b 100644 --- a/www/lib/CoreLibs/Create/Uids.php +++ b/www/lib/CoreLibs/Create/Uids.php @@ -38,7 +38,7 @@ class Uids $uniqid_length++; } /** @var int<1,max> make sure that internal this is correct */ - $random_bytes_length = ($uniqid_length - ($uniqid_length % 2)) / 2; + $random_bytes_length = (int)(($uniqid_length - ($uniqid_length % 2)) / 2); $uniqid = bin2hex(random_bytes($random_bytes_length)); // if not forced shorten return next lower length if (!$force_length) { @@ -56,26 +56,6 @@ class Uids */ public static function uuidv4(): string { - /* return sprintf( - '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', - // 32 bits for "time_low" - mt_rand(0, 0xffff), - mt_rand(0, 0xffff), - // 16 bits for "time_mid" - mt_rand(0, 0xffff), - // 16 bits for "time_hi_and_version", - // four most significant bits holds version number 4 - mt_rand(0, 0x0fff) | 0x4000, - // 16 bits, 8 bits for "clk_seq_hi_res", - // 8 bits for "clk_seq_low", - // two most significant bits holds zero and one for variant DCE1.1 - mt_rand(0, 0x3fff) | 0x8000, - // 48 bits for "node" - mt_rand(0, 0xffff), - mt_rand(0, 0xffff), - mt_rand(0, 0xffff) - ); */ - $data = random_bytes(16); assert(strlen($data) == 16); @@ -93,6 +73,20 @@ class Uids return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); } + /** + * regex validate uuid v4 + * + * @param string $uuidv4 + * @return bool + */ + public static function validateUuuidv4(string $uuidv4): bool + { + if (!preg_match("/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/", $uuidv4)) { + return false; + } + return true; + } + /** * creates a uniq id based on lengths * diff --git a/www/lib/CoreLibs/DB/Extended/ArrayIO.php b/www/lib/CoreLibs/DB/Extended/ArrayIO.php index d40b71b4..ac98091c 100644 --- a/www/lib/CoreLibs/DB/Extended/ArrayIO.php +++ b/www/lib/CoreLibs/DB/Extended/ArrayIO.php @@ -374,7 +374,7 @@ class ArrayIO extends \CoreLibs\DB\IO public function dbDelete(array $table_array = [], bool $acl_limit = false): array { // is array and has values, override set and set new - if (is_array($table_array) && count($table_array)) { + if (count($table_array)) { $this->table_array = $table_array; } if (!$this->dbCheckPkSet()) { @@ -440,7 +440,7 @@ class ArrayIO extends \CoreLibs\DB\IO public function dbRead(bool $edit = false, array $table_array = []): array { // if array give, overrules internal array - if (is_array($table_array) && count($table_array)) { + if (count($table_array)) { $this->table_array = $table_array; } if (!$this->dbCheckPkSet()) { diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index a006b1a6..47206323 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -284,7 +284,8 @@ class IO public const ERROR_HASH_TYPE = 'adler32'; /** @var string regex to get returning with matches at position 1 */ public const REGEX_RETURNING = '/\s+returning\s+(.+\s*(?:.+\s*)+);?$/i'; - /** @var array allowed convert target for placeholder: pg or pdo (currently not available) */ + /** @var array allowed convert target for placeholder: + * pg or pdo (currently not available) */ public const DB_CONVERT_PLACEHOLDER_TARGET = ['pg']; // REGEX_SELECT // REGEX_UPDATE @@ -499,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()) { @@ -823,6 +824,10 @@ class IO ); break; default: + // no context on DB_INFO + if ($id == 'DB_INFO') { + $context = []; + } // used named arguments so we can easy change the order of debug $this->log->debug( group_id: $debug_id, @@ -910,7 +915,7 @@ class IO if ($cursor !== false) { [$db_prefix, $db_error_string] = $this->db_functions->__dbPrintError($cursor); } - if ($cursor === false && method_exists($this->db_functions, '__dbPrintError')) { + if ($cursor === false && method_exists($this->db_functions, '__dbPrintError')) { /** @phpstan-ignore-line */ [$db_prefix, $db_error_string] = $this->db_functions->__dbPrintError(); } // prefix the master if not the same @@ -1307,33 +1312,14 @@ class IO } /** - * count $ leading parameters only + * count placeholder entries in the query * * @param string $query Query to check * @return int Number of parameters found */ private function __dbCountQueryParams(string $query): int { - $match = []; - // regex for params: only stand alone $number allowed - // exclude all '' enclosed strings, ignore all numbers [note must start with digit] - // can have space/tab/new line - // must have <> = , ( [not equal, equal, comma, opening round bracket] - // can have space/tab/new line - // $ number with 1-9 for first and 0-9 for further digits - // /s for matching new line in . list - // [disabled, we don't used ^ or $] /m for multi line match - // Matches in 1:, must be array_filtered to remove empty, count with array_unique - $query_split = '[(=,?-]|->|->>|#>|#>>|@>|<@|\?\|\?\&|\|\||#-'; - preg_match_all( - '/' - . '(?:\'.*?\')?\s*(?:\?\?|<>|' . $query_split . ')\s*' - . '(?:\d+|(?:\'.*?\')|(\$[1-9]{1}(?:[0-9]{1,})?))' - . '/s', - $query, - $match - ); - return count(array_unique(array_filter($match[1]))); + return $this->db_functions->__dbCountQueryParams($query); } /** @@ -1427,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 @@ -1440,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; @@ -1733,7 +1716,7 @@ class IO { if ( !empty($this->dbh) && - $this->dbh instanceof \PgSql\Connection + $this->dbh instanceof \PgSql\Connection /** @phpstan-ignore-line future could be other */ ) { // reset any client encodings set $this->dbResetEncoding(); @@ -1814,14 +1797,13 @@ class IO $html_tags = ['{b}', '{/b}', '{br}']; $replace_html = ['', '', '
']; $replace_text = ['', '', ' **** ']; - $string = ''; - $string .= '{b}-DB-info->{/b} Connected to db {b}\'' . $this->db_name . '\'{/b} '; - $string .= 'with schema {b}\'' . $this->db_schema . '\'{/b} '; - $string .= 'as user {b}\'' . $this->db_user . '\'{/b} '; - $string .= 'at host {b}\'' . $this->db_host . '\'{/b} '; - $string .= 'on port {b}\'' . $this->db_port . '\'{/b} '; - $string .= 'with ssl mode {b}\'' . $this->db_ssl . '\'{/b}{br}'; - $string .= '{b}-DB-info->{/b} DB IO Class debug output: {b}' + $string = '{b}-DB-info->{/b} Connected to db {b}\'' . $this->db_name . '\'{/b} ' + . 'with schema {b}\'' . $this->db_schema . '\'{/b} ' + . 'as user {b}\'' . $this->db_user . '\'{/b} ' + . 'at host {b}\'' . $this->db_host . '\'{/b} ' + . 'on port {b}\'' . $this->db_port . '\'{/b} ' + . 'with ssl mode {b}\'' . $this->db_ssl . '\'{/b}{br}' + . '{b}-DB-info->{/b} DB IO Class debug output: {b}' . ($this->dbGetDebug() ? 'Yes' : 'No') . '{/b}'; if ($log === true) { // if debug, remove / change b @@ -1829,7 +1811,7 @@ class IO $html_tags, $replace_text, $string - ), 'dbInfo'); + ), 'DB_INFO'); } else { $string = $string . '{br}'; } @@ -1985,7 +1967,7 @@ class IO if (is_array($array)) { $this->nbsp = ''; $string .= $this->__printArray($array); - $this->__dbDebugMessage('db', $string, 'dbDumpData'); + $this->__dbDebugMessage('db', $string, 'DB_INFO'); } return $string; } @@ -2961,7 +2943,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, @@ -3156,8 +3138,10 @@ class IO 'pk_name' => '', 'count' => 0, 'query' => '', + 'query_raw' => $query, 'result' => null, - 'returning_id' => false + 'returning_id' => false, + 'placeholder_converted' => [], ]; // if this is an insert query, check if we can add a return if ($this->dbCheckQueryForInsert($query, true)) { @@ -3197,6 +3181,39 @@ class IO $this->prepare_cursor[$stm_name]['pk_name'] = $pk_name; } } + // QUERY PARAMS: run query params check and rewrite + if ($this->dbGetConvertPlaceholder() === true) { + try { + $this->placeholder_converted = ConvertPlaceholder::convertPlaceholderInQuery( + $query, + null, + $this->dbGetConvertPlaceholderTarget() + ); + // write the new queries over the old + if (!empty($this->placeholder_converted['query'])) { + $query = $this->placeholder_converted['query']; + } + $this->prepare_cursor[$stm_name]['placeholder_converted'] = $this->placeholder_converted; + } catch (\OutOfRangeException $e) { + $this->__dbError($e->getCode(), context:[ + 'statement_name' => $stm_name, + 'query' => $query, + 'location' => 'dbPrepare', + 'error' => 'OutOfRangeException', + 'exception' => $e + ]); + return false; + } catch (\RuntimeException $e) { + $this->__dbError($e->getCode(), context:[ + 'statement_name' => $stm_name, + 'query' => $query, + 'location' => 'dbPrepare', + 'error' => 'RuntimeException', + 'exception' => $e + ]); + return false; + } + } // check prepared curser parameter count $this->prepare_cursor[$stm_name]['count'] = $this->__dbCountQueryParams($query); $this->prepare_cursor[$stm_name]['query'] = $query; @@ -3218,11 +3235,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, ]); @@ -3732,7 +3750,7 @@ class IO } /** - * convert db values (set) + * convert db values (set) to php matching types * * @param Convert $convert * @return void @@ -3743,7 +3761,7 @@ class IO } /** - * unsert convert db values flag + * unsert convert db values flag for converting db to php matching types * * @param Convert $convert * @return void @@ -3754,7 +3772,7 @@ class IO } /** - * Reset to origincal config file set + * Reset to original config file set for converting db to php matching type * * @return void */ @@ -3766,7 +3784,7 @@ class IO } /** - * check if a conert flag is set + * check if a convert flag is set for converting db to php matching type * * @param Convert $convert * @return bool @@ -3780,7 +3798,7 @@ class IO } /** - * Set if we want to auto convert PDO/\Pg placeholders + * Set if we want to auto convert to PDO/\Pg placeholders * * @param bool $flag * @return void @@ -4291,7 +4309,7 @@ class IO * @param string $stm_name The name of the stored statement * @param string $key Key field name in prepared cursor array * Allowed are: pk_name, count, query, returning_id - * @return null|string|int|bool Entry from each of the valid keys + * @return null|string|int|bool|array Entry from each of the valid keys * Will return false on error * Not ethat returnin_id also can return false * but will not set an error entry @@ -4299,7 +4317,7 @@ class IO public function dbGetPrepareCursorValue( string $stm_name, string $key - ): null|string|int|bool { + ): null|string|int|bool|array { // if no statement name if (empty($stm_name)) { $this->__dbError( @@ -4310,7 +4328,7 @@ class IO return false; } // if not a valid key - if (!in_array($key, ['pk_name', 'count', 'query', 'returning_id'])) { + if (!in_array($key, ['pk_name', 'count', 'query', 'returning_id', 'placeholder_converted'])) { $this->__dbError( 102, false, @@ -4345,6 +4363,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 // *************************** diff --git a/www/lib/CoreLibs/DB/SQL/Interface/SqlFunctions.php b/www/lib/CoreLibs/DB/SQL/Interface/SqlFunctions.php index 7e81a164..8ccfa9a6 100644 --- a/www/lib/CoreLibs/DB/SQL/Interface/SqlFunctions.php +++ b/www/lib/CoreLibs/DB/SQL/Interface/SqlFunctions.php @@ -285,6 +285,22 @@ interface SqlFunctions */ public function __dbConnectionBusySocketWait(int $timeout_seconds = 3): bool; + /** + * Undocumented function + * + * @param string $parameter + * @param bool $strip + * @return string + */ + public function __dbVersionInfo(string $parameter, bool $strip = true): string; + + /** + * Undocumented function + * + * @return array + */ + public function __dbVersionInfoParameterList(): array; + /** * Undocumented function * @@ -292,6 +308,13 @@ interface SqlFunctions */ public function __dbVersion(): string; + /** + * Undocumented function + * + * @return int + */ + public function __dbVersionNumeric(): int; + /** * Undocumented function * @@ -306,6 +329,14 @@ interface SqlFunctions ?int &$end = null ): ?array; + /** + * Undocumented function + * + * @param string $parameter + * @return string|bool + */ + public function __dbParameter(string $parameter): string|bool; + /** * Undocumented function * @@ -343,6 +374,14 @@ interface SqlFunctions * @return string */ public function __dbGetEncoding(): string; + + /** + * Undocumented function + * + * @param string $query + * @return int + */ + public function __dbCountQueryParams(string $query): int; } // __END__ diff --git a/www/lib/CoreLibs/DB/SQL/PgSQL.php b/www/lib/CoreLibs/DB/SQL/PgSQL.php index fa4c7e89..a343c8bc 100644 --- a/www/lib/CoreLibs/DB/SQL/PgSQL.php +++ b/www/lib/CoreLibs/DB/SQL/PgSQL.php @@ -51,6 +51,8 @@ declare(strict_types=1); namespace CoreLibs\DB\SQL; +use CoreLibs\DB\Support\ConvertPlaceholder; + // below no ignore is needed if we want to use PgSql interface checks with PHP 8.0 // as main system. Currently all @var sets are written as object /** @#phan-file-suppress PhanUndeclaredTypeProperty,PhanUndeclaredTypeParameter,PhanUndeclaredTypeReturnType */ @@ -102,7 +104,7 @@ class PgSQL implements Interface\SqlFunctions * SELECT foo FROM bar WHERE foobar = $1 * * @param string $query Query string with placeholders $1, .. - * @param array $params Matching parameters for each placerhold + * @param array $params Matching parameters for each placeholder * @return \PgSql\Result|false Query result */ public function __dbQueryParams(string $query, array $params): \PgSql\Result|false @@ -140,7 +142,7 @@ class PgSQL implements Interface\SqlFunctions * sends an async query to the server with params * * @param string $query Query string with placeholders $1, .. - * @param array $params Matching parameters for each placerhold + * @param array $params Matching parameters for each placeholder * @return bool true/false Query sent successful status */ public function __dbSendQueryParams(string $query, array $params): bool @@ -405,17 +407,13 @@ class PgSQL implements Interface\SqlFunctions } // no PK name given at all if (empty($pk_name)) { - // if name is plurar, make it singular - // if (preg_match("/.*s$/i", $table)) - // $table = substr($table, 0, -1); // set pk_name to "id" $pk_name = $table . "_id"; } - $seq = ($schema ? $schema . '.' : '') . $table . "_" . $pk_name . "_seq"; - $q = "SELECT CURRVAL('$seq') AS insert_id"; + $q = "SELECT CURRVAL(pg_get_serial_sequence($1, $2)) AS insert_id"; // I have to do manually or I overwrite the original insert internal vars ... - if ($q = $this->__dbQuery($q)) { - if (is_array($res = $this->__dbFetchArray($q))) { + if ($cursor = $this->__dbQueryParams($q, [$table, $pk_name])) { + if (is_array($res = $this->__dbFetchArray($cursor))) { list($id) = $res; } else { return false; @@ -449,26 +447,36 @@ class PgSQL implements Interface\SqlFunctions $table_prefix = $schema . '.'; } } + $params = [$table_prefix . $table]; + $replace = ['', '']; // read from table the PK name // faster primary key get - $q = "SELECT pg_attribute.attname AS column_name, " - . "format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS type " - . "FROM pg_index, pg_class, pg_attribute "; + $q = <<__dbQuery($q); + $cursor = $this->__dbQueryParams(str_replace( + ['{PG_NAMESPACE}', '{NSPNAME}'], + $replace, + $q + ), $params); if ($cursor !== false) { $__db_fetch_array = $this->__dbFetchArray($cursor); if (!is_array($__db_fetch_array)) { @@ -893,11 +901,13 @@ class PgSQL implements Interface\SqlFunctions public function __dbSetSchema(string $db_schema): int { // check if schema actually exists - $query = "SELECT EXISTS(" - . "SELECT 1 FROM information_schema.schemata " - . "WHERE schema_name = " . $this->__dbEscapeLiteral($db_schema) - . ")"; - $cursor = $this->__dbQuery($query); + $query = <<__dbQueryParams($query, [$db_schema]); // abort if execution fails if ($cursor === false) { return 1; @@ -966,6 +976,34 @@ class PgSQL implements Interface\SqlFunctions { return $this->__dbShow('client_encoding'); } + + /** + * Count placeholder queries. $ only + * + * @param string $query + * @return int + */ + public function __dbCountQueryParams(string $query): int + { + $matches = []; + // regex for params: only stand alone $number allowed + // exclude all '' enclosed strings, ignore all numbers [note must start with digit] + // can have space/tab/new line + // must have <> = , ( [not equal, equal, comma, opening round bracket] + // can have space/tab/new line + // $ number with 1-9 for first and 0-9 for further digits + // Collects also PDO ? and :named, but they are ignored + // /s for matching new line in . list + // [disabled, we don't used ^ or $] /m for multi line match + // 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, + $query, + $matches + ); + return count(array_unique(array_filter($matches[3]))); + } } // __END__ diff --git a/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php b/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php index 6e652acc..484b7828 100644 --- a/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php +++ b/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php @@ -14,6 +14,87 @@ 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 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 */ + private const PATTERN_NUMBERED = '(\$[1-9]{1}(?:[0-9]{1,})?)'; + // below here are full regex that will be used + /** @var string replace regex for named (:...) entries */ + public const REGEX_REPLACE_NAMED = '/' + . '(' . self::PATTERN_ELEMENT . ')' + . self::PATTERN_COMMENT + . '(' + . self::PATTERN_IGNORE + . self::PATTERN_NAMED + . ')' + . '/s'; + /** @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 + . ')' + . '/s'; + /** @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 + . ')' + . '/s'; + /** @var string the main lookup query for all placeholders */ + public const REGEX_LOOKUP_PLACEHOLDERS = '/' + // 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 + . self::PATTERN_IGNORE + // :name named part (PDO) [1] + . self::PATTERN_NAMED . '|' + // ? question mark part (PDO) [2] + . self::PATTERN_QUESTION_MARK . '|' + // $n numbered part (\PG php) [3] + . self::PATTERN_NUMBERED + // end match + . ')' + // single line -> add line break to matches in "." + . '/s'; + /** * Convert PDO type query with placeholders to \PG style and vica versa * For PDO to: ? and :named @@ -27,44 +108,24 @@ class ConvertPlaceholder * found has -1 if an error occoured in the preg_match_all call * * @param string $query Query with placeholders to convert - * @param array $params The parameters that are used for the query, and will be updated + * @param ?array $params The parameters that are used for the query, and will be updated * @param string $convert_to Either pdo or pg, will be converted to lower case for check - * @return array{original:array{query:string,params:array},type:''|'named'|'numbered'|'question_mark',found:int,matches:array,params_lookup:array,query:string,params:array} - * @throws \OutOfRangeException 200 + * @return array{original:array{query:string,params:array,empty_params:bool},type:''|'named'|'numbered'|'question_mark',found:int,matches:array,params_lookup:array,query:string,params:array} + * @throws \OutOfRangeException 200 If mixed placeholder types + * @throws \InvalidArgumentException 300 or 301 if wrong convert to with found placeholders */ public static function convertPlaceholderInQuery( string $query, - array $params, + ?array $params, string $convert_to = 'pg' ): array { $convert_to = strtolower($convert_to); $matches = []; - $query_split = '[(=,?-]|->|->>|#>|#>>|@>|<@|\?\|\?\&|\|\||#-'; - $pattern = '/' - // prefix string part, must match towards - // seperator for ( = , ? - [and json/jsonb in pg doc section 9.15] - . '(?:\'.*?\')?\s*(?:\?\?|' . $query_split . ')\s*' - // match for replace part - . '(?:' - // digit -> ignore - . '\d+|' - // other string -> ignore - . '(?:\'.*?\')|' - // :name named part (PDO) - . '(:\w+)|' - // ? question mark part (PDO) - . '(?:(?:\?\?)?\s*(\?{1}))|' - // $n numbered part (\PG php) - . '(\$[1-9]{1}(?:[0-9]{1,})?)' - // end match - . ')' - // single line -> add line break to matches in "." - . '/s'; // matches: // 1: :named // 2: ? question mark // 3: $n numbered - $found = preg_match_all($pattern, $query, $matches, PREG_UNMATCHED_AS_NULL); + $found = preg_match_all(self::REGEX_LOOKUP_PLACEHOLDERS, $query, $matches, PREG_UNMATCHED_AS_NULL); // if false or null set to -1 // || $found === null if ($found === false) { @@ -77,10 +138,10 @@ class ConvertPlaceholder /** @var array 3: $n matches */ $numbered_matches = array_filter($matches[3]); // count matches - $count_named = count($named_matches); + $count_named = count(array_unique($named_matches)); $count_qmark = count($qmark_matches); - $count_numbered = count($numbered_matches); - // throw if mixed + $count_numbered = count(array_unique($numbered_matches)); + // throw exception if mixed found if ( ($count_named && $count_qmark) || ($count_named && $count_numbered) || @@ -88,140 +149,195 @@ class ConvertPlaceholder ) { throw new \OutOfRangeException('Cannot have named, question mark and numbered in the same query', 200); } - // rebuild - $matches_return = []; - $type = ''; + // // throw if invalid conversion + // if (($count_named || $count_qmark) && $convert_to != 'pg') { + // throw new \InvalidArgumentException('Cannot convert from named or question mark placeholders to PDO', 300); + // } + // if ($count_numbered && $convert_to != 'pdo') { + // throw new \InvalidArgumentException('Cannot convert from numbered placeholders to Pg', 301); + // } + // return array + $return_placeholders = [ + // original + 'original' => [ + 'query' => $query, + 'params' => $params ?? [], + 'empty_params' => $params === null ? true : false, + ], + // type found, empty if nothing was done + 'type' => '', + // int: found, not found; -1: problem (set from false) + 'found' => (int)$found, + 'matches' => [], + // old to new lookup check + 'params_lookup' => [], + // this must match the count in params in new + 'needed' => 0, + // new + 'query' => '', + 'params' => [], + ]; + // replace basic regex and name settings + if ($count_named) { + $return_placeholders['type'] = 'named'; + $return_placeholders['matches'] = $named_matches; + $return_placeholders['needed'] = $count_named; + } elseif ($count_qmark) { + $return_placeholders['type'] = 'question_mark'; + $return_placeholders['matches'] = $qmark_matches; + $return_placeholders['needed'] = $count_qmark; + // for each ?:DTN: -> replace with $1 ... $n, any remaining :DTN: remove + } elseif ($count_numbered) { + $return_placeholders['type'] = 'numbered'; + $return_placeholders['matches'] = $numbered_matches; + $return_placeholders['needed'] = $count_numbered; + } + // run convert only if matching type and direction + if ( + (($count_named || $count_qmark) && $convert_to == 'pg') || + ($count_numbered && $convert_to == 'pdo') + ) { + $param_list = self::updateParamList($return_placeholders); + $return_placeholders['params_lookup'] = $param_list['params_lookup']; + $return_placeholders['query'] = $param_list['query']; + $return_placeholders['params'] = $param_list['params']; + } + // return data + return $return_placeholders; + } + + /** + * Updates the params list from one style to the other to match the query output + * if original.empty_params is set to true, no params replacement is done + * if param replacement has been done in a dbPrepare then this has to be run + * with the return palceholders array with params in original filled and empty_params turned off + * + * phpcs:disable Generic.Files.LineLength + * @param array{original:array{query:string,params:array,empty_params:bool},type:''|'named'|'numbered'|'question_mark',found:int,matches?:array,params_lookup?:array,query?:string,params?:array} $converted_placeholders + * phpcs:enable Generic.Files.LineLength + * @return array{params_lookup:array,query:string,params:array} + */ + public static function updateParamList(array $converted_placeholders): array + { + // skip if nothing set + if (!$converted_placeholders['found']) { + return [ + 'params_lookup' => [], + 'query' => '', + 'params' => [] + ]; + } $query_new = ''; $params_new = []; $params_lookup = []; - if ($count_named && $convert_to == 'pg') { - $type = 'named'; - $matches_return = $named_matches; - // only check for :named - $pattern_replace = '/' - . '((?:\'.*?\')?\s*(?:\?\?|' . $query_split . ')\s*)' - . '(\d+|(?:\'.*?\')|(:\w+))' - . '/s'; - // 0: full - // 1: pre part - // 2: keep part UNLESS '3' is set - // 3: replace part :named - $pos = 0; - $query_new = preg_replace_callback( - $pattern_replace, - function ($matches) use (&$pos, &$params_new, &$params_lookup, $params) { - // only count up if $match[3] is not yet in lookup table - if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) { - $pos++; - $params_lookup[$matches[3]] = '$' . $pos; - $params_new[] = $params[$matches[3]] ?? - throw new \RuntimeException( - 'Cannot lookup ' . $matches[3] . ' 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]] ?? + // set to null if params is empty + $params = $converted_placeholders['original']['params']; + $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 + $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]])) { + $pos++; + $params_lookup[$matches[3]] = '$' . $pos; + // skip params setup if param list is empty + if (!$empty_params) { + $params_new[] = $params[$matches[3]] ?? + throw new \RuntimeException( + 'Cannot lookup ' . $matches[3] . ' 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 + ) + ); + }, + $converted_placeholders['original']['query'] + ); + break; + case 'question_mark': + if (!$empty_params) { + // order and data stays the same + $params_new = $params ?? []; + } + // 0: full + // 1: pre part + // 2: keep part UNLESS '3' is set + // 3: replace part ? + $pos = 0; + $query_new = preg_replace_callback( + self::REGEX_REPLACE_QUESTION_MARK, + function ($matches) use (&$pos, &$params_lookup) { + // only count pos up for actual replacements we will do + if (!empty($matches[3])) { + $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 + ); + }, + $converted_placeholders['original']['query'] + ); + break; + case 'numbered': + // 0: full + // 1: pre part + // 2: keep part UNLESS '3' is set + // 3: 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]])) { + $pos++; + $params_lookup[$matches[3]] = ':' . $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 + ); + } + } + // 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 + 221 ) - ); - }, - $query - ); - } elseif ($count_qmark && $convert_to == 'pg') { - $type = 'question_mark'; - $matches_return = $qmark_matches; - // order and data stays the same - $params_new = $params; - // only check for ? - $pattern_replace = '/' - . '((?:\'.*?\')?\s*(?:\?\?|' . $query_split . ')\s*)' - . '(\d+|(?:\'.*?\')|(?:(?:\?\?)?\s*(\?{1})))' - . '/s'; - // 0: full - // 1: pre part - // 2: keep part UNLESS '3' is set - // 3: replace part ? - $pos = 0; - $query_new = preg_replace_callback( - $pattern_replace, - function ($matches) use (&$pos, &$params_lookup) { - // only count pos up for actual replacements we will do - if (!empty($matches[3])) { - $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 - ); - }, - $query - ); - // for each ?:DTN: -> replace with $1 ... $n, any remaining :DTN: remove - } elseif ($count_numbered && $convert_to == 'pdo') { - // convert numbered to named - $type = 'numbered'; - $matches_return = $numbered_matches; - // only check for $n - $pattern_replace = '/' - . '((?:\'.*?\')?\s*(?:\?\?|' . $query_split . ')\s*)' - . '(\d+|(?:\'.*?\')|(\$[1-9]{1}(?:[0-9]{1,})?))' - . '/s'; - // 0: full - // 1: pre part - // 2: keep part UNLESS '3' is set - // 3: replace part $numbered - $pos = 0; - $query_new = preg_replace_callback( - $pattern_replace, - function ($matches) use (&$pos, &$params_new, &$params_lookup, $params) { - // only count up if $match[3] is not yet in lookup table - if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) { - $pos++; - $params_lookup[$matches[3]] = ':' . $pos . '_named'; - $params_new[] = $params[($pos - 1)] ?? - throw new \RuntimeException( - 'Cannot lookup ' . ($pos - 1) . ' in params list', - 220 - ); - } - // 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 - ) - ); - }, - $query - ); + ); + }, + $converted_placeholders['original']['query'] + ); + break; } - // return, old query is always set return [ - // original - 'original' => [ - 'query' => $query, - 'params' => $params, - ], - // type found, empty if nothing was done - 'type' => $type, - // int: found, not found; -1: problem (set from false) - 'found' => (int)$found, - 'matches' => $matches_return, - // old to new lookup check 'params_lookup' => $params_lookup, - // new 'query' => $query_new ?? '', 'params' => $params_new, ]; diff --git a/www/lib/CoreLibs/DeprecatedHelper/Deprecated84.php b/www/lib/CoreLibs/DeprecatedHelper/Deprecated84.php new file mode 100644 index 00000000..c9de92c7 --- /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<0,max> $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__ diff --git a/www/lib/CoreLibs/Language/Core/GetTextReader.php b/www/lib/CoreLibs/Language/Core/GetTextReader.php index 0a5715a7..dd1ef96d 100644 --- a/www/lib/CoreLibs/Language/Core/GetTextReader.php +++ b/www/lib/CoreLibs/Language/Core/GetTextReader.php @@ -190,7 +190,6 @@ class GetTextReader private function loadTables(): void { if ( - is_array($this->cache_translations) && is_array($this->table_originals) && is_array($this->table_translations) ) { @@ -318,10 +317,7 @@ class GetTextReader if ($this->enable_cache) { // Caching enabled, get translated string from cache - if ( - is_array($this->cache_translations) && - array_key_exists($string, $this->cache_translations) - ) { + if (array_key_exists($string, $this->cache_translations)) { return $this->cache_translations[$string]; } else { return $string; @@ -481,7 +477,7 @@ class GetTextReader $key = $single . chr(0) . $plural; if ($this->enable_cache) { - if (is_array($this->cache_translations) && !array_key_exists($key, $this->cache_translations)) { + if (!array_key_exists($key, $this->cache_translations)) { return ($number != 1) ? $plural : $single; } else { $result = $this->cache_translations[$key]; 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) diff --git a/www/lib/CoreLibs/Output/Form/Generate.php b/www/lib/CoreLibs/Output/Form/Generate.php index 62ca7370..68e7a1df 100644 --- a/www/lib/CoreLibs/Output/Form/Generate.php +++ b/www/lib/CoreLibs/Output/Form/Generate.php @@ -474,7 +474,7 @@ class Generate $page_name_camel_case ); try { - /** @var TableArrays\Interface\TableArraysInterface|false $class */ + /** @var TableArrays\Interface\TableArraysInterface $class */ $class = new $class_string($this); } catch (\Throwable $t) { $this->log->critical('CLASS LOADING: Failed loading: ' . $class_string . ' => ' . $t->getMessage()); @@ -1757,14 +1757,9 @@ class Generate $this->dba->setTableArrayEntry($this->dba->getTableArray()[$key]['preset'], $key, 'value'); } } - if (is_array($this->reference_array)) { - if (!is_array($this->reference_array)) { - $this->reference_array = []; - } - reset($this->reference_array); - foreach ($this->reference_array as $key => $value) { - unset($this->reference_array[$key]['selected']); - } + reset($this->reference_array); + foreach ($this->reference_array as $key => $value) { + unset($this->reference_array[$key]['selected']); } $this->warning = 1; $this->msg = $this->l->__('Cleared for new Dataset!'); @@ -1787,20 +1782,15 @@ class Generate $this->dba->unsetTableArrayEntry($key, 'input_value'); } - if (is_array($this->reference_array)) { - // load each reference_table - if (!is_array($this->reference_array)) { - $this->reference_array = []; - } - reset($this->reference_array); - foreach ($this->reference_array as $key => $value) { - unset($this->reference_array[$key]['selected']); - $q = 'SELECT ' . $this->reference_array[$key]['other_table_pk'] - . ' FROM ' . $this->reference_array[$key]['table_name'] - . ' WHERE ' . $this->int_pk_name . ' = ' . $this->dba->getTableArray()[$this->int_pk_name]['value']; - while (is_array($res = $this->dba->dbReturn($q))) { - $this->reference_array[$key]['selected'][] = $res[$this->reference_array[$key]['other_table_pk']]; - } + // load each reference_table + reset($this->reference_array); + foreach ($this->reference_array as $key => $value) { + unset($this->reference_array[$key]['selected']); + $q = 'SELECT ' . $this->reference_array[$key]['other_table_pk'] + . ' FROM ' . $this->reference_array[$key]['table_name'] + . ' WHERE ' . $this->int_pk_name . ' = ' . $this->dba->getTableArray()[$this->int_pk_name]['value']; + while (is_array($res = $this->dba->dbReturn($q))) { + $this->reference_array[$key]['selected'][] = $res[$this->reference_array[$key]['other_table_pk']]; } } $this->warning = 1; @@ -1979,24 +1969,19 @@ class Generate // write the object $this->dba->dbWrite($addslashes, [], true); // write reference array (s) if necessary - if (is_array($this->reference_array)) { - if (!is_array($this->reference_array)) { - $this->reference_array = []; + reset($this->reference_array); + foreach ($this->reference_array as $reference_array) { + $q = 'DELETE FROM ' . $reference_array['table_name'] + . ' WHERE ' . $this->int_pk_name . ' = ' . $this->dba->getTableArray()[$this->int_pk_name]['value']; + $this->dba->dbExec($q); + $q = 'INSERT INTO ' . $reference_array['table_name'] + . ' (' . $reference_array['other_table_pk'] . ', ' . $this->int_pk_name . ') VALUES '; + for ($i = 0, $i_max = count($reference_array['selected']); $i < $i_max; $i++) { + $t_q = '(' . $reference_array['selected'][$i] . ', ' + . $this->dba->getTableArray()[$this->int_pk_name]['value'] . ')'; + $this->dba->dbExec($q . $t_q); } - reset($this->reference_array); - foreach ($this->reference_array as $reference_array) { - $q = 'DELETE FROM ' . $reference_array['table_name'] - . ' WHERE ' . $this->int_pk_name . ' = ' . $this->dba->getTableArray()[$this->int_pk_name]['value']; - $this->dba->dbExec($q); - $q = 'INSERT INTO ' . $reference_array['table_name'] - . ' (' . $reference_array['other_table_pk'] . ', ' . $this->int_pk_name . ') VALUES '; - for ($i = 0, $i_max = count($reference_array['selected']); $i < $i_max; $i++) { - $t_q = '(' . $reference_array['selected'][$i] . ', ' - . $this->dba->getTableArray()[$this->int_pk_name]['value'] . ')'; - $this->dba->dbExec($q . $t_q); - } - } // foreach reference arrays - } // if reference arrays + } // foreach reference arrays // write element list if (!empty($this->element_list)) { $type = []; @@ -2230,16 +2215,11 @@ class Generate public function formDeleteTableArray() { // remove any reference arrays - if (is_array($this->reference_array)) { - if (!is_array($this->reference_array)) { - $this->reference_array = []; - } - reset($this->reference_array); - foreach ($this->reference_array as $reference_array) { - $q = 'DELETE FROM ' . $reference_array['table_name'] - . ' WHERE ' . $this->int_pk_name . ' = ' . $this->dba->getTableArray()[$this->int_pk_name]['value']; - $this->dba->dbExec($q); - } + reset($this->reference_array); + foreach ($this->reference_array as $reference_array) { + $q = 'DELETE FROM ' . $reference_array['table_name'] + . ' WHERE ' . $this->int_pk_name . ' = ' . $this->dba->getTableArray()[$this->int_pk_name]['value']; + $this->dba->dbExec($q); } // remove any element list references if (!empty($this->element_list)) { 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', diff --git a/www/lib/CoreLibs/Output/Form/Token.php b/www/lib/CoreLibs/Output/Form/Token.php index 49becb30..e793e4ba 100644 --- a/www/lib/CoreLibs/Output/Form/Token.php +++ b/www/lib/CoreLibs/Output/Form/Token.php @@ -2,7 +2,7 @@ /* * sets a form token in the _SESSION variable - * session must be started for this to work + * session must be started and running for this to work */ declare(strict_types=1); diff --git a/www/lib/CoreLibs/Output/Image.php b/www/lib/CoreLibs/Output/Image.php index 9fd82619..aa449602 100644 --- a/www/lib/CoreLibs/Output/Image.php +++ b/www/lib/CoreLibs/Output/Image.php @@ -256,8 +256,8 @@ class Image } // check resize parameters if ($inc_width > $thumb_width || $inc_height > $thumb_height) { - $thumb_width_r = 0; - $thumb_height_r = 0; + $thumb_width_r = 1; + $thumb_height_r = 1; // we need to keep the aspect ration on longest side if ( ($inc_height > $inc_width && @@ -288,6 +288,12 @@ class Image !file_exists($thumbnail_write_path . $thumbnail) ) { // image, copy source image, offset in image, source x/y, new size, source image size + if ($thumb_width_r < 1) { + $thumb_width_r = 1; + } + if ($thumb_height_r < 1) { + $thumb_height_r = 1; + } $thumb = imagecreatetruecolor($thumb_width_r, $thumb_height_r); if ($thumb === false) { throw new \RuntimeException( @@ -380,9 +386,7 @@ class Image } } // add output path - if ($thumbnail !== false) { - $thumbnail = $thumbnail_web_path . $thumbnail; - } + $thumbnail = $thumbnail_web_path . $thumbnail; } elseif ($create_dummy === true) { // create dummy image in the thumbnail size // if one side is missing, use the other side to create a square @@ -399,10 +403,10 @@ class Image !file_exists($thumbnail_write_path . $thumbnail) ) { // if both are unset, set to 250 - if ($thumb_height == 0) { + if ($thumb_height < 1) { $thumb_height = 250; } - if ($thumb_width == 0) { + if ($thumb_width < 1) { $thumb_width = 250; } $thumb = imagecreatetruecolor($thumb_width, $thumb_height); diff --git a/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php b/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php new file mode 100644 index 00000000..a30cb3b1 --- /dev/null +++ b/www/lib/CoreLibs/Security/AsymmetricAnonymousEncryption.php @@ -0,0 +1,408 @@ +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->public_key != $public_key + ) { + 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); /** @phan-suppress-current-line PhanTypeObjectUnsetDeclaredProperty */ + } + + /* ************************************************************************ + * MARK: PRIVATE + * *************************************************************************/ + + /** + * Create the internal key pair in binary + * + * @param ?string $key_pair + * @return string + * @throws \UnexpectedValueException key pair empty + * @throws \UnexpectedValueException invalid hex key pair + * @throws \RangeException key pair not correct size + */ + 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 + * @throws \UnexpectedValueException public key empty + * @throws \UnexpectedValueException invalid hex key + * @throws \RangeException invalid key length + */ + 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 + * @throws \UnexpectedValueException create encryption failed + * @throws \UnexpectedValueException convert to base64 failed + */ + 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 + * @throws \UnexpectedValueException message string empty + * @throws \UnexpectedValueException base64 decoding failed + * @throws \UnexpectedValueException decryption failed + * @throws \UnexpectedValueException could not decrypt message + */ + private function asymmetricDecryption( + #[\SensitiveParameter] + string $message, + #[\SensitiveParameter] + ?string $key_pair + ): string { + if (empty($message)) { + throw new \UnexpectedValueException('Encrypted 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); + sodium_memzero($result); + throw new \UnexpectedValueException("Decrypting message failed: " . $e->getMessage()); + } + sodium_memzero($key_pair); + sodium_memzero($result); + if (!is_string($plaintext)) { + throw new \UnexpectedValueException('Invalid key pair'); + } + return $plaintext; + } + + /* ************************************************************************ + * MARK: PUBLIC + * *************************************************************************/ + + /** + * sets the private key for encryption + * + * @param string $key_pair Key pair in hex + * @return AsymmetricAnonymousEncryption + * @throws \UnexpectedValueException key pair empty + */ + public function setKeyPair( + #[\SensitiveParameter] + string $key_pair + ): 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); + } + return $this; + } + + /** + * 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 AsymmetricAnonymousEncryption + * @throws \UnexpectedValueException public key empty + */ + public function setPublicKey(string $public_key): AsymmetricAnonymousEncryption + { + 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); + return $this; + } + + /** + * 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 { diff --git a/www/lib/CoreLibs/Security/SymmetricEncryption.php b/www/lib/CoreLibs/Security/SymmetricEncryption.php index c12f4b3f..08826212 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,16 +45,49 @@ 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 { - 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; } + /** + * 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); /** @phan-suppress-current-line PhanTypeObjectUnsetDeclaredProperty */ + } + /* ************************************************************************ * MARK: PRIVATE * *************************************************************************/ @@ -62,11 +95,19 @@ 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 + * @throws \UnexpectedValueException empty key + * @throws \UnexpectedValueException invalid hex key + * @throws \RangeException invalid length */ - 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) { @@ -87,36 +128,42 @@ 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(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; } /** @@ -124,15 +171,15 @@ class SymmetricEncryption * * @param string $message Message to encrypt * @param ?string $key Mandatory encryption key, will throw exception if empty - * @return string - * @throws \Exception - * @throws \RangeException + * @return string Ciphered text + * @throws \UnexpectedValueException create message failed */ - private function encryptData(string $message, ?string $key): string - { - if (empty($this->key) || $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 { @@ -145,6 +192,8 @@ class SymmetricEncryption ) ); } catch (SodiumException $e) { + sodium_memzero($message); + sodium_memzero($key); throw new \UnexpectedValueException("Create encrypted message failed: " . $e->getMessage()); } sodium_memzero($message); @@ -156,19 +205,49 @@ class SymmetricEncryption * MARK: PUBLIC * *************************************************************************/ - /** * set a new key for encryption * * @param string $key - * @return void + * @return SymmetricEncryption + * @throws \UnexpectedValueException key cannot be empty */ - public function setKey(string $key) - { + public function setKey( + #[\SensitiveParameter] + string $key + ): 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); + return $this; + } + + /** + * 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; } /** @@ -178,13 +257,13 @@ 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(string $encrypted, string $key): string - { + public static function decryptKey( + #[\SensitiveParameter] + string $encrypted, + #[\SensitiveParameter] + string $key + ): string { return self::getInstance()->decryptData($encrypted, $key); } @@ -193,12 +272,11 @@ class SymmetricEncryption * * @param string $encrypted Message encrypted with safeEncrypt() * @return string - * @throws \RangeException - * @throws \UnexpectedValueException - * @throws \UnexpectedValueException */ - public function decrypt(string $encrypted): string - { + public function decrypt( + #[\SensitiveParameter] + string $encrypted + ): string { return $this->decryptData($encrypted, $this->key); } @@ -209,11 +287,13 @@ 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(string $message, string $key): string - { + public static function encryptKey( + #[\SensitiveParameter] + string $message, + #[\SensitiveParameter] + string $key + ): string { return self::getInstance()->encryptData($message, $key); } @@ -222,11 +302,11 @@ class SymmetricEncryption * * @param string $message Message to encrypt * @return string - * @throws \Exception - * @throws \RangeException */ - public function encrypt(string $message): string - { + public function encrypt( + #[\SensitiveParameter] + string $message + ): string { return $this->encryptData($message, $this->key); } } diff --git a/www/lib/CoreLibs/Template/SmartyExtend.php b/www/lib/CoreLibs/Template/SmartyExtend.php index e9b906d6..10eaa981 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 +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 * 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 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 $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 _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,77 @@ class SmartyExtend extends \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 { + /** @phan-suppress-next-line PhanNoopNew */ + 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; + } + } + } } /** diff --git a/www/lib/CoreLibs/UrlRequests/Curl.php b/www/lib/CoreLibs/UrlRequests/Curl.php new file mode 100644 index 00000000..ced5129f --- /dev/null +++ b/www/lib/CoreLibs/UrlRequests/Curl.php @@ -0,0 +1,1041 @@ + all the valid request type */ + private const VALID_REQUEST_TYPES = ["get", "post", "put", "patch", "delete"]; + /** @var array list of requests type that are set as custom in the curl options */ + private const CUSTOM_REQUESTS = ["put", "patch", "delete"]; + /** @var array list of requests types that have _POST type fields */ + private const HAVE_POST_FIELDS = ["post", "put", "patch", "delete"]; + /** @var array list of requests that must have a body */ + private const MANDATORY_POST_FIELDS = ["post", "put", "patch"]; + /** @var int http ok request */ + public const HTTP_OK = 200; + /** @var int http ok creted response */ + public const HTTP_CREATED = 201; + /** @var int http ok no content */ + public const HTTP_NO_CONTENT = 204; + /** @var int error bad request */ + public const HTTP_BAD_REQUEST = 400; + /** @var int error not authorized Request */ + public const HTTP_NOT_AUTHORIZED = 401; + /** @var int error forbidden */ + public const HTTP_FORBIDDEN = 403; + /** @var int error not found */ + public const HTTP_NOT_FOUND = 404; + /** @var int error conflict */ + public const HTTP_CONFLICT = 409; + /** @var int error unprocessable entity */ + public const HTTP_UNPROCESSABLE_ENTITY = 422; + /** @var int major version for user agent */ + public const MAJOR_VERSION = 1; + + // the config is set to be as much compatible to guzzelHttp as possible + // phpcs:disable Generic.Files.LineLength + /** @var array{auth?:array{0:string,1:string,2:string},http_errors:bool,base_uri:string,headers:array>,query:array,timeout:float,connection_timeout:float} config settings as + *phpcs:enable Generic.Files.LineLength + * auth: [0: user, 1: password, 2: auth type] + * http_errors: default true, bool true/false for throwing exception on >= 400 HTTP errors + * base_uri: base url to set, will prefix all urls given in calls + * headers: (array) base headers, can be overwritten by headers set in call + * timeout: default 0, in seconds (CURLOPT_TIMEOUT_MS) + * connect_timeout: default 300, in seconds (CURLOPT_CONNECTTIMEOUT_MS) + */ + private array $config = [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [], + 'timeout' => 0, + 'connection_timeout' => 300, + ]; + /** @var array{scheme?:string,user?:string,host?:string,port?:string,path?:string,query?:string,fragment?:string,pass?:string} parsed base_uri */ + private array $parsed_base_uri = []; + /** @var array lower key header name matches to given header name */ + private array $headers_named = []; + /** @var int auth type from auth array in config */ + private int $auth_type = 0; + /** @var string username and password string from auth array in config */ + private string $auth_userpwd = ''; + /** @var string set if auth type basic is given, will be set as "Authorization: ..." */ + private string $auth_basic_header = ''; + + /** @var array> received headers per header name, with sub array if there are redirects */ + private array $received_headers = []; + + /** @var string the current url sent */ + private string $url = ''; + /** @var array{scheme?:string,user?:string,host?:string,port?:string,path?:string,query?:string,fragment?:string,pass?:string} parsed url to sent */ + private array $parsed_url = []; + /** @var array the current headers sent */ + private array $headers = []; + + /** + * see config allowe entries above + * + * @param array $config config settings to be set + */ + public function __construct(array $config = []) + { + $this->setConfiguration($config); + } + + // ********************************************************************* + // MARK: PRIVATE METHODS + // ********************************************************************* + + /** + * Set the main configuration + * + * phpcs:disable Generic.Files.LineLength + * @param array{auth?:array{0:string,1:string,2:string},http_errors?:bool,base_uri?:string,headers?:array>,query?:array,timeout?:float,connection_timeout?:float} $config + * @return void + * phpcs:enable Generic.Files.LineLength + */ + private function setConfiguration(array $config) + { + $default_config = [ + 'http_errors' => true, + 'base_uri' => '', + 'query' => [], + 'headers' => [], + 'timeout' => 0, + 'connection_timeout' => 300, + ]; + // auth string is array of 0: user, 1: password, 2: auth type + if (!empty($config['auth']) && is_array($config['auth'])) { + $auth_data = $this->authParser($config['auth']); + $this->auth_basic_header = $auth_data['auth_basic_header']; + $this->auth_type = $auth_data['auth_type']; + $this->auth_userpwd = $auth_data['auth_userpwd']; + } + // only set if bool + if ( + !isset($config['http_errors']) || + !is_bool($config['http_errors']) + ) { + $config['http_errors'] = true; + } + if (!empty($config['base_uri'])) { + if (($parsed_base_uri = $this->parseUrl($config['base_uri'])) !== false) { + $this->parsed_base_uri = $parsed_base_uri; + $config['base_uri'] = $config['base_uri']; + } + } + // general headers + if (!empty($config['headers'])) { + // seat the key lookup with lower keys + foreach (array_keys($config['headers']) as $key) { + if (isset($this->headers_named[strtolower((string)$key)])) { + continue; + } + $this->headers_named[strtolower((string)$key)] = (string)$key; + } + } + // timeout (must be numeric) + if (!empty($config['timeout']) && !is_numeric($config['timeout'])) { + $config['timeout'] = 0; + } + if (!empty($config['connection_timeout']) && !is_numeric($config['connection_timeout'])) { + $config['connection_timeout'] = 300; + } + + $this->config = array_merge($default_config, $config); + } + + // MARK: auth parser + + /** + * set various auth parameters and return them as array for further processing + * + * @param array{0:string,1:string,2:string} $auth + * @return array{auth_basic_header:string,auth_type:int,auth_userpwd:string} + */ + private function authParser(array $auth): array + { + $return_auth = [ + 'auth_basic_header' => '', + 'auth_type' => 0, + 'auth_userpwd' => '', + ]; + // on empty return as is, to force defaults + if ($auth === []) { + return $return_auth; + } + // base auth sets the header actually + $type = isset($auth[2]) ? strtolower($auth[2]) : 'basic'; + $userpwd = $auth[0] . ':' . $auth[1]; + switch ($type) { + case 'basic': + $return_auth['auth_basic_header'] = 'Basic ' . base64_encode( + $userpwd + ); + break; + case 'digest': + $return_auth['auth_type'] = CURLAUTH_DIGEST; + $return_auth['auth_userpwd'] = $userpwd; + break; + case 'ntlm': + $return_auth['auth_type'] = CURLAUTH_NTLM; + $return_auth['auth_userpwd'] = $userpwd; + break; + } + return $return_auth; + } + + // MARK: parse and build url + + /** + * From: https://github.com/guzzle/psr7/blob/a70f5c95fb43bc83f07c9c948baa0dc1829bf201/src/Uri.php#L106C5-L132C6 + * guzzle/psr7::parse + * + * convert the url to valid sets + * + * @param string $url + * @return array{scheme?:string,user?:string,host?:string,port?:string,path?:string,query?:string,fragment?:string,pass?:string}|false + */ + private function parseUrl(string $url): array|false + { + // If IPv6 + $prefix = ''; + if (preg_match('%^(.*://\[[0-9:a-f]+\])(.*?)$%', $url, $matches)) { + /** @var array{0:string, 1:string, 2:string} $matches */ + $prefix = $matches[1]; + $url = $matches[2]; + } + + /** @var string $encodedUrl */ + $encodedUrl = preg_replace_callback( + '%[^:/@?&=#]+%usD', + static function ($matches) { + return urlencode($matches[0]); + }, + $url + ); + + $result = parse_url($prefix . $encodedUrl); + + if ($result === false) { + return false; + } + + /** @var callable $caller */ + $caller = 'urldecode'; + return array_map($caller, $result); + } + + /** + * build back the URL based on the parsed URL scheme + * NOTE: this is only a sub implementation + * + * phpcs:disable Generic.Files.LineLength + * @param array{scheme?:string,user?:string,host?:string,port?:string,path?:string,query?:string,fragment?:string,pass?:string} $parsed_url + * @param bool $remove_until_slash [default=false] + * @param bool $add_query [default=false] + * @param bool $add_fragment [default=false] + * @return string + * phpcs:enable Generic.Files.LineLength + */ + private function buildUrl( + array $parsed_url, + bool $remove_until_slash = false, + bool $add_query = false, + bool $add_fragment = false + ): string { + $url = ''; + // scheme has : + if (!empty($parsed_url['scheme'])) { + $url .= $parsed_url['scheme'] . ':'; + } + // host + port = authority + if (!empty($parsed_url['host'])) { + $url .= '//'; + $url .= $parsed_url['host'] ?? ''; + if (!empty($parsed_url['port'])) { + $url .= ':' . $parsed_url['port']; + } + } + // remove the last part "/.." because we do not end with "/" + if ($remove_until_slash) { + $url_path = $parsed_url['path'] ?? ''; + if (($lastSlashPos = strrpos($url_path, '/')) !== false) { + $url .= substr($url_path, 0, $lastSlashPos + 1); + } + } else { + $url .= $parsed_url['path'] ?? ''; + } + // only on demand + if ($add_query && !empty($parsed_url['query'])) { + $url .= '?' . $parsed_url['query']; + } + if ($add_fragment && !empty($parsed_url['fragment'])) { + $url .= '#' . $parsed_url['fragment']; + } + return $url; + } + + // MARK: query, params and headers convert + + /** + * Build URL with base url and parameters + * + * @param string $url_req to send + * @param null|array $query any optional parameters to send + * @return string the fully build URL + */ + private function buildQuery(string $url_req, null|array $query = null): string + { + if (($parsed_url = $this->parseUrl($url_req)) !== false) { + $this->parsed_url = $parsed_url; + } + $url = $url_req; + if ( + !empty($this->config['base_uri']) && + empty($this->parsed_url['scheme']) + ) { + if (str_ends_with($this->config['base_uri'], '/')) { + $url = $this->config['base_uri'] . $url_req; + } else { + // remove until last / and add url, strip leading / if set + // remove last "/" part until we are at the domain + // if we do not start with http(s):// then assume blank + // NOTE any fragments or params will get dropped, only path will remain + $url = $this->buildUrl($this->parsed_base_uri, remove_until_slash: true) . $url_req; + } + if (($parsed_url = $this->parseUrl($url)) !== false) { + $this->parsed_url = $parsed_url; + } + } + // build query with global query + // any query set in the base_url or url_req will be overwritten + if (!empty($this->config['query'])) { + // add current query if set + // for params: if foo[0] then we ADD as php array type + // note that this has to be done on the user side, we just merge and local overrides global + $query = array_merge($this->config['query'], $query ?? []); + } + if (is_array($query)) { + $query = http_build_query($query, '', '&', PHP_QUERY_RFC3986); + } + // add the params to the url + if (!empty($query)) { + // if the url_url has a query or a a fragment, + // we need to build that url new + // $parsed_url = false; + if (!empty($this->parsed_url['query']) || !empty($this->parsed_url['framgent'])) { + $url = $this->buildUrl($this->parsed_url); + } + $url .= '?' . $query; + // fragments are ignored + // if (!empty($this->parsed_url['fragment'])) { + // $url .= '#' . $parsed_url['fragment']; + // } + } + // parse again with current url + if ($url != $url_req) { + if (($parsed_url = $this->parseUrl($url)) !== false) { + $this->parsed_url = $parsed_url; + } + } + + return $url; + } + + /** + * Convert array body data to json type string + * + * @param string|array $body + * @return string + */ + private function convertPayloadData(string|array $body): string + { + // convert to string as JSON block if it is an array + if (is_array($body)) { + $params = Json::jsonConvertArrayTo($body); + } elseif (is_string($body)) { + $params = $body; + } + return $params ?? ''; + } + + /** + * header convert from array key -> value to string list + * if the key value is numeric, it is assumed this is an array string list only + * Note: this should not be the case + * + * @param array> $headers + * @return array + */ + private function convertHeaders(array $headers): array + { + $return_headers = []; + $header_keys = []; + foreach ($headers as $key => $value) { + if (!is_string($key)) { + // TODO: throw error + continue; + } + // bad if not valid header key + if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $key)) { + throw new \UnexpectedValueException( + Json::jsonConvertArrayTo([ + 'status' => 'ERROR', + 'code' => 'R002', + 'type' => 'InvalidHeaderKey', + 'message' => 'Header key contains invalid characters', + 'context' => [ + 'key' => $key, + 'allowed' => '/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', + ], + ]), + 1 + ); + } + // if value is array, join to string + if (is_array($value)) { + $value = join(', ', $value); + } + $value = trim((string)$value, " \t"); + // header values must be valid + if (!preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) { + throw new \UnexpectedValueException( + Json::jsonConvertArrayTo([ + 'status' => 'ERROR', + 'code' => 'R003', + 'type' => 'InvalidHeaderValue', + 'message' => 'Header value contains invalid characters', + 'context' => [ + 'key' => $key, + 'value' => $value, + 'allowed' => '/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', + ], + ]), + 1 + ); + } + $return_headers[] = (string)$key . ':' . $value; + } + // remove empty entries + return $return_headers; + } + + /** + * default headers that are always set + * Authorization + * User-Agent + * + * @param array> $headers already set headers + * @param ?string $auth_basic_header + * @return array> + */ + private function buildDefaultHeaders(array $headers = [], ?string $auth_basic_header = ''): array + { + // add auth header if set, will overwrite any already set auth header + if ($auth_basic_header !== null && $auth_basic_header == '' && !empty($this->auth_basic_header)) { + $auth_basic_header = $this->auth_basic_header; + } + if (!empty($auth_basic_header)) { + // check if there is any auth header set, remove that one + if (!empty($auth_header_set = $this->headers_named[strtolower('Authorization')] ?? null)) { + unset($headers[$auth_header_set]); + } + // set new auth header + $headers['Authorization'] = $auth_basic_header; + } + // always add HTTP_HOST and HTTP_USER_AGENT + if (!isset($headers[strtolower('User-Agent')])) { + $headers['User-Agent'] = 'CoreLibsUrlRequestCurl/' . self::MAJOR_VERSION; + } + return $headers; + } + + /** + * Build headers, combine with global headers of they are set + * + * @param null|array> $headers + * @param ?string $auth_basic_header + * @return array> + */ + private function buildHeaders(null|array $headers, ?string $auth_basic_header): array + { + // if headers is null, return empty headers, do not set config default headers + // but the automatic set User-Agent and Authorization headers are always set + if ($headers === null) { + return $this->buildDefaultHeaders(auth_basic_header: $auth_basic_header); + } + // merge master headers with sub headers, sub headers overwrite master headers + if (!empty($this->config['headers'])) { + // we need to build the current headers as a lookup table + $headers_lookup = []; + foreach (array_keys($headers) as $key) { + $headers_lookup[strtolower((string)$key)] = (string)$key; + } + // add config headers if not set in local header + foreach ($this->headers_named as $header_key => $key) { + // is set local, use this, else use global + if (isset($headers_lookup[$header_key])) { + continue; + } + $headers[$key] = $this->config['headers'][$key]; + } + } + $headers = $this->buildDefaultHeaders($headers, $auth_basic_header); + return $headers; + } + + /** + * Set the array block that is sent to the request call + * Make sure that if headers is set as key but null it stays null and set to empty array + * if headers key is missing + * "get" calls do not set any body (null) + * + * phpcs:disable Generic.Files.LineLength + * @param string $type if set as get do not add body, else add body + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Request options + * @return array{auth:null|array{0:string,1:string,2:string},headers:null|array>,query:null|array,body:null|string|array,http_errors:null|bool} + * phpcs:enable Generic.Files.LineLength + */ + private function setOptions(string $type, array $options): array + { + return [ + "auth" => !array_key_exists('auth', $options) ? ['', '', ''] : $options['auth'], + "headers" => !array_key_exists('headers', $options) ? [] : $options['headers'], + "query" => $options['query'] ?? null, + "http_errors" => !array_key_exists('http_errors', $options) ? null : $options['http_errors'], + "body" => $options["body"] ?? + // check if we need a payload data set, set empty on not set + (in_array($type, self::MANDATORY_POST_FIELDS) && !isset($options['body']) ? [] : null) + ]; + } + + // MARK: main curl request + + /** + * Overall request call + * + * phpcs:disable Generic.Files.LineLength + * @param string $type get, post, pathc, put, delete: + * if not set or invalid throw error + * @param string $url The URL being requested, + * including domain and protocol + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Request options + * @return array{code:string,headers:array>,content:string} Return content + * code: HTTP code, if http_errors if off, this can also hold 400 or 500 type codes + * headers: earch header entry has an array of the entries, can be more than one if proxied, etc + * content: content string as is, if JSON type must be decoded afterwards + * @throws \RuntimeException if type param is not valid + * phpcs:enable Generic.Files.LineLength + */ + private function curlRequest( + string $type, + string $url, + array $options, + ): array { + // check if we need a payload data set, set empty on not set + $options = $this->setOptions($type, $options); + // set auth from override + if (is_array($options['auth'])) { + $auth_data = $this->authParser($options['auth']); + } else { + $auth_data = [ + 'auth_basic_header' => null, + 'auth_type' => null, + 'auth_userpwd' => null, + ]; + } + // build url + $this->url = $this->buildQuery($url, $options['query']); + $this->headers = $this->convertHeaders($this->buildHeaders( + $options['headers'], + $auth_data['auth_basic_header'] + )); + if (!in_array($type, self::VALID_REQUEST_TYPES)) { + throw new \RuntimeException( + Json::jsonConvertArrayTo([ + 'status' => 'ERROR', + 'code' => 'R001', + 'type' => 'InvalidRequestType', + 'message' => 'Invalid request type set: ' . $type, + 'context' => [ + 'type' => $type, + 'url' => $this->url, + 'headers' => $this->headers, + ], + ]), + 0, + ); + } + // init curl handle + $handle = $this->handleCurleInit($this->url); + // set the standard curl options + $this->setCurlOptions($handle, $this->headers, [ + 'auth_type' => $auth_data['auth_type'], + 'auth_userpwd' => $auth_data['auth_userpwd'], + ]); + // for post we set POST option + if ($type == "post") { + curl_setopt($handle, CURLOPT_POST, true); + } elseif (!empty($type) && in_array($type, self::CUSTOM_REQUESTS)) { + curl_setopt($handle, CURLOPT_CUSTOMREQUEST, strtoupper($type)); + } + // set body data if not null, will send empty [] for empty data + if (in_array($type, self::HAVE_POST_FIELDS) && $options['body'] !== null) { + curl_setopt($handle, CURLOPT_POSTFIELDS, $this->convertPayloadData($options['body'])); + } + // reset all headers before we start the call + $this->received_headers = []; + // run curl execute + $http_result = $this->handleCurlExec($handle); + // for debug + // print "CURLINFO_HEADER_OUT:
" . curl_getinfo($handle, CURLINFO_HEADER_OUT) . "
"; + // get response code and bail on not authorized + $http_response = $this->handleCurlResponse($handle, $http_result, $options['http_errors']); + // close handler + $this->handleCurlClose($handle); + // return response and result + return [ + 'code' => (string)$http_response, + 'headers' => $this->received_headers, + 'content' => (string)$http_result + ]; + } + + // MARK: curl init + + /** + * Handel curl init and errors + * + * @param string $url + * @return \CurlHandle + * @throws \RuntimeException if curl could not be initialized + */ + private function handleCurleInit(string $url): \CurlHandle + { + $handle = curl_init($url); + if ($handle !== false) { + return $handle; + } + // throw Error here with all codes + throw new \RuntimeException( + Json::jsonConvertArrayTo([ + 'status' => 'FAILURE', + 'code' => 'C001', + 'type' => 'CurlInitError', + 'message' => 'Failed to init curl with url: ' . $url, + 'context' => [ + 'url' => $url, + ], + ]), + 0, + ); + } + + // MARK: set curl options and header collector + + /** + * set the default curl options + * + * headers array: do not split into "key" => "value", they must be "key: value" + * + * @param \CurlHandle $handle + * @param array $headers list of options + * @param array{auth_type:?int,auth_userpwd:?string} $auth_data auth options to override global + * @return void + */ + private function setCurlOptions(\CurlHandle $handle, array $headers, array $auth_data): void + { + // for not Basic auth only, basic auth sets its own header + if ($auth_data['auth_type'] !== null || $auth_data['auth_userpwd'] !== null) { + // set global if any of the two is empty and both globals are set + if ( + (empty($auth_data['auth_type']) || empty($auth_data['auth_userpwd'])) && + !empty($this->auth_type) && !empty($this->auth_userpwd) + ) { + $auth_data['auth_type'] = $this->auth_type; + $auth_data['auth_userpwd'] = $this->auth_userpwd; + } + } + // set auth options for curl + if (!empty($auth_data['auth_type']) && !empty($auth_data['auth_userpwd'])) { + curl_setopt($handle, CURLOPT_HTTPAUTH, $auth_data['auth_type']); + curl_setopt($handle, CURLOPT_USERPWD, $auth_data['auth_userpwd']); + } + if ($headers !== []) { + curl_setopt($handle, CURLOPT_HTTPHEADER, $headers); + } + // curl_setopt($handle, CURLOPT_FAILONERROR, true); + // return response as string and not just HTTP_OK + curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); + // for debug only + curl_setopt($handle, CURLINFO_HEADER_OUT, true); + // curl_setopt($handle, CURLOPT_HEADER, true); + // collect the current request headers + curl_setopt($handle, CURLOPT_HEADERFUNCTION, [$this, 'collectCurlHttpHeaders']); + // if any timeout <1 + $timeout_requires_no_signal = false; + // if we have a timeout signal + if (!empty($this->config['timeout'])) { + $timeout_requires_no_signal = $this->config['timeout'] < 1; + curl_setopt($handle, CURLOPT_TIMEOUT_MS, (int)round($this->config['timeout'] * 1000)); + } + if (!empty($this->config['connection_timeout'])) { + $timeout_requires_no_signal = $timeout_requires_no_signal || + $this->config['connection_timeout'] < 1; + curl_setopt($handle, CURLOPT_CONNECTTIMEOUT_MS, (int)round($this->config['connection_timeout'] * 1000, 1)); + } + if ($timeout_requires_no_signal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') { + curl_setopt($handle, CURLOPT_NOSIGNAL, true); + } + } + + /** + * Collect HTTP headers + * They will be reset before each call + * + * @param \CurlHandle $curl current curl handle + * @param string $header header string to parse + * @return int size of current line of header + */ + private function collectCurlHttpHeaders(\CurlHandle $curl, string $header): int + { + $len = strlen($header); + $header = explode(':', $header, 2); + if (count($header) < 2) { + // ignore invalid headers + return $len; + } + $this->received_headers[strtolower(trim($header[0]))][] = trim($header[1]); + return $len; + } + + // MARK: Curl Exception handler + + /** + * handles any CURL execute and on error throws a correct error message + * + * @param \CurlHandle $handle Curl handler + * @return string Return content as string, if False will throw exception + * will only return HTTP_OK if CURLOPT_RETURNTRANSFER is turned off + * @throws \RuntimeException if the connection had an error + */ + private function handleCurlExec(\CurlHandle $handle): string + { + // execute query + $http_result = curl_exec($handle); + if ($http_result === true) { + // only if CURLOPT_RETURNTRANSFER is turned off + return (string)self::HTTP_OK; + } elseif ($http_result !== false) { + return $http_result; + } + $url = curl_getinfo($handle, CURLINFO_EFFECTIVE_URL); + $errno = curl_errno($handle); + $message = curl_error($handle); + switch ($errno) { + case CURLE_COULDNT_CONNECT: + case CURLE_COULDNT_RESOLVE_HOST: + case CURLE_OPERATION_TIMEOUTED: + $message = 'Could not connect to server (' . $url . '). Please check your ' + . 'internet connection and try again. [' . $message . ']'; + break; + case CURLE_SSL_PEER_CERTIFICATE: + $message = 'Could not verify SSL certificate. Please make sure ' + . 'that your network is not intercepting certificates. ' + . '(Try going to ' . $url . 'in your browser.) ' + . '[' . $message . ']'; + break; + case 0: + default: + $message = 'Unexpected error communicating with server: ' . $message; + } + + // throw an error like in the normal reqeust, but set to CURL error + throw new \RuntimeException( + Json::jsonConvertArrayTo([ + 'status' => 'FAILURE', + 'code' => 'C002', + 'type' => 'CurlExecError', + 'message' => $message, + 'context' => [ + 'url' => $url, + 'errno' => $errno, + 'message' => $message, + ], + ]), + $errno + ); + } + + // MARK: curl response handler + + /** + * Handle curl response, will throw exception on anything that is lower 400 + * can be turned off by setting http_errors to false + * + * @param \CurlHandle $handle Curl handler + * @param string $http_result result string from the url call + * @param ?bool $http_errors if we should throw an exception on error, + * override config setting + * @return string http response code + * @throws \RuntimeException if http_errors is true then will throw exception on any response code >= 400 + */ + private function handleCurlResponse( + \CurlHandle $handle, + string $http_result, + ?bool $http_errors + ): string { + $http_response = curl_getinfo($handle, CURLINFO_RESPONSE_CODE); + if ( + empty($http_errors ?? $this->config['http_errors']) || + $http_response < self::HTTP_BAD_REQUEST + ) { + return (string)$http_response; + } + // set curl error number + $err = curl_errno($handle); + // throw Error here with all codes + throw new \RuntimeException( + Json::jsonConvertArrayTo([ + 'status' => 'ERROR', + 'code' => 'H' . (string)$http_response, + 'type' => $http_response < 500 ? 'ClientError' : 'ServerError', + 'message' => 'Request could not be finished successfully because of bad request response', + 'context' => [ + 'http_response' => $http_response, + // extract all the error content if returned + 'result' => Json::jsonConvertToArray($http_result), + // curl internal error number + 'curl_errno' => $err, + // the full curl info block + 'curl_info' => curl_getinfo($handle), + ], + ]), + $err + ); + } + + /** + * close the current curl handle + * + * @param \CurlHandle $handle + * @return void + */ + private function handleCurlClose(\CurlHandle $handle): void + { + curl_close($handle); + } + + // ********************************************************************* + // MARK: PUBLIC METHODS + // ********************************************************************* + + // MARK: get class vars + + /** + * get the config array with all settings + * + * @return array all current config settings + */ + public function getConfig(): array + { + return $this->config; + } + + /** + * Return the full url as it was sent + * + * @return string url sent + */ + public function getUrlSent(): string + { + return $this->url; + } + + /** + * get the parsed url + * + * @return array{scheme?:string,user?:string,host?:string,port?:string,path?:string,query?:string,fragment?:string,pass?:string} + */ + public function getUrlParsedSent(): array + { + return $this->parsed_url; + } + + /** + * Return the full headers as they where sent + * + * @return array + */ + public function getHeadersSent(): array + { + return $this->headers; + } + + // MARK: set/remove for global headers + + /** + * set, add or overwrite header + * On default this will overwrite header, and not set + * + * @param array> $header + * @param bool $add [default=false] if set will add header to existing value + * @return void + */ + public function setHeaders(array $header, bool $add = false): void + { + foreach ($header as $key => $value) { + // check header previously set + if (isset($this->headers_named[strtolower($key)])) { + $header_key = $this->headers_named[strtolower($key)]; + if ($add) { + // for this add we always add array on the right side + if (!is_array($value)) { + $value = (array)$value; + } + // if not array, rewrite entry to array + if (!is_array($this->config['headers'][$header_key])) { + $this->config['headers'][$header_key] = [ + $this->config['headers'][$header_key] + ]; + } + $this->config['headers'][$header_key] = array_merge( + $this->config['headers'][$header_key], + $value + ); + } else { + $this->config['headers'][$header_key] = $value; + } + } else { + $this->headers_named[strtolower($key)] = $key; + $this->config['headers'][$key] = $value; + } + } + } + + /** + * remove header entry + * if key is only set then match only key, if both are set both sides must match + * + * @param array> $remove_headers + * @return void + */ + public function removeHeaders(array $remove_headers): void + { + foreach ($remove_headers as $key => $value) { + if (!isset($this->headers_named[strtolower($key)])) { + continue; + } + $header_key = $this->headers_named[strtolower($key)]; + if (!isset($this->config['headers'][$header_key])) { + continue; + } + // full remove + if ( + empty($value) || + ( + ( + // array both sides = equal + // string both sides = equal + (is_array($value) && is_array($this->config['headers'][$header_key])) || + (is_string($value) && is_string($this->config['headers'][$header_key])) + ) && + $value == $this->config['headers'][$header_key] + ) + ) { + unset($this->config['headers'][$header_key]); + unset($this->headers_named[$header_key]); + } elseif ( + // string value, array keys = in + // or both array and not a full match in the one before + (is_string($value) || is_array($value)) && + is_array($this->config['headers'][$header_key]) + ) { + // part remove of key, value must be array + if (!is_array($value)) { + $value = [$value]; + } + // array values so we rewrite the key pos + $this->config['headers'][$header_key] = array_values(array_diff( + $this->config['headers'][$header_key], + $value + )); + } + } + } + + // MARK: update/set base url + + /** + * Update or set the base url set + * if empty will unset the base url + * + * @param string $base_uri + * @return void + */ + public function setBaseUri(string $base_uri): void + { + $this->config['base_uri'] = $base_uri; + $this->parsed_base_uri = []; + if (!empty($base_uri)) { + if (($parsed_base_uri = $this->parseUrl($base_uri)) !== false) { + $this->parsed_base_uri = $parsed_base_uri; + } + } + } + + // MARK: main public call interface + + /** + * combined set call for any type of request with options type parameters + * + * phpcs:disable Generic.Files.LineLength + * @param string $type + * @param string $url + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options + * @return array{code:string,headers:array>,content:string} Result code, headers and content as array, content is json + * @throws \UnexpectedValueException on missing body data when body data is needed + * phpcs:enable Generic.Files.LineLength + */ + public function request(string $type, string $url, array $options = []): array + { + // can have + // - headers + // - query + // - auth: null for no auth at all + // - http_errors: false for no exception on http error + // depending on type, must have (post/put/patch), optional for (delete) + // - body + $type = strtolower($type); + return $this->curlRequest( + $type, + $url, + $options, + ); + } +} + +// __END__ diff --git a/www/lib/CoreLibs/UrlRequests/CurlTrait.php b/www/lib/CoreLibs/UrlRequests/CurlTrait.php new file mode 100644 index 00000000..a3b0cbe5 --- /dev/null +++ b/www/lib/CoreLibs/UrlRequests/CurlTrait.php @@ -0,0 +1,128 @@ +>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Request options + * @return array{code:string,headers:array>,content:string} [default=[]] Result code, headers and content as array, content is json + * @throws \UnexpectedValueException on missing body data when body data is needed + */ + abstract public function request(string $type, string $url, array $options = []): array; + + /** + * Makes an request to the target url via curl: GET + * Returns result as string (json) + * + * @param string $url The URL being requested, + * including domain and protocol + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set + * @return array{code:string,headers:array>,content:string} [default=[]] Result code, headers and content as array, content is json + */ + public function get(string $url, array $options = []): array + { + return $this->request( + "get", + $url, + $options, + ); + } + + /** + * Makes an request to the target url via curl: POST + * Returns result as string (json) + * + * @param string $url The URL being requested, + * including domain and protocol + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set + * @return array{code:string,headers:array>,content:string} Result code, headers and content as array, content is json + */ + public function post(string $url, array $options): array + { + return $this->request( + "post", + $url, + $options, + ); + } + + /** + * Makes an request to the target url via curl: PUT + * Returns result as string (json) + * + * @param string $url The URL being requested, + * including domain and protocol + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set + * @return array{code:string,headers:array>,content:string} Result code, headers and content as array, content is json + */ + public function put(string $url, array $options): array + { + return $this->request( + "put", + $url, + $options, + ); + } + + /** + * Makes an request to the target url via curl: PATCH + * Returns result as string (json) + * + * @param string $url The URL being requested, + * including domain and protocol + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set + * @return array{code:string,headers:array>,content:string} Result code, headers and content as array, content is json + */ + public function patch(string $url, array $options): array + { + return $this->request( + "patch", + $url, + $options, + ); + } + + /** + * Makes an request to the target url via curl: DELETE + * Returns result as string (json) + * Note that DELETE body is optional + * + * @param string $url The URL being requested, + * including domain and protocol + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options Options to set + * @return array{code:string,headers:array>,content:string} [default=[]] Result code, headers and content as array, content is json + */ + public function delete(string $url, array $options = []): array + { + return $this->request( + "delete", + $url, + $options, + ); + } +} + +// __END__ diff --git a/www/lib/CoreLibs/UrlRequests/Interface/RequestsInterface.php b/www/lib/CoreLibs/UrlRequests/Interface/RequestsInterface.php new file mode 100644 index 00000000..bc6bb0e3 --- /dev/null +++ b/www/lib/CoreLibs/UrlRequests/Interface/RequestsInterface.php @@ -0,0 +1,83 @@ + all current config settings + */ + public function getConfig(): array; + + /** + * Return the full url as it was sent + * + * @return string url sent + */ + public function getUrlSent(): string; + + /** + * get the parsed url + * + * @return array{scheme?:string,user?:string,host?:string,port?:string,path?:string,query?:string,fragment?:string,pass?:string} + */ + public function getUrlParsedSent(): array; + + /** + * Return the full headers as they where sent + * + * @return array + */ + public function getHeadersSent(): array; + + /** + * set, add or overwrite header + * On default this will overwrite header, and not set + * + * @param array> $header + * @param bool $add [default=false] if set will add header to existing value + * @return void + */ + public function setHeaders(array $header, bool $add = false): void; + + /** + * remove header entry + * if key is only set then match only key, if both are set both sides must match + * + * @param array $remove_headers + * @return void + */ + public function removeHeaders(array $remove_headers): void; + + /** + * Update the base url set, if empty will unset the base url + * + * @param string $base_uri + * @return void + */ + public function setBaseUri(string $base_uri): void; + + /** + * combined set call for any type of request with options type parameters + * + * phpcs:disable Generic.Files.LineLength + * @param string $type + * @param string $url + * @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array>,query?:null|array,body?:null|string|array,http_errors?:null|bool} $options + * @return array{code:string,headers:array>,content:string} Result code, headers and content as array, content is json + * @throws \UnexpectedValueException on missing body data when body data is needed + * phpcs:enable Generic.Files.LineLength + */ + public function request(string $type, string $url, array $options = []): array; +} + +// __END__ 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.'); diff --git a/www/media/sqlite/.gitignore b/www/media/sqlite/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/www/media/sqlite/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/www/vendor/autoload.php b/www/vendor/autoload.php deleted file mode 100644 index 74efe898..00000000 --- a/www/vendor/autoload.php +++ /dev/null @@ -1,25 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer\Autoload; - -/** - * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. - * - * $loader = new \Composer\Autoload\ClassLoader(); - * - * // register classes with namespaces - * $loader->add('Symfony\Component', __DIR__.'/component'); - * $loader->add('Symfony', __DIR__.'/framework'); - * - * // activate the autoloader - * $loader->register(); - * - * // to enable searching the include path (eg. for PEAR packages) - * $loader->setUseIncludePath(true); - * - * In this example, if you try to use a class in the Symfony\Component - * namespace or one of its children (Symfony\Component\Console for instance), - * the autoloader will first look for the class under the component/ - * directory, and it will then fallback to the framework/ directory if not - * found before giving up. - * - * This class is loosely based on the Symfony UniversalClassLoader. - * - * @author Fabien Potencier - * @author Jordi Boggiano - * @see https://www.php-fig.org/psr/psr-0/ - * @see https://www.php-fig.org/psr/psr-4/ - */ -class ClassLoader -{ - /** @var \Closure(string):void */ - private static $includeFile; - - /** @var string|null */ - private $vendorDir; - - // PSR-4 - /** - * @var array> - */ - private $prefixLengthsPsr4 = array(); - /** - * @var array> - */ - private $prefixDirsPsr4 = array(); - /** - * @var list - */ - private $fallbackDirsPsr4 = array(); - - // PSR-0 - /** - * List of PSR-0 prefixes - * - * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) - * - * @var array>> - */ - private $prefixesPsr0 = array(); - /** - * @var list - */ - private $fallbackDirsPsr0 = array(); - - /** @var bool */ - private $useIncludePath = false; - - /** - * @var array - */ - private $classMap = array(); - - /** @var bool */ - private $classMapAuthoritative = false; - - /** - * @var array - */ - private $missingClasses = array(); - - /** @var string|null */ - private $apcuPrefix; - - /** - * @var array - */ - private static $registeredLoaders = array(); - - /** - * @param string|null $vendorDir - */ - public function __construct($vendorDir = null) - { - $this->vendorDir = $vendorDir; - self::initializeIncludeClosure(); - } - - /** - * @return array> - */ - public function getPrefixes() - { - if (!empty($this->prefixesPsr0)) { - return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); - } - - return array(); - } - - /** - * @return array> - */ - public function getPrefixesPsr4() - { - return $this->prefixDirsPsr4; - } - - /** - * @return list - */ - public function getFallbackDirs() - { - return $this->fallbackDirsPsr0; - } - - /** - * @return list - */ - public function getFallbackDirsPsr4() - { - return $this->fallbackDirsPsr4; - } - - /** - * @return array Array of classname => path - */ - public function getClassMap() - { - return $this->classMap; - } - - /** - * @param array $classMap Class to filename map - * - * @return void - */ - public function addClassMap(array $classMap) - { - if ($this->classMap) { - $this->classMap = array_merge($this->classMap, $classMap); - } else { - $this->classMap = $classMap; - } - } - - /** - * Registers a set of PSR-0 directories for a given prefix, either - * appending or prepending to the ones previously set for this prefix. - * - * @param string $prefix The prefix - * @param list|string $paths The PSR-0 root directories - * @param bool $prepend Whether to prepend the directories - * - * @return void - */ - public function add($prefix, $paths, $prepend = false) - { - $paths = (array) $paths; - if (!$prefix) { - if ($prepend) { - $this->fallbackDirsPsr0 = array_merge( - $paths, - $this->fallbackDirsPsr0 - ); - } else { - $this->fallbackDirsPsr0 = array_merge( - $this->fallbackDirsPsr0, - $paths - ); - } - - return; - } - - $first = $prefix[0]; - if (!isset($this->prefixesPsr0[$first][$prefix])) { - $this->prefixesPsr0[$first][$prefix] = $paths; - - return; - } - if ($prepend) { - $this->prefixesPsr0[$first][$prefix] = array_merge( - $paths, - $this->prefixesPsr0[$first][$prefix] - ); - } else { - $this->prefixesPsr0[$first][$prefix] = array_merge( - $this->prefixesPsr0[$first][$prefix], - $paths - ); - } - } - - /** - * Registers a set of PSR-4 directories for a given namespace, either - * appending or prepending to the ones previously set for this namespace. - * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param list|string $paths The PSR-4 base directories - * @param bool $prepend Whether to prepend the directories - * - * @throws \InvalidArgumentException - * - * @return void - */ - public function addPsr4($prefix, $paths, $prepend = false) - { - $paths = (array) $paths; - if (!$prefix) { - // Register directories for the root namespace. - if ($prepend) { - $this->fallbackDirsPsr4 = array_merge( - $paths, - $this->fallbackDirsPsr4 - ); - } else { - $this->fallbackDirsPsr4 = array_merge( - $this->fallbackDirsPsr4, - $paths - ); - } - } elseif (!isset($this->prefixDirsPsr4[$prefix])) { - // Register directories for a new namespace. - $length = strlen($prefix); - if ('\\' !== $prefix[$length - 1]) { - throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); - } - $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; - $this->prefixDirsPsr4[$prefix] = $paths; - } elseif ($prepend) { - // Prepend directories for an already registered namespace. - $this->prefixDirsPsr4[$prefix] = array_merge( - $paths, - $this->prefixDirsPsr4[$prefix] - ); - } else { - // Append directories for an already registered namespace. - $this->prefixDirsPsr4[$prefix] = array_merge( - $this->prefixDirsPsr4[$prefix], - $paths - ); - } - } - - /** - * Registers a set of PSR-0 directories for a given prefix, - * replacing any others previously set for this prefix. - * - * @param string $prefix The prefix - * @param list|string $paths The PSR-0 base directories - * - * @return void - */ - public function set($prefix, $paths) - { - if (!$prefix) { - $this->fallbackDirsPsr0 = (array) $paths; - } else { - $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; - } - } - - /** - * Registers a set of PSR-4 directories for a given namespace, - * replacing any others previously set for this namespace. - * - * @param string $prefix The prefix/namespace, with trailing '\\' - * @param list|string $paths The PSR-4 base directories - * - * @throws \InvalidArgumentException - * - * @return void - */ - public function setPsr4($prefix, $paths) - { - if (!$prefix) { - $this->fallbackDirsPsr4 = (array) $paths; - } else { - $length = strlen($prefix); - if ('\\' !== $prefix[$length - 1]) { - throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); - } - $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; - $this->prefixDirsPsr4[$prefix] = (array) $paths; - } - } - - /** - * Turns on searching the include path for class files. - * - * @param bool $useIncludePath - * - * @return void - */ - public function setUseIncludePath($useIncludePath) - { - $this->useIncludePath = $useIncludePath; - } - - /** - * Can be used to check if the autoloader uses the include path to check - * for classes. - * - * @return bool - */ - public function getUseIncludePath() - { - return $this->useIncludePath; - } - - /** - * Turns off searching the prefix and fallback directories for classes - * that have not been registered with the class map. - * - * @param bool $classMapAuthoritative - * - * @return void - */ - public function setClassMapAuthoritative($classMapAuthoritative) - { - $this->classMapAuthoritative = $classMapAuthoritative; - } - - /** - * Should class lookup fail if not found in the current class map? - * - * @return bool - */ - public function isClassMapAuthoritative() - { - return $this->classMapAuthoritative; - } - - /** - * APCu prefix to use to cache found/not-found classes, if the extension is enabled. - * - * @param string|null $apcuPrefix - * - * @return void - */ - public function setApcuPrefix($apcuPrefix) - { - $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; - } - - /** - * The APCu prefix in use, or null if APCu caching is not enabled. - * - * @return string|null - */ - public function getApcuPrefix() - { - return $this->apcuPrefix; - } - - /** - * Registers this instance as an autoloader. - * - * @param bool $prepend Whether to prepend the autoloader or not - * - * @return void - */ - public function register($prepend = false) - { - spl_autoload_register(array($this, 'loadClass'), true, $prepend); - - if (null === $this->vendorDir) { - return; - } - - if ($prepend) { - self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; - } else { - unset(self::$registeredLoaders[$this->vendorDir]); - self::$registeredLoaders[$this->vendorDir] = $this; - } - } - - /** - * Unregisters this instance as an autoloader. - * - * @return void - */ - public function unregister() - { - spl_autoload_unregister(array($this, 'loadClass')); - - if (null !== $this->vendorDir) { - unset(self::$registeredLoaders[$this->vendorDir]); - } - } - - /** - * Loads the given class or interface. - * - * @param string $class The name of the class - * @return true|null True if loaded, null otherwise - */ - public function loadClass($class) - { - if ($file = $this->findFile($class)) { - $includeFile = self::$includeFile; - $includeFile($file); - - return true; - } - - return null; - } - - /** - * Finds the path to the file where the class is defined. - * - * @param string $class The name of the class - * - * @return string|false The path if found, false otherwise - */ - public function findFile($class) - { - // class map lookup - if (isset($this->classMap[$class])) { - return $this->classMap[$class]; - } - if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { - return false; - } - if (null !== $this->apcuPrefix) { - $file = apcu_fetch($this->apcuPrefix.$class, $hit); - if ($hit) { - return $file; - } - } - - $file = $this->findFileWithExtension($class, '.php'); - - // Search for Hack files if we are running on HHVM - if (false === $file && defined('HHVM_VERSION')) { - $file = $this->findFileWithExtension($class, '.hh'); - } - - if (null !== $this->apcuPrefix) { - apcu_add($this->apcuPrefix.$class, $file); - } - - if (false === $file) { - // Remember that this class does not exist. - $this->missingClasses[$class] = true; - } - - return $file; - } - - /** - * Returns the currently registered loaders keyed by their corresponding vendor directories. - * - * @return array - */ - public static function getRegisteredLoaders() - { - return self::$registeredLoaders; - } - - /** - * @param string $class - * @param string $ext - * @return string|false - */ - private function findFileWithExtension($class, $ext) - { - // PSR-4 lookup - $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; - - $first = $class[0]; - if (isset($this->prefixLengthsPsr4[$first])) { - $subPath = $class; - while (false !== $lastPos = strrpos($subPath, '\\')) { - $subPath = substr($subPath, 0, $lastPos); - $search = $subPath . '\\'; - if (isset($this->prefixDirsPsr4[$search])) { - $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); - foreach ($this->prefixDirsPsr4[$search] as $dir) { - if (file_exists($file = $dir . $pathEnd)) { - return $file; - } - } - } - } - } - - // PSR-4 fallback dirs - foreach ($this->fallbackDirsPsr4 as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { - return $file; - } - } - - // PSR-0 lookup - if (false !== $pos = strrpos($class, '\\')) { - // namespaced class name - $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) - . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); - } else { - // PEAR-like class name - $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; - } - - if (isset($this->prefixesPsr0[$first])) { - foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { - if (0 === strpos($class, $prefix)) { - foreach ($dirs as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { - return $file; - } - } - } - } - } - - // PSR-0 fallback dirs - foreach ($this->fallbackDirsPsr0 as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { - return $file; - } - } - - // PSR-0 include paths. - if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { - return $file; - } - - return false; - } - - /** - * @return void - */ - private static function initializeIncludeClosure() - { - if (self::$includeFile !== null) { - return; - } - - /** - * Scope isolated include. - * - * Prevents access to $this/self from included files. - * - * @param string $file - * @return void - */ - self::$includeFile = \Closure::bind(static function($file) { - include $file; - }, null, null); - } -} diff --git a/www/vendor/composer/InstalledVersions.php b/www/vendor/composer/InstalledVersions.php deleted file mode 100644 index 51e734a7..00000000 --- a/www/vendor/composer/InstalledVersions.php +++ /dev/null @@ -1,359 +0,0 @@ - - * Jordi Boggiano - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Composer; - -use Composer\Autoload\ClassLoader; -use Composer\Semver\VersionParser; - -/** - * This class is copied in every Composer installed project and available to all - * - * See also https://getcomposer.org/doc/07-runtime.md#installed-versions - * - * To require its presence, you can require `composer-runtime-api ^2.0` - * - * @final - */ -class InstalledVersions -{ - /** - * @var mixed[]|null - * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null - */ - private static $installed; - - /** - * @var bool|null - */ - private static $canGetVendors; - - /** - * @var array[] - * @psalm-var array}> - */ - private static $installedByVendor = array(); - - /** - * Returns a list of all package names which are present, either by being installed, replaced or provided - * - * @return string[] - * @psalm-return list - */ - public static function getInstalledPackages() - { - $packages = array(); - foreach (self::getInstalled() as $installed) { - $packages[] = array_keys($installed['versions']); - } - - if (1 === \count($packages)) { - return $packages[0]; - } - - return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); - } - - /** - * Returns a list of all package names with a specific type e.g. 'library' - * - * @param string $type - * @return string[] - * @psalm-return list - */ - public static function getInstalledPackagesByType($type) - { - $packagesByType = array(); - - foreach (self::getInstalled() as $installed) { - foreach ($installed['versions'] as $name => $package) { - if (isset($package['type']) && $package['type'] === $type) { - $packagesByType[] = $name; - } - } - } - - return $packagesByType; - } - - /** - * Checks whether the given package is installed - * - * This also returns true if the package name is provided or replaced by another package - * - * @param string $packageName - * @param bool $includeDevRequirements - * @return bool - */ - public static function isInstalled($packageName, $includeDevRequirements = true) - { - foreach (self::getInstalled() as $installed) { - if (isset($installed['versions'][$packageName])) { - return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; - } - } - - return false; - } - - /** - * Checks whether the given package satisfies a version constraint - * - * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: - * - * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') - * - * @param VersionParser $parser Install composer/semver to have access to this class and functionality - * @param string $packageName - * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package - * @return bool - */ - public static function satisfies(VersionParser $parser, $packageName, $constraint) - { - $constraint = $parser->parseConstraints((string) $constraint); - $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); - - return $provided->matches($constraint); - } - - /** - * Returns a version constraint representing all the range(s) which are installed for a given package - * - * It is easier to use this via isInstalled() with the $constraint argument if you need to check - * whether a given version of a package is installed, and not just whether it exists - * - * @param string $packageName - * @return string Version constraint usable with composer/semver - */ - public static function getVersionRanges($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - $ranges = array(); - if (isset($installed['versions'][$packageName]['pretty_version'])) { - $ranges[] = $installed['versions'][$packageName]['pretty_version']; - } - if (array_key_exists('aliases', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); - } - if (array_key_exists('replaced', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); - } - if (array_key_exists('provided', $installed['versions'][$packageName])) { - $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); - } - - return implode(' || ', $ranges); - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present - */ - public static function getVersion($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['version'])) { - return null; - } - - return $installed['versions'][$packageName]['version']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present - */ - public static function getPrettyVersion($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['pretty_version'])) { - return null; - } - - return $installed['versions'][$packageName]['pretty_version']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference - */ - public static function getReference($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - if (!isset($installed['versions'][$packageName]['reference'])) { - return null; - } - - return $installed['versions'][$packageName]['reference']; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @param string $packageName - * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. - */ - public static function getInstallPath($packageName) - { - foreach (self::getInstalled() as $installed) { - if (!isset($installed['versions'][$packageName])) { - continue; - } - - return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; - } - - throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); - } - - /** - * @return array - * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} - */ - public static function getRootPackage() - { - $installed = self::getInstalled(); - - return $installed[0]['root']; - } - - /** - * Returns the raw installed.php data for custom implementations - * - * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. - * @return array[] - * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} - */ - public static function getRawData() - { - @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); - - if (null === self::$installed) { - // only require the installed.php file if this file is loaded from its dumped location, - // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 - if (substr(__DIR__, -8, 1) !== 'C') { - self::$installed = include __DIR__ . '/installed.php'; - } else { - self::$installed = array(); - } - } - - return self::$installed; - } - - /** - * Returns the raw data of all installed.php which are currently loaded for custom implementations - * - * @return array[] - * @psalm-return list}> - */ - public static function getAllRawData() - { - return self::getInstalled(); - } - - /** - * Lets you reload the static array from another file - * - * This is only useful for complex integrations in which a project needs to use - * this class but then also needs to execute another project's autoloader in process, - * and wants to ensure both projects have access to their version of installed.php. - * - * A typical case would be PHPUnit, where it would need to make sure it reads all - * the data it needs from this class, then call reload() with - * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure - * the project in which it runs can then also use this class safely, without - * interference between PHPUnit's dependencies and the project's dependencies. - * - * @param array[] $data A vendor/composer/installed.php data set - * @return void - * - * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data - */ - public static function reload($data) - { - self::$installed = $data; - self::$installedByVendor = array(); - } - - /** - * @return array[] - * @psalm-return list}> - */ - private static function getInstalled() - { - if (null === self::$canGetVendors) { - self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); - } - - $installed = array(); - - if (self::$canGetVendors) { - foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { - if (isset(self::$installedByVendor[$vendorDir])) { - $installed[] = self::$installedByVendor[$vendorDir]; - } elseif (is_file($vendorDir.'/composer/installed.php')) { - /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ - $required = require $vendorDir.'/composer/installed.php'; - $installed[] = self::$installedByVendor[$vendorDir] = $required; - if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { - self::$installed = $installed[count($installed) - 1]; - } - } - } - } - - if (null === self::$installed) { - // only require the installed.php file if this file is loaded from its dumped location, - // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 - if (substr(__DIR__, -8, 1) !== 'C') { - /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ - $required = require __DIR__ . '/installed.php'; - self::$installed = $required; - } else { - self::$installed = array(); - } - } - - if (self::$installed !== array()) { - $installed[] = self::$installed; - } - - return $installed; - } -} diff --git a/www/vendor/composer/LICENSE b/www/vendor/composer/LICENSE deleted file mode 100644 index f27399a0..00000000 --- a/www/vendor/composer/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - -Copyright (c) Nils Adermann, Jordi Boggiano - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - diff --git a/www/vendor/composer/autoload_classmap.php b/www/vendor/composer/autoload_classmap.php deleted file mode 100644 index 4de0d9cc..00000000 --- a/www/vendor/composer/autoload_classmap.php +++ /dev/null @@ -1,266 +0,0 @@ - $baseDir . '/lib/autoloader.php', - 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', - 'CoreLibs\\ACL\\Login' => $baseDir . '/lib/CoreLibs/ACL/Login.php', - 'CoreLibs\\Admin\\Backend' => $baseDir . '/lib/CoreLibs/Admin/Backend.php', - 'CoreLibs\\Admin\\EditBase' => $baseDir . '/lib/CoreLibs/Admin/EditBase.php', - 'CoreLibs\\Basic' => $baseDir . '/lib/CoreLibs/Basic.php', - 'CoreLibs\\Check\\Colors' => $baseDir . '/lib/CoreLibs/Check/Colors.php', - 'CoreLibs\\Check\\Email' => $baseDir . '/lib/CoreLibs/Check/Email.php', - 'CoreLibs\\Check\\Encoding' => $baseDir . '/lib/CoreLibs/Check/Encoding.php', - 'CoreLibs\\Check\\File' => $baseDir . '/lib/CoreLibs/Check/File.php', - 'CoreLibs\\Check\\Password' => $baseDir . '/lib/CoreLibs/Check/Password.php', - 'CoreLibs\\Check\\PhpVersion' => $baseDir . '/lib/CoreLibs/Check/PhpVersion.php', - 'CoreLibs\\Combined\\ArrayHandler' => $baseDir . '/lib/CoreLibs/Combined/ArrayHandler.php', - 'CoreLibs\\Combined\\DateTime' => $baseDir . '/lib/CoreLibs/Combined/DateTime.php', - 'CoreLibs\\Convert\\Byte' => $baseDir . '/lib/CoreLibs/Convert/Byte.php', - 'CoreLibs\\Convert\\Colors' => $baseDir . '/lib/CoreLibs/Convert/Colors.php', - 'CoreLibs\\Convert\\Encoding' => $baseDir . '/lib/CoreLibs/Convert/Encoding.php', - 'CoreLibs\\Convert\\Extends\\SetVarTypeMain' => $baseDir . '/lib/CoreLibs/Convert/Extends/SetVarTypeMain.php', - 'CoreLibs\\Convert\\Html' => $baseDir . '/lib/CoreLibs/Convert/Html.php', - 'CoreLibs\\Convert\\Json' => $baseDir . '/lib/CoreLibs/Convert/Json.php', - 'CoreLibs\\Convert\\Math' => $baseDir . '/lib/CoreLibs/Convert/Math.php', - 'CoreLibs\\Convert\\MimeAppName' => $baseDir . '/lib/CoreLibs/Convert/MimeAppName.php', - 'CoreLibs\\Convert\\MimeEncode' => $baseDir . '/lib/CoreLibs/Convert/MimeEncode.php', - 'CoreLibs\\Convert\\SetVarType' => $baseDir . '/lib/CoreLibs/Convert/SetVarType.php', - 'CoreLibs\\Convert\\SetVarTypeNull' => $baseDir . '/lib/CoreLibs/Convert/SetVarTypeNull.php', - 'CoreLibs\\Convert\\Strings' => $baseDir . '/lib/CoreLibs/Convert/Strings.php', - 'CoreLibs\\Create\\Email' => $baseDir . '/lib/CoreLibs/Create/Email.php', - 'CoreLibs\\Create\\Hash' => $baseDir . '/lib/CoreLibs/Create/Hash.php', - 'CoreLibs\\Create\\RandomKey' => $baseDir . '/lib/CoreLibs/Create/RandomKey.php', - 'CoreLibs\\Create\\Session' => $baseDir . '/lib/CoreLibs/Create/Session.php', - 'CoreLibs\\Create\\Uids' => $baseDir . '/lib/CoreLibs/Create/Uids.php', - 'CoreLibs\\DB\\Extended\\ArrayIO' => $baseDir . '/lib/CoreLibs/DB/Extended/ArrayIO.php', - 'CoreLibs\\DB\\IO' => $baseDir . '/lib/CoreLibs/DB/IO.php', - 'CoreLibs\\DB\\Options\\Convert' => $baseDir . '/lib/CoreLibs/DB/Options/Convert.php', - 'CoreLibs\\DB\\SQL\\Interface\\SqlFunctions' => $baseDir . '/lib/CoreLibs/DB/SQL/Interface/SqlFunctions.php', - 'CoreLibs\\DB\\SQL\\PgSQL' => $baseDir . '/lib/CoreLibs/DB/SQL/PgSQL.php', - 'CoreLibs\\DB\\Support\\ConvertPlaceholder' => $baseDir . '/lib/CoreLibs/DB/Support/ConvertPlaceholder.php', - 'CoreLibs\\Debug\\FileWriter' => $baseDir . '/lib/CoreLibs/Debug/FileWriter.php', - 'CoreLibs\\Debug\\Logging' => $baseDir . '/lib/CoreLibs/Debug/Logging.php', - 'CoreLibs\\Debug\\LoggingLegacy' => $baseDir . '/lib/CoreLibs/Debug/LoggingLegacy.php', - 'CoreLibs\\Debug\\MemoryUsage' => $baseDir . '/lib/CoreLibs/Debug/MemoryUsage.php', - 'CoreLibs\\Debug\\RunningTime' => $baseDir . '/lib/CoreLibs/Debug/RunningTime.php', - 'CoreLibs\\Debug\\Support' => $baseDir . '/lib/CoreLibs/Debug/Support.php', - 'CoreLibs\\Get\\DotEnv' => $baseDir . '/lib/CoreLibs/Get/DotEnv.php', - 'CoreLibs\\Get\\System' => $baseDir . '/lib/CoreLibs/Get/System.php', - 'CoreLibs\\Language\\Core\\CachedFileReader' => $baseDir . '/lib/CoreLibs/Language/Core/CachedFileReader.php', - 'CoreLibs\\Language\\Core\\FileReader' => $baseDir . '/lib/CoreLibs/Language/Core/FileReader.php', - 'CoreLibs\\Language\\Core\\GetTextReader' => $baseDir . '/lib/CoreLibs/Language/Core/GetTextReader.php', - 'CoreLibs\\Language\\Core\\StreamReader' => $baseDir . '/lib/CoreLibs/Language/Core/StreamReader.php', - 'CoreLibs\\Language\\Core\\StringReader' => $baseDir . '/lib/CoreLibs/Language/Core/StringReader.php', - 'CoreLibs\\Language\\GetLocale' => $baseDir . '/lib/CoreLibs/Language/GetLocale.php', - 'CoreLibs\\Language\\L10n' => $baseDir . '/lib/CoreLibs/Language/L10n.php', - 'CoreLibs\\Logging\\ErrorMessage' => $baseDir . '/lib/CoreLibs/Logging/ErrorMessage.php', - 'CoreLibs\\Logging\\Logger\\Flag' => $baseDir . '/lib/CoreLibs/Logging/Logger/Flag.php', - 'CoreLibs\\Logging\\Logger\\Level' => $baseDir . '/lib/CoreLibs/Logging/Logger/Level.php', - 'CoreLibs\\Logging\\Logger\\MessageLevel' => $baseDir . '/lib/CoreLibs/Logging/Logger/MessageLevel.php', - 'CoreLibs\\Logging\\Logging' => $baseDir . '/lib/CoreLibs/Logging/Logging.php', - 'CoreLibs\\Output\\Form\\Elements' => $baseDir . '/lib/CoreLibs/Output/Form/Elements.php', - 'CoreLibs\\Output\\Form\\Generate' => $baseDir . '/lib/CoreLibs/Output/Form/Generate.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditAccess' => $baseDir . '/lib/CoreLibs/Output/Form/TableArrays/EditAccess.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditGroups' => $baseDir . '/lib/CoreLibs/Output/Form/TableArrays/EditGroups.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditLanguages' => $baseDir . '/lib/CoreLibs/Output/Form/TableArrays/EditLanguages.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditMenuGroup' => $baseDir . '/lib/CoreLibs/Output/Form/TableArrays/EditMenuGroup.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditOrder' => $baseDir . '/lib/CoreLibs/Output/Form/TableArrays/EditOrder.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditPages' => $baseDir . '/lib/CoreLibs/Output/Form/TableArrays/EditPages.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditSchemas' => $baseDir . '/lib/CoreLibs/Output/Form/TableArrays/EditSchemas.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditUsers' => $baseDir . '/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditVisibleGroup' => $baseDir . '/lib/CoreLibs/Output/Form/TableArrays/EditVisibleGroup.php', - 'CoreLibs\\Output\\Form\\TableArrays\\Interface\\TableArraysInterface' => $baseDir . '/lib/CoreLibs/Output/Form/TableArrays/Interface/TableArraysInterface.php', - 'CoreLibs\\Output\\Form\\Token' => $baseDir . '/lib/CoreLibs/Output/Form/Token.php', - 'CoreLibs\\Output\\Image' => $baseDir . '/lib/CoreLibs/Output/Image.php', - 'CoreLibs\\Output\\ProgressBar' => $baseDir . '/lib/CoreLibs/Output/ProgressBar.php', - 'CoreLibs\\Security\\CreateKey' => $baseDir . '/lib/CoreLibs/Security/CreateKey.php', - 'CoreLibs\\Security\\Password' => $baseDir . '/lib/CoreLibs/Security/Password.php', - 'CoreLibs\\Security\\SymmetricEncryption' => $baseDir . '/lib/CoreLibs/Security/SymmetricEncryption.php', - 'CoreLibs\\Template\\HtmlBuilder\\Block' => $baseDir . '/lib/CoreLibs/Template/HtmlBuilder/Block.php', - 'CoreLibs\\Template\\HtmlBuilder\\Element' => $baseDir . '/lib/CoreLibs/Template/HtmlBuilder/Element.php', - 'CoreLibs\\Template\\HtmlBuilder\\General\\Error' => $baseDir . '/lib/CoreLibs/Template/HtmlBuilder/General/Error.php', - 'CoreLibs\\Template\\HtmlBuilder\\General\\HtmlBuilderExcpetion' => $baseDir . '/lib/CoreLibs/Template/HtmlBuilder/General/HtmlBuilderExcpetion.php', - 'CoreLibs\\Template\\HtmlBuilder\\General\\Settings' => $baseDir . '/lib/CoreLibs/Template/HtmlBuilder/General/Settings.php', - 'CoreLibs\\Template\\HtmlBuilder\\StringReplace' => $baseDir . '/lib/CoreLibs/Template/HtmlBuilder/StringReplace.php', - 'CoreLibs\\Template\\SmartyExtend' => $baseDir . '/lib/CoreLibs/Template/SmartyExtend.php', - 'FileUpload\\Core\\qqUploadedFile' => $baseDir . '/lib/FileUpload/Core/qqUploadedFile.php', - 'FileUpload\\Core\\qqUploadedFileForm' => $baseDir . '/lib/FileUpload/Core/qqUploadedFileForm.php', - 'FileUpload\\Core\\qqUploadedFileXhr' => $baseDir . '/lib/FileUpload/Core/qqUploadedFileXhr.php', - 'FileUpload\\qqFileUploader' => $baseDir . '/lib/FileUpload/qqFileUploader.php', - 'Smarty' => $vendorDir . '/egrajp/smarty-extended/src/Smarty.class.php', - 'SmartyCompilerException' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smartycompilerexception.php', - 'SmartyException' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smartyexception.php', - 'Smarty_Autoloader' => $vendorDir . '/egrajp/smarty-extended/src/Autoloader.php', - 'Smarty_CacheResource' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_cacheresource.php', - 'Smarty_CacheResource_Custom' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_cacheresource_custom.php', - 'Smarty_CacheResource_KeyValueStore' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_cacheresource_keyvaluestore.php', - 'Smarty_Data' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_data.php', - 'Smarty_Internal_Block' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_block.php', - 'Smarty_Internal_CacheResource_File' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_cacheresource_file.php', - 'Smarty_Internal_CompileBase' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compilebase.php', - 'Smarty_Internal_Compile_Append' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_append.php', - 'Smarty_Internal_Compile_Assign' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_assign.php', - 'Smarty_Internal_Compile_Block' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_block.php', - 'Smarty_Internal_Compile_Block_Child' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_block_child.php', - 'Smarty_Internal_Compile_Block_Parent' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_block_parent.php', - 'Smarty_Internal_Compile_Blockclose' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_block.php', - 'Smarty_Internal_Compile_Break' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_break.php', - 'Smarty_Internal_Compile_Call' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_call.php', - 'Smarty_Internal_Compile_Capture' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_capture.php', - 'Smarty_Internal_Compile_CaptureClose' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_capture.php', - 'Smarty_Internal_Compile_Child' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_child.php', - 'Smarty_Internal_Compile_Config_Load' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_config_load.php', - 'Smarty_Internal_Compile_Continue' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_continue.php', - 'Smarty_Internal_Compile_Debug' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_debug.php', - 'Smarty_Internal_Compile_Else' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_if.php', - 'Smarty_Internal_Compile_Elseif' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_if.php', - 'Smarty_Internal_Compile_Eval' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_eval.php', - 'Smarty_Internal_Compile_Extends' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_extends.php', - 'Smarty_Internal_Compile_For' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_for.php', - 'Smarty_Internal_Compile_Forclose' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_for.php', - 'Smarty_Internal_Compile_Foreach' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_foreach.php', - 'Smarty_Internal_Compile_Foreachclose' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_foreach.php', - 'Smarty_Internal_Compile_Foreachelse' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_foreach.php', - 'Smarty_Internal_Compile_Forelse' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_for.php', - 'Smarty_Internal_Compile_Function' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_function.php', - 'Smarty_Internal_Compile_Functionclose' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_function.php', - 'Smarty_Internal_Compile_If' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_if.php', - 'Smarty_Internal_Compile_Ifclose' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_if.php', - 'Smarty_Internal_Compile_Include' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_include.php', - 'Smarty_Internal_Compile_Insert' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_insert.php', - 'Smarty_Internal_Compile_Ldelim' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_ldelim.php', - 'Smarty_Internal_Compile_Make_Nocache' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_make_nocache.php', - 'Smarty_Internal_Compile_Nocache' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_nocache.php', - 'Smarty_Internal_Compile_Nocacheclose' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_nocache.php', - 'Smarty_Internal_Compile_Parent' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_parent.php', - 'Smarty_Internal_Compile_Private_Block_Plugin' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_block_plugin.php', - 'Smarty_Internal_Compile_Private_ForeachSection' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_foreachsection.php', - 'Smarty_Internal_Compile_Private_Function_Plugin' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_function_plugin.php', - 'Smarty_Internal_Compile_Private_Modifier' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_modifier.php', - 'Smarty_Internal_Compile_Private_Object_Block_Function' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_object_block_function.php', - 'Smarty_Internal_Compile_Private_Object_Function' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_object_function.php', - 'Smarty_Internal_Compile_Private_Print_Expression' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_print_expression.php', - 'Smarty_Internal_Compile_Private_Registered_Block' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_registered_block.php', - 'Smarty_Internal_Compile_Private_Registered_Function' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_registered_function.php', - 'Smarty_Internal_Compile_Private_Special_Variable' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_special_variable.php', - 'Smarty_Internal_Compile_Rdelim' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_rdelim.php', - 'Smarty_Internal_Compile_Section' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_section.php', - 'Smarty_Internal_Compile_Sectionclose' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_section.php', - 'Smarty_Internal_Compile_Sectionelse' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_section.php', - 'Smarty_Internal_Compile_Setfilter' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_setfilter.php', - 'Smarty_Internal_Compile_Setfilterclose' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_setfilter.php', - 'Smarty_Internal_Compile_Shared_Inheritance' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_shared_inheritance.php', - 'Smarty_Internal_Compile_While' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_while.php', - 'Smarty_Internal_Compile_Whileclose' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_while.php', - 'Smarty_Internal_Config_File_Compiler' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_config_file_compiler.php', - 'Smarty_Internal_Configfilelexer' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_configfilelexer.php', - 'Smarty_Internal_Configfileparser' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_configfileparser.php', - 'Smarty_Internal_Data' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_data.php', - 'Smarty_Internal_Debug' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_debug.php', - 'Smarty_Internal_ErrorHandler' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_errorhandler.php', - 'Smarty_Internal_Extension_Handler' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_extension_handler.php', - 'Smarty_Internal_Method_AddAutoloadFilters' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_addautoloadfilters.php', - 'Smarty_Internal_Method_AddDefaultModifiers' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_adddefaultmodifiers.php', - 'Smarty_Internal_Method_Append' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_append.php', - 'Smarty_Internal_Method_AppendByRef' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_appendbyref.php', - 'Smarty_Internal_Method_AssignByRef' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_assignbyref.php', - 'Smarty_Internal_Method_AssignGlobal' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_assignglobal.php', - 'Smarty_Internal_Method_ClearAllAssign' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_clearallassign.php', - 'Smarty_Internal_Method_ClearAllCache' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_clearallcache.php', - 'Smarty_Internal_Method_ClearAssign' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_clearassign.php', - 'Smarty_Internal_Method_ClearCache' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_clearcache.php', - 'Smarty_Internal_Method_ClearCompiledTemplate' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_clearcompiledtemplate.php', - 'Smarty_Internal_Method_ClearConfig' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_clearconfig.php', - 'Smarty_Internal_Method_CompileAllConfig' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_compileallconfig.php', - 'Smarty_Internal_Method_CompileAllTemplates' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_compilealltemplates.php', - 'Smarty_Internal_Method_ConfigLoad' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_configload.php', - 'Smarty_Internal_Method_CreateData' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_createdata.php', - 'Smarty_Internal_Method_GetAutoloadFilters' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getautoloadfilters.php', - 'Smarty_Internal_Method_GetConfigVariable' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getconfigvariable.php', - 'Smarty_Internal_Method_GetConfigVars' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getconfigvars.php', - 'Smarty_Internal_Method_GetDebugTemplate' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getdebugtemplate.php', - 'Smarty_Internal_Method_GetDefaultModifiers' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getdefaultmodifiers.php', - 'Smarty_Internal_Method_GetGlobal' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getglobal.php', - 'Smarty_Internal_Method_GetRegisteredObject' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getregisteredobject.php', - 'Smarty_Internal_Method_GetStreamVariable' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getstreamvariable.php', - 'Smarty_Internal_Method_GetTags' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_gettags.php', - 'Smarty_Internal_Method_GetTemplateVars' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_gettemplatevars.php', - 'Smarty_Internal_Method_Literals' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_literals.php', - 'Smarty_Internal_Method_LoadFilter' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_loadfilter.php', - 'Smarty_Internal_Method_LoadPlugin' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_loadplugin.php', - 'Smarty_Internal_Method_MustCompile' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_mustcompile.php', - 'Smarty_Internal_Method_RegisterCacheResource' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registercacheresource.php', - 'Smarty_Internal_Method_RegisterClass' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerclass.php', - 'Smarty_Internal_Method_RegisterDefaultConfigHandler' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerdefaultconfighandler.php', - 'Smarty_Internal_Method_RegisterDefaultPluginHandler' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerdefaultpluginhandler.php', - 'Smarty_Internal_Method_RegisterDefaultTemplateHandler' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerdefaulttemplatehandler.php', - 'Smarty_Internal_Method_RegisterFilter' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerfilter.php', - 'Smarty_Internal_Method_RegisterObject' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerobject.php', - 'Smarty_Internal_Method_RegisterPlugin' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerplugin.php', - 'Smarty_Internal_Method_RegisterResource' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerresource.php', - 'Smarty_Internal_Method_SetAutoloadFilters' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_setautoloadfilters.php', - 'Smarty_Internal_Method_SetDebugTemplate' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_setdebugtemplate.php', - 'Smarty_Internal_Method_SetDefaultModifiers' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_setdefaultmodifiers.php', - 'Smarty_Internal_Method_UnloadFilter' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_unloadfilter.php', - 'Smarty_Internal_Method_UnregisterCacheResource' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_unregistercacheresource.php', - 'Smarty_Internal_Method_UnregisterFilter' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_unregisterfilter.php', - 'Smarty_Internal_Method_UnregisterObject' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_unregisterobject.php', - 'Smarty_Internal_Method_UnregisterPlugin' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_unregisterplugin.php', - 'Smarty_Internal_Method_UnregisterResource' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_unregisterresource.php', - 'Smarty_Internal_Nocache_Insert' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_nocache_insert.php', - 'Smarty_Internal_ParseTree' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_parsetree.php', - 'Smarty_Internal_ParseTree_Code' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_parsetree_code.php', - 'Smarty_Internal_ParseTree_Dq' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_parsetree_dq.php', - 'Smarty_Internal_ParseTree_DqContent' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_parsetree_dqcontent.php', - 'Smarty_Internal_ParseTree_Tag' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_parsetree_tag.php', - 'Smarty_Internal_ParseTree_Template' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_parsetree_template.php', - 'Smarty_Internal_ParseTree_Text' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_parsetree_text.php', - 'Smarty_Internal_Resource_Eval' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_resource_eval.php', - 'Smarty_Internal_Resource_Extends' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_resource_extends.php', - 'Smarty_Internal_Resource_File' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_resource_file.php', - 'Smarty_Internal_Resource_Php' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_resource_php.php', - 'Smarty_Internal_Resource_Stream' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_resource_stream.php', - 'Smarty_Internal_Resource_String' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_resource_string.php', - 'Smarty_Internal_Runtime_CacheModify' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_cachemodify.php', - 'Smarty_Internal_Runtime_CacheResourceFile' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_cacheresourcefile.php', - 'Smarty_Internal_Runtime_Capture' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_capture.php', - 'Smarty_Internal_Runtime_CodeFrame' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_codeframe.php', - 'Smarty_Internal_Runtime_FilterHandler' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_filterhandler.php', - 'Smarty_Internal_Runtime_Foreach' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_foreach.php', - 'Smarty_Internal_Runtime_GetIncludePath' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_getincludepath.php', - 'Smarty_Internal_Runtime_Inheritance' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_inheritance.php', - 'Smarty_Internal_Runtime_Make_Nocache' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_make_nocache.php', - 'Smarty_Internal_Runtime_TplFunction' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_tplfunction.php', - 'Smarty_Internal_Runtime_UpdateCache' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_updatecache.php', - 'Smarty_Internal_Runtime_UpdateScope' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_updatescope.php', - 'Smarty_Internal_Runtime_WriteFile' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_writefile.php', - 'Smarty_Internal_SmartyTemplateCompiler' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_smartytemplatecompiler.php', - 'Smarty_Internal_Template' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_template.php', - 'Smarty_Internal_TemplateBase' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_templatebase.php', - 'Smarty_Internal_TemplateCompilerBase' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_templatecompilerbase.php', - 'Smarty_Internal_Templatelexer' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_templatelexer.php', - 'Smarty_Internal_Templateparser' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_templateparser.php', - 'Smarty_Internal_TestInstall' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_testinstall.php', - 'Smarty_Internal_Undefined' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_undefined.php', - 'Smarty_Resource' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_resource.php', - 'Smarty_Resource_Custom' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_resource_custom.php', - 'Smarty_Resource_Recompiled' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_resource_recompiled.php', - 'Smarty_Resource_Uncompiled' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_resource_uncompiled.php', - 'Smarty_Security' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_security.php', - 'Smarty_Template_Cached' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_template_cached.php', - 'Smarty_Template_Compiled' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_template_compiled.php', - 'Smarty_Template_Config' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_template_config.php', - 'Smarty_Template_Resource_Base' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_template_resource_base.php', - 'Smarty_Template_Source' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_template_source.php', - 'Smarty_Undefined_Variable' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_undefined_variable.php', - 'Smarty_Variable' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_variable.php', - 'TPC_yyStackEntry' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_configfileparser.php', - 'TP_yyStackEntry' => $vendorDir . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_templateparser.php', - 'TestCalls\\DB\\TestDB' => $baseDir . '/lib/TestCalls/DB/TestDB.php', - 'TestCalls\\Test' => $baseDir . '/lib/TestCalls/Test.php', -); diff --git a/www/vendor/composer/autoload_namespaces.php b/www/vendor/composer/autoload_namespaces.php deleted file mode 100644 index 15a2ff3a..00000000 --- a/www/vendor/composer/autoload_namespaces.php +++ /dev/null @@ -1,9 +0,0 @@ - array($vendorDir . '/gullevek/dotenv/src'), - 'gullevek\\dotEnv\\' => array($vendorDir . '/gullevek/dotenv/src'), - 'Psr\\Log\\' => array($vendorDir . '/psr/log/src'), -); diff --git a/www/vendor/composer/autoload_real.php b/www/vendor/composer/autoload_real.php deleted file mode 100644 index 9047eb7d..00000000 --- a/www/vendor/composer/autoload_real.php +++ /dev/null @@ -1,38 +0,0 @@ -register(true); - - return $loader; - } -} diff --git a/www/vendor/composer/autoload_static.php b/www/vendor/composer/autoload_static.php deleted file mode 100644 index 01b288b7..00000000 --- a/www/vendor/composer/autoload_static.php +++ /dev/null @@ -1,305 +0,0 @@ - - array ( - 'gullevek\\dotenv\\' => 16, - 'gullevek\\dotEnv\\' => 16, - ), - 'P' => - array ( - 'Psr\\Log\\' => 8, - ), - ); - - public static $prefixDirsPsr4 = array ( - 'gullevek\\dotenv\\' => - array ( - 0 => __DIR__ . '/..' . '/gullevek/dotenv/src', - ), - 'gullevek\\dotEnv\\' => - array ( - 0 => __DIR__ . '/..' . '/gullevek/dotenv/src', - ), - 'Psr\\Log\\' => - array ( - 0 => __DIR__ . '/..' . '/psr/log/src', - ), - ); - - public static $classMap = array ( - 'Autoloader\\Autoload' => __DIR__ . '/../..' . '/lib/autoloader.php', - 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', - 'CoreLibs\\ACL\\Login' => __DIR__ . '/../..' . '/lib/CoreLibs/ACL/Login.php', - 'CoreLibs\\Admin\\Backend' => __DIR__ . '/../..' . '/lib/CoreLibs/Admin/Backend.php', - 'CoreLibs\\Admin\\EditBase' => __DIR__ . '/../..' . '/lib/CoreLibs/Admin/EditBase.php', - 'CoreLibs\\Basic' => __DIR__ . '/../..' . '/lib/CoreLibs/Basic.php', - 'CoreLibs\\Check\\Colors' => __DIR__ . '/../..' . '/lib/CoreLibs/Check/Colors.php', - 'CoreLibs\\Check\\Email' => __DIR__ . '/../..' . '/lib/CoreLibs/Check/Email.php', - 'CoreLibs\\Check\\Encoding' => __DIR__ . '/../..' . '/lib/CoreLibs/Check/Encoding.php', - 'CoreLibs\\Check\\File' => __DIR__ . '/../..' . '/lib/CoreLibs/Check/File.php', - 'CoreLibs\\Check\\Password' => __DIR__ . '/../..' . '/lib/CoreLibs/Check/Password.php', - 'CoreLibs\\Check\\PhpVersion' => __DIR__ . '/../..' . '/lib/CoreLibs/Check/PhpVersion.php', - 'CoreLibs\\Combined\\ArrayHandler' => __DIR__ . '/../..' . '/lib/CoreLibs/Combined/ArrayHandler.php', - 'CoreLibs\\Combined\\DateTime' => __DIR__ . '/../..' . '/lib/CoreLibs/Combined/DateTime.php', - 'CoreLibs\\Convert\\Byte' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/Byte.php', - 'CoreLibs\\Convert\\Colors' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/Colors.php', - 'CoreLibs\\Convert\\Encoding' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/Encoding.php', - 'CoreLibs\\Convert\\Extends\\SetVarTypeMain' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/Extends/SetVarTypeMain.php', - 'CoreLibs\\Convert\\Html' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/Html.php', - 'CoreLibs\\Convert\\Json' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/Json.php', - 'CoreLibs\\Convert\\Math' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/Math.php', - 'CoreLibs\\Convert\\MimeAppName' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/MimeAppName.php', - 'CoreLibs\\Convert\\MimeEncode' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/MimeEncode.php', - 'CoreLibs\\Convert\\SetVarType' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/SetVarType.php', - 'CoreLibs\\Convert\\SetVarTypeNull' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/SetVarTypeNull.php', - 'CoreLibs\\Convert\\Strings' => __DIR__ . '/../..' . '/lib/CoreLibs/Convert/Strings.php', - 'CoreLibs\\Create\\Email' => __DIR__ . '/../..' . '/lib/CoreLibs/Create/Email.php', - 'CoreLibs\\Create\\Hash' => __DIR__ . '/../..' . '/lib/CoreLibs/Create/Hash.php', - 'CoreLibs\\Create\\RandomKey' => __DIR__ . '/../..' . '/lib/CoreLibs/Create/RandomKey.php', - 'CoreLibs\\Create\\Session' => __DIR__ . '/../..' . '/lib/CoreLibs/Create/Session.php', - 'CoreLibs\\Create\\Uids' => __DIR__ . '/../..' . '/lib/CoreLibs/Create/Uids.php', - 'CoreLibs\\DB\\Extended\\ArrayIO' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/Extended/ArrayIO.php', - 'CoreLibs\\DB\\IO' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/IO.php', - 'CoreLibs\\DB\\Options\\Convert' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/Options/Convert.php', - 'CoreLibs\\DB\\SQL\\Interface\\SqlFunctions' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/SQL/Interface/SqlFunctions.php', - 'CoreLibs\\DB\\SQL\\PgSQL' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/SQL/PgSQL.php', - 'CoreLibs\\DB\\Support\\ConvertPlaceholder' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/Support/ConvertPlaceholder.php', - 'CoreLibs\\Debug\\FileWriter' => __DIR__ . '/../..' . '/lib/CoreLibs/Debug/FileWriter.php', - 'CoreLibs\\Debug\\Logging' => __DIR__ . '/../..' . '/lib/CoreLibs/Debug/Logging.php', - 'CoreLibs\\Debug\\LoggingLegacy' => __DIR__ . '/../..' . '/lib/CoreLibs/Debug/LoggingLegacy.php', - 'CoreLibs\\Debug\\MemoryUsage' => __DIR__ . '/../..' . '/lib/CoreLibs/Debug/MemoryUsage.php', - 'CoreLibs\\Debug\\RunningTime' => __DIR__ . '/../..' . '/lib/CoreLibs/Debug/RunningTime.php', - 'CoreLibs\\Debug\\Support' => __DIR__ . '/../..' . '/lib/CoreLibs/Debug/Support.php', - 'CoreLibs\\Get\\DotEnv' => __DIR__ . '/../..' . '/lib/CoreLibs/Get/DotEnv.php', - 'CoreLibs\\Get\\System' => __DIR__ . '/../..' . '/lib/CoreLibs/Get/System.php', - 'CoreLibs\\Language\\Core\\CachedFileReader' => __DIR__ . '/../..' . '/lib/CoreLibs/Language/Core/CachedFileReader.php', - 'CoreLibs\\Language\\Core\\FileReader' => __DIR__ . '/../..' . '/lib/CoreLibs/Language/Core/FileReader.php', - 'CoreLibs\\Language\\Core\\GetTextReader' => __DIR__ . '/../..' . '/lib/CoreLibs/Language/Core/GetTextReader.php', - 'CoreLibs\\Language\\Core\\StreamReader' => __DIR__ . '/../..' . '/lib/CoreLibs/Language/Core/StreamReader.php', - 'CoreLibs\\Language\\Core\\StringReader' => __DIR__ . '/../..' . '/lib/CoreLibs/Language/Core/StringReader.php', - 'CoreLibs\\Language\\GetLocale' => __DIR__ . '/../..' . '/lib/CoreLibs/Language/GetLocale.php', - 'CoreLibs\\Language\\L10n' => __DIR__ . '/../..' . '/lib/CoreLibs/Language/L10n.php', - 'CoreLibs\\Logging\\ErrorMessage' => __DIR__ . '/../..' . '/lib/CoreLibs/Logging/ErrorMessage.php', - 'CoreLibs\\Logging\\Logger\\Flag' => __DIR__ . '/../..' . '/lib/CoreLibs/Logging/Logger/Flag.php', - 'CoreLibs\\Logging\\Logger\\Level' => __DIR__ . '/../..' . '/lib/CoreLibs/Logging/Logger/Level.php', - 'CoreLibs\\Logging\\Logger\\MessageLevel' => __DIR__ . '/../..' . '/lib/CoreLibs/Logging/Logger/MessageLevel.php', - 'CoreLibs\\Logging\\Logging' => __DIR__ . '/../..' . '/lib/CoreLibs/Logging/Logging.php', - 'CoreLibs\\Output\\Form\\Elements' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/Elements.php', - 'CoreLibs\\Output\\Form\\Generate' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/Generate.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditAccess' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/TableArrays/EditAccess.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditGroups' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/TableArrays/EditGroups.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditLanguages' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/TableArrays/EditLanguages.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditMenuGroup' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/TableArrays/EditMenuGroup.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditOrder' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/TableArrays/EditOrder.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditPages' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/TableArrays/EditPages.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditSchemas' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/TableArrays/EditSchemas.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditUsers' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/TableArrays/EditUsers.php', - 'CoreLibs\\Output\\Form\\TableArrays\\EditVisibleGroup' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/TableArrays/EditVisibleGroup.php', - 'CoreLibs\\Output\\Form\\TableArrays\\Interface\\TableArraysInterface' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/TableArrays/Interface/TableArraysInterface.php', - 'CoreLibs\\Output\\Form\\Token' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Form/Token.php', - 'CoreLibs\\Output\\Image' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/Image.php', - 'CoreLibs\\Output\\ProgressBar' => __DIR__ . '/../..' . '/lib/CoreLibs/Output/ProgressBar.php', - 'CoreLibs\\Security\\CreateKey' => __DIR__ . '/../..' . '/lib/CoreLibs/Security/CreateKey.php', - 'CoreLibs\\Security\\Password' => __DIR__ . '/../..' . '/lib/CoreLibs/Security/Password.php', - 'CoreLibs\\Security\\SymmetricEncryption' => __DIR__ . '/../..' . '/lib/CoreLibs/Security/SymmetricEncryption.php', - 'CoreLibs\\Template\\HtmlBuilder\\Block' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/HtmlBuilder/Block.php', - 'CoreLibs\\Template\\HtmlBuilder\\Element' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/HtmlBuilder/Element.php', - 'CoreLibs\\Template\\HtmlBuilder\\General\\Error' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/HtmlBuilder/General/Error.php', - 'CoreLibs\\Template\\HtmlBuilder\\General\\HtmlBuilderExcpetion' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/HtmlBuilder/General/HtmlBuilderExcpetion.php', - 'CoreLibs\\Template\\HtmlBuilder\\General\\Settings' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/HtmlBuilder/General/Settings.php', - 'CoreLibs\\Template\\HtmlBuilder\\StringReplace' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/HtmlBuilder/StringReplace.php', - 'CoreLibs\\Template\\SmartyExtend' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/SmartyExtend.php', - 'FileUpload\\Core\\qqUploadedFile' => __DIR__ . '/../..' . '/lib/FileUpload/Core/qqUploadedFile.php', - 'FileUpload\\Core\\qqUploadedFileForm' => __DIR__ . '/../..' . '/lib/FileUpload/Core/qqUploadedFileForm.php', - 'FileUpload\\Core\\qqUploadedFileXhr' => __DIR__ . '/../..' . '/lib/FileUpload/Core/qqUploadedFileXhr.php', - 'FileUpload\\qqFileUploader' => __DIR__ . '/../..' . '/lib/FileUpload/qqFileUploader.php', - 'Smarty' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/Smarty.class.php', - 'SmartyCompilerException' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smartycompilerexception.php', - 'SmartyException' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smartyexception.php', - 'Smarty_Autoloader' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/Autoloader.php', - 'Smarty_CacheResource' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_cacheresource.php', - 'Smarty_CacheResource_Custom' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_cacheresource_custom.php', - 'Smarty_CacheResource_KeyValueStore' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_cacheresource_keyvaluestore.php', - 'Smarty_Data' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_data.php', - 'Smarty_Internal_Block' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_block.php', - 'Smarty_Internal_CacheResource_File' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_cacheresource_file.php', - 'Smarty_Internal_CompileBase' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compilebase.php', - 'Smarty_Internal_Compile_Append' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_append.php', - 'Smarty_Internal_Compile_Assign' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_assign.php', - 'Smarty_Internal_Compile_Block' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_block.php', - 'Smarty_Internal_Compile_Block_Child' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_block_child.php', - 'Smarty_Internal_Compile_Block_Parent' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_block_parent.php', - 'Smarty_Internal_Compile_Blockclose' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_block.php', - 'Smarty_Internal_Compile_Break' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_break.php', - 'Smarty_Internal_Compile_Call' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_call.php', - 'Smarty_Internal_Compile_Capture' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_capture.php', - 'Smarty_Internal_Compile_CaptureClose' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_capture.php', - 'Smarty_Internal_Compile_Child' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_child.php', - 'Smarty_Internal_Compile_Config_Load' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_config_load.php', - 'Smarty_Internal_Compile_Continue' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_continue.php', - 'Smarty_Internal_Compile_Debug' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_debug.php', - 'Smarty_Internal_Compile_Else' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_if.php', - 'Smarty_Internal_Compile_Elseif' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_if.php', - 'Smarty_Internal_Compile_Eval' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_eval.php', - 'Smarty_Internal_Compile_Extends' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_extends.php', - 'Smarty_Internal_Compile_For' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_for.php', - 'Smarty_Internal_Compile_Forclose' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_for.php', - 'Smarty_Internal_Compile_Foreach' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_foreach.php', - 'Smarty_Internal_Compile_Foreachclose' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_foreach.php', - 'Smarty_Internal_Compile_Foreachelse' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_foreach.php', - 'Smarty_Internal_Compile_Forelse' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_for.php', - 'Smarty_Internal_Compile_Function' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_function.php', - 'Smarty_Internal_Compile_Functionclose' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_function.php', - 'Smarty_Internal_Compile_If' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_if.php', - 'Smarty_Internal_Compile_Ifclose' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_if.php', - 'Smarty_Internal_Compile_Include' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_include.php', - 'Smarty_Internal_Compile_Insert' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_insert.php', - 'Smarty_Internal_Compile_Ldelim' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_ldelim.php', - 'Smarty_Internal_Compile_Make_Nocache' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_make_nocache.php', - 'Smarty_Internal_Compile_Nocache' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_nocache.php', - 'Smarty_Internal_Compile_Nocacheclose' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_nocache.php', - 'Smarty_Internal_Compile_Parent' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_parent.php', - 'Smarty_Internal_Compile_Private_Block_Plugin' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_block_plugin.php', - 'Smarty_Internal_Compile_Private_ForeachSection' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_foreachsection.php', - 'Smarty_Internal_Compile_Private_Function_Plugin' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_function_plugin.php', - 'Smarty_Internal_Compile_Private_Modifier' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_modifier.php', - 'Smarty_Internal_Compile_Private_Object_Block_Function' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_object_block_function.php', - 'Smarty_Internal_Compile_Private_Object_Function' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_object_function.php', - 'Smarty_Internal_Compile_Private_Print_Expression' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_print_expression.php', - 'Smarty_Internal_Compile_Private_Registered_Block' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_registered_block.php', - 'Smarty_Internal_Compile_Private_Registered_Function' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_registered_function.php', - 'Smarty_Internal_Compile_Private_Special_Variable' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_private_special_variable.php', - 'Smarty_Internal_Compile_Rdelim' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_rdelim.php', - 'Smarty_Internal_Compile_Section' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_section.php', - 'Smarty_Internal_Compile_Sectionclose' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_section.php', - 'Smarty_Internal_Compile_Sectionelse' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_section.php', - 'Smarty_Internal_Compile_Setfilter' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_setfilter.php', - 'Smarty_Internal_Compile_Setfilterclose' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_setfilter.php', - 'Smarty_Internal_Compile_Shared_Inheritance' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_shared_inheritance.php', - 'Smarty_Internal_Compile_While' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_while.php', - 'Smarty_Internal_Compile_Whileclose' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_compile_while.php', - 'Smarty_Internal_Config_File_Compiler' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_config_file_compiler.php', - 'Smarty_Internal_Configfilelexer' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_configfilelexer.php', - 'Smarty_Internal_Configfileparser' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_configfileparser.php', - 'Smarty_Internal_Data' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_data.php', - 'Smarty_Internal_Debug' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_debug.php', - 'Smarty_Internal_ErrorHandler' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_errorhandler.php', - 'Smarty_Internal_Extension_Handler' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_extension_handler.php', - 'Smarty_Internal_Method_AddAutoloadFilters' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_addautoloadfilters.php', - 'Smarty_Internal_Method_AddDefaultModifiers' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_adddefaultmodifiers.php', - 'Smarty_Internal_Method_Append' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_append.php', - 'Smarty_Internal_Method_AppendByRef' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_appendbyref.php', - 'Smarty_Internal_Method_AssignByRef' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_assignbyref.php', - 'Smarty_Internal_Method_AssignGlobal' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_assignglobal.php', - 'Smarty_Internal_Method_ClearAllAssign' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_clearallassign.php', - 'Smarty_Internal_Method_ClearAllCache' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_clearallcache.php', - 'Smarty_Internal_Method_ClearAssign' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_clearassign.php', - 'Smarty_Internal_Method_ClearCache' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_clearcache.php', - 'Smarty_Internal_Method_ClearCompiledTemplate' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_clearcompiledtemplate.php', - 'Smarty_Internal_Method_ClearConfig' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_clearconfig.php', - 'Smarty_Internal_Method_CompileAllConfig' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_compileallconfig.php', - 'Smarty_Internal_Method_CompileAllTemplates' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_compilealltemplates.php', - 'Smarty_Internal_Method_ConfigLoad' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_configload.php', - 'Smarty_Internal_Method_CreateData' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_createdata.php', - 'Smarty_Internal_Method_GetAutoloadFilters' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getautoloadfilters.php', - 'Smarty_Internal_Method_GetConfigVariable' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getconfigvariable.php', - 'Smarty_Internal_Method_GetConfigVars' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getconfigvars.php', - 'Smarty_Internal_Method_GetDebugTemplate' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getdebugtemplate.php', - 'Smarty_Internal_Method_GetDefaultModifiers' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getdefaultmodifiers.php', - 'Smarty_Internal_Method_GetGlobal' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getglobal.php', - 'Smarty_Internal_Method_GetRegisteredObject' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getregisteredobject.php', - 'Smarty_Internal_Method_GetStreamVariable' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_getstreamvariable.php', - 'Smarty_Internal_Method_GetTags' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_gettags.php', - 'Smarty_Internal_Method_GetTemplateVars' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_gettemplatevars.php', - 'Smarty_Internal_Method_Literals' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_literals.php', - 'Smarty_Internal_Method_LoadFilter' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_loadfilter.php', - 'Smarty_Internal_Method_LoadPlugin' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_loadplugin.php', - 'Smarty_Internal_Method_MustCompile' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_mustcompile.php', - 'Smarty_Internal_Method_RegisterCacheResource' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registercacheresource.php', - 'Smarty_Internal_Method_RegisterClass' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerclass.php', - 'Smarty_Internal_Method_RegisterDefaultConfigHandler' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerdefaultconfighandler.php', - 'Smarty_Internal_Method_RegisterDefaultPluginHandler' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerdefaultpluginhandler.php', - 'Smarty_Internal_Method_RegisterDefaultTemplateHandler' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerdefaulttemplatehandler.php', - 'Smarty_Internal_Method_RegisterFilter' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerfilter.php', - 'Smarty_Internal_Method_RegisterObject' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerobject.php', - 'Smarty_Internal_Method_RegisterPlugin' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerplugin.php', - 'Smarty_Internal_Method_RegisterResource' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_registerresource.php', - 'Smarty_Internal_Method_SetAutoloadFilters' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_setautoloadfilters.php', - 'Smarty_Internal_Method_SetDebugTemplate' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_setdebugtemplate.php', - 'Smarty_Internal_Method_SetDefaultModifiers' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_setdefaultmodifiers.php', - 'Smarty_Internal_Method_UnloadFilter' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_unloadfilter.php', - 'Smarty_Internal_Method_UnregisterCacheResource' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_unregistercacheresource.php', - 'Smarty_Internal_Method_UnregisterFilter' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_unregisterfilter.php', - 'Smarty_Internal_Method_UnregisterObject' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_unregisterobject.php', - 'Smarty_Internal_Method_UnregisterPlugin' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_unregisterplugin.php', - 'Smarty_Internal_Method_UnregisterResource' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_method_unregisterresource.php', - 'Smarty_Internal_Nocache_Insert' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_nocache_insert.php', - 'Smarty_Internal_ParseTree' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_parsetree.php', - 'Smarty_Internal_ParseTree_Code' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_parsetree_code.php', - 'Smarty_Internal_ParseTree_Dq' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_parsetree_dq.php', - 'Smarty_Internal_ParseTree_DqContent' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_parsetree_dqcontent.php', - 'Smarty_Internal_ParseTree_Tag' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_parsetree_tag.php', - 'Smarty_Internal_ParseTree_Template' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_parsetree_template.php', - 'Smarty_Internal_ParseTree_Text' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_parsetree_text.php', - 'Smarty_Internal_Resource_Eval' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_resource_eval.php', - 'Smarty_Internal_Resource_Extends' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_resource_extends.php', - 'Smarty_Internal_Resource_File' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_resource_file.php', - 'Smarty_Internal_Resource_Php' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_resource_php.php', - 'Smarty_Internal_Resource_Stream' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_resource_stream.php', - 'Smarty_Internal_Resource_String' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_resource_string.php', - 'Smarty_Internal_Runtime_CacheModify' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_cachemodify.php', - 'Smarty_Internal_Runtime_CacheResourceFile' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_cacheresourcefile.php', - 'Smarty_Internal_Runtime_Capture' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_capture.php', - 'Smarty_Internal_Runtime_CodeFrame' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_codeframe.php', - 'Smarty_Internal_Runtime_FilterHandler' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_filterhandler.php', - 'Smarty_Internal_Runtime_Foreach' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_foreach.php', - 'Smarty_Internal_Runtime_GetIncludePath' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_getincludepath.php', - 'Smarty_Internal_Runtime_Inheritance' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_inheritance.php', - 'Smarty_Internal_Runtime_Make_Nocache' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_make_nocache.php', - 'Smarty_Internal_Runtime_TplFunction' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_tplfunction.php', - 'Smarty_Internal_Runtime_UpdateCache' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_updatecache.php', - 'Smarty_Internal_Runtime_UpdateScope' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_updatescope.php', - 'Smarty_Internal_Runtime_WriteFile' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_runtime_writefile.php', - 'Smarty_Internal_SmartyTemplateCompiler' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_smartytemplatecompiler.php', - 'Smarty_Internal_Template' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_template.php', - 'Smarty_Internal_TemplateBase' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_templatebase.php', - 'Smarty_Internal_TemplateCompilerBase' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_templatecompilerbase.php', - 'Smarty_Internal_Templatelexer' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_templatelexer.php', - 'Smarty_Internal_Templateparser' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_templateparser.php', - 'Smarty_Internal_TestInstall' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_testinstall.php', - 'Smarty_Internal_Undefined' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_undefined.php', - 'Smarty_Resource' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_resource.php', - 'Smarty_Resource_Custom' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_resource_custom.php', - 'Smarty_Resource_Recompiled' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_resource_recompiled.php', - 'Smarty_Resource_Uncompiled' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_resource_uncompiled.php', - 'Smarty_Security' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_security.php', - 'Smarty_Template_Cached' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_template_cached.php', - 'Smarty_Template_Compiled' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_template_compiled.php', - 'Smarty_Template_Config' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_template_config.php', - 'Smarty_Template_Resource_Base' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_template_resource_base.php', - 'Smarty_Template_Source' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_template_source.php', - 'Smarty_Undefined_Variable' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_undefined_variable.php', - 'Smarty_Variable' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_variable.php', - 'TPC_yyStackEntry' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_configfileparser.php', - 'TP_yyStackEntry' => __DIR__ . '/..' . '/egrajp/smarty-extended/src/sysplugins/smarty_internal_templateparser.php', - 'TestCalls\\DB\\TestDB' => __DIR__ . '/../..' . '/lib/TestCalls/DB/TestDB.php', - 'TestCalls\\Test' => __DIR__ . '/../..' . '/lib/TestCalls/Test.php', - ); - - public static function getInitializer(ClassLoader $loader) - { - return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit10fe8fe2ec4017b8644d2b64bcf398b9::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit10fe8fe2ec4017b8644d2b64bcf398b9::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInit10fe8fe2ec4017b8644d2b64bcf398b9::$classMap; - - }, null, ClassLoader::class); - } -} diff --git a/www/vendor/composer/installed.json b/www/vendor/composer/installed.json deleted file mode 100644 index 9d57f961..00000000 --- a/www/vendor/composer/installed.json +++ /dev/null @@ -1,149 +0,0 @@ -{ - "packages": [ - { - "name": "egrajp/smarty-extended", - "version": "4.5.2", - "version_normalized": "4.5.2.0", - "dist": { - "type": "zip", - "url": "https://git.egplusww.jp/api/packages/Composer/composer/files/egrajp%2Fsmarty-extended/4.5.2/egrajp-smarty-extended.4.5.2.zip", - "shasum": "a2c67a5047aad349a2cfa54240a44da449df9c4c" - }, - "time": "2024-04-16T18:25:27+09:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" - ] - }, - "license": [ - "LGPL-3.0" - ], - "authors": [ - { - "name": "Clemens Schwaighofer", - "email": "clemens.schwaighofer@egplusww.com" - } - ], - "description": "Smarty, extended with gettext, checkbox/radio labels and index numbers", - "homepage": "https://github.com/smarty-php/smarty/", - "keywords": [ - "templating" - ], - "install-path": "../egrajp/smarty-extended" - }, - { - "name": "gullevek/dotenv", - "version": "v2.1.0", - "version_normalized": "2.1.0.0", - "source": { - "type": "git", - "url": "https://github.com/gullevek/dotEnv.git", - "reference": "b9feacaded4e48effff9da7d1173752aef3dc27f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/gullevek/dotEnv/zipball/b9feacaded4e48effff9da7d1173752aef3dc27f", - "reference": "b9feacaded4e48effff9da7d1173752aef3dc27f", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "require-dev": { - "phan/phan": "^5.4", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9" - }, - "time": "2024-08-21T02:41:15+00:00", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "gullevek\\dotEnv\\": "src/", - "gullevek\\dotenv\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Clemens Schwaighofer", - "email": "gullevek@gullevek.org", - "homepage": "http://gullevek.org" - } - ], - "description": "Simple .env file processing and storing in _ENV array", - "homepage": "https://github.com/gullevek/dotEnv", - "keywords": [ - ".env", - "_ENV", - "dotenv", - "environment variables" - ], - "support": { - "issues": "https://github.com/gullevek/dotEnv/issues", - "source": "https://github.com/gullevek/dotEnv/tree/v2.1.0" - }, - "install-path": "../gullevek/dotenv" - }, - { - "name": "psr/log", - "version": "3.0.0", - "version_normalized": "3.0.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "time": "2021-07-14T16:46:02+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Psr\\Log\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], - "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" - }, - "install-path": "../psr/log" - } - ], - "dev": true, - "dev-package-names": [] -} diff --git a/www/vendor/composer/installed.php b/www/vendor/composer/installed.php deleted file mode 100644 index d29dc657..00000000 --- a/www/vendor/composer/installed.php +++ /dev/null @@ -1,50 +0,0 @@ - array( - 'name' => 'egrajp/corelibs-dev', - 'pretty_version' => 'dev-master', - 'version' => 'dev-master', - 'reference' => null, - 'type' => 'library', - 'install_path' => __DIR__ . '/../../', - 'aliases' => array(), - 'dev' => true, - ), - 'versions' => array( - 'egrajp/corelibs-dev' => array( - 'pretty_version' => 'dev-master', - 'version' => 'dev-master', - 'reference' => null, - 'type' => 'library', - 'install_path' => __DIR__ . '/../../', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'egrajp/smarty-extended' => array( - 'pretty_version' => '4.5.2', - 'version' => '4.5.2.0', - 'reference' => null, - 'type' => 'library', - 'install_path' => __DIR__ . '/../egrajp/smarty-extended', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'gullevek/dotenv' => array( - 'pretty_version' => 'v2.1.0', - 'version' => '2.1.0.0', - 'reference' => 'b9feacaded4e48effff9da7d1173752aef3dc27f', - 'type' => 'library', - 'install_path' => __DIR__ . '/../gullevek/dotenv', - 'aliases' => array(), - 'dev_requirement' => false, - ), - 'psr/log' => array( - 'pretty_version' => '3.0.0', - 'version' => '3.0.0.0', - 'reference' => 'fe5ea303b0887d5caefd3d431c3e61ad47037001', - 'type' => 'library', - 'install_path' => __DIR__ . '/../psr/log', - 'aliases' => array(), - 'dev_requirement' => false, - ), - ), -); diff --git a/www/vendor/composer/platform_check.php b/www/vendor/composer/platform_check.php deleted file mode 100644 index 4c3a5d68..00000000 --- a/www/vendor/composer/platform_check.php +++ /dev/null @@ -1,26 +0,0 @@ -= 80100)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; -} - -if ($issues) { - if (!headers_sent()) { - header('HTTP/1.1 500 Internal Server Error'); - } - if (!ini_get('display_errors')) { - if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { - fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); - } elseif (!headers_sent()) { - echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; - } - } - trigger_error( - 'Composer detected issues in your platform: ' . implode(' ', $issues), - E_USER_ERROR - ); -} diff --git a/www/vendor/egrajp/smarty-extended/.gitignore b/www/vendor/egrajp/smarty-extended/.gitignore deleted file mode 100644 index 7579f743..00000000 --- a/www/vendor/egrajp/smarty-extended/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -vendor -composer.lock diff --git a/www/vendor/egrajp/smarty-extended/ReadMe.md b/www/vendor/egrajp/smarty-extended/ReadMe.md deleted file mode 100644 index ded4fcec..00000000 --- a/www/vendor/egrajp/smarty-extended/ReadMe.md +++ /dev/null @@ -1,46 +0,0 @@ -# Composer package from Smarty Extended - -This is an updated package for smarty\smarty - -Adds: - -- translation block -- label and pos for checkboxes and radio buttons - -For local install only - -## Setup from central composer - -Setup from gitea internal servers - -```sh -composer config repositories.git.egplusww.jp.Composer composer https://git.egplusww.jp/api/packages/Composer/composer -``` - -Alternative setup composer local zip file repot: -`composer config repositories.composer.egplusww.jp composer http://composer.egplusww.jp` - -## Install package - -`composer require egrajp/smarty-extended:^4.3` - -## How to update - -1) update the original composer for ^4.3 -2) copy over the src/sysplugins and all base files in src/ -3) check either function.html_checkboxes.php and function.html_options.php have changed -4) copy src/plugins except the above two files, be sure to keep the block.t.php and function_popup*.php -5) Create new release version as official relase number - -## Updated files (different from master) - -### New - -`src/plugins/block.t.php` -`src/plugins/function_popup.php` -`src/plugins/function_popup.init.php` - -### Changed - -`src/plugins/function.html_checkboxes.php` -`src/plugins/function.html_options.php` diff --git a/www/vendor/egrajp/smarty-extended/composer.json b/www/vendor/egrajp/smarty-extended/composer.json deleted file mode 100644 index dfc944f8..00000000 --- a/www/vendor/egrajp/smarty-extended/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "egrajp/smarty-extended", - "description": "Smarty, extended with gettext, checkbox/radio labels and index numbers", - "type": "library", - "keywords": [ - "templating" - ], - "homepage": "https://github.com/smarty-php/smarty/", - "license": "LGPL-3.0", - "autoload": { - "classmap": [ - "src/" - ] - }, - "authors": [ - { - "name": "Clemens Schwaighofer", - "email": "clemens.schwaighofer@egplusww.com" - } - ], - "minimum-stability": "dev", - "require": {} -} diff --git a/www/vendor/egrajp/smarty-extended/publish/.gitignore b/www/vendor/egrajp/smarty-extended/publish/.gitignore deleted file mode 100644 index 81a38be6..00000000 --- a/www/vendor/egrajp/smarty-extended/publish/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.env* diff --git a/www/vendor/egrajp/smarty-extended/publish/last.published b/www/vendor/egrajp/smarty-extended/publish/last.published deleted file mode 100644 index 4404a17b..00000000 --- a/www/vendor/egrajp/smarty-extended/publish/last.published +++ /dev/null @@ -1 +0,0 @@ -4.5.1 diff --git a/www/vendor/egrajp/smarty-extended/publish/package-download/.gitignore b/www/vendor/egrajp/smarty-extended/publish/package-download/.gitignore deleted file mode 100644 index c4c4ffc6..00000000 --- a/www/vendor/egrajp/smarty-extended/publish/package-download/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.zip diff --git a/www/vendor/egrajp/smarty-extended/publish/publish.sh b/www/vendor/egrajp/smarty-extended/publish/publish.sh deleted file mode 100755 index e74a53db..00000000 --- a/www/vendor/egrajp/smarty-extended/publish/publish.sh +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env bash - -BASE_FOLDER=$(dirname $(readlink -f $0))"/"; -PACKAGE_DOWNLOAD="${BASE_FOLDER}package-download/"; -if [ ! -d "${PACKAGE_DOWNLOAD}" ]; then - mkdir "${PACKAGE_DOWNLOAD}"; -fi; -VERSION=$(git tag --list | sort -V | tail -n1 | sed -e "s/^v//"); -file_last_published="${BASE_FOLDER}last.published"; -go_flag="$1"; - -if [ -z "${VERSION}" ]; then - echo "Version must be set in the form x.y.z without any leading characters"; - exit; -fi; -# compare version, if different or newer, deploy -if [ -f "${file_last_published}" ]; then - LAST_PUBLISHED_VERSION=$(cat ${file_last_published}); - if $(dpkg --compare-versions "${VERSION}" le "${LAST_PUBLISHED_VERSION}"); then - echo "git tag version ${VERSION} is not newer than previous published version ${LAST_PUBLISHED_VERSION}"; - exit; - fi; -fi; - -# read in the .env.deploy file and we must have -# GITEA_UPLOAD_FILENAME -# GITLAB_USER -# GITLAB_TOKEN -# GITLAB_URL -# GITEA_USER -# GITEA_DEPLOY_TOKEN -# GITEA_URL_DL -# GITEA_URL_PUSH -if [ ! -f "${BASE_FOLDER}.env.deploy" ]; then - echo "Deploy enviroment file .env.deploy is missing"; - exit; -fi; -set -o allexport; -cd ${BASE_FOLDER}; -source .env.deploy; -cd -; -set +o allexport; - -if [ "${go_flag}" != "go" ]; then - echo "No go flag given"; - echo "Would publish ${VERSION}"; - echo "[END]"; - exit; -fi; - -echo "[START]"; - -# gitea -if [ ! -z "${GITEA_UPLOAD_FILENAME}" ] && - [ ! -z "${GITEA_URL_DL}" ] && [ ! -z "${GITEA_URL_PUSH}" ] && - [ ! -z "${GITEA_USER}" ] && [ ! -z "${GITEA_TOKEN}" ]; then - curl -LJO \ - --output-dir "${PACKAGE_DOWNLOAD}" \ - ${GITEA_URL_DL}/v${VERSION}.zip; - # echo "curl -LJO \ - # --output-dir "${PACKAGE_DOWNLOAD}" \ - # ${GITEA_URL_DL}/v${VERSION}.zip;" - curl --user ${GITEA_USER}:${GITEA_TOKEN} \ - --upload-file "${PACKAGE_DOWNLOAD}${GITEA_UPLOAD_FILENAME}-v${VERSION}.zip" \ - ${GITEA_URL_PUSH}?version=${VERSION}; - # echo "curl --user ${GITEA_USER}:${GITEA_TOKEN} \ - # --upload-file "${PACKAGE_DOWNLOAD}${GITEA_UPLOAD_FILENAME}-v${VERSION}.zip" \ - # ${GITEA_URL_PUSH}?version=${VERSION};" - echo "${VERSION}" > "${file_last_published}"; -else - echo "Missing either GITEA_UPLOAD_FILENAME, GITEA_URL_DL, GITEA_URL_PUSH, GITEA_USER or GITEA_TOKEN environment variable"; -fi; - -# gitlab -if [ ! -z "${GITLAB_URL}" ] && [ ! -z "${GITLAB_DEPLOY_TOKEN}" ]; then - curl --data tag=v${VERSION} \ - --header "Deploy-Token: ${GITLAB_DEPLOY_TOKEN}" \ - "${GITLAB_URL}"; - curl --data branch=master \ - --header "Deploy-Token: ${GITLAB_DEPLOY_TOKEN}" \ - "${GITLAB_URL}"; - echo "${VERSION}" > "${file_last_published}"; -else - echo "Missing GITLAB_DEPLOY_TOKEN environment variable"; -fi; - -echo ""; -echo "[DONE]"; - -# __END__ diff --git a/www/vendor/egrajp/smarty-extended/src/Autoloader.php b/www/vendor/egrajp/smarty-extended/src/Autoloader.php deleted file mode 100644 index da7e32ab..00000000 --- a/www/vendor/egrajp/smarty-extended/src/Autoloader.php +++ /dev/null @@ -1,111 +0,0 @@ - 'Smarty.class.php'); - - /** - * Registers Smarty_Autoloader backward compatible to older installations. - * - * @param bool $prepend Whether to prepend the autoloader or not. - */ - public static function registerBC($prepend = false) - { - /** - * register the class autoloader - */ - if (!defined('SMARTY_SPL_AUTOLOAD')) { - define('SMARTY_SPL_AUTOLOAD', 0); - } - if (SMARTY_SPL_AUTOLOAD - && set_include_path(get_include_path() . PATH_SEPARATOR . SMARTY_SYSPLUGINS_DIR) !== false - ) { - $registeredAutoLoadFunctions = spl_autoload_functions(); - if (!isset($registeredAutoLoadFunctions[ 'spl_autoload' ])) { - spl_autoload_register(); - } - } else { - self::register($prepend); - } - } - - /** - * Registers Smarty_Autoloader as an SPL autoloader. - * - * @param bool $prepend Whether to prepend the autoloader or not. - */ - public static function register($prepend = false) - { - self::$SMARTY_DIR = defined('SMARTY_DIR') ? SMARTY_DIR : __DIR__ . DIRECTORY_SEPARATOR; - self::$SMARTY_SYSPLUGINS_DIR = defined('SMARTY_SYSPLUGINS_DIR') ? SMARTY_SYSPLUGINS_DIR : - self::$SMARTY_DIR . 'sysplugins' . DIRECTORY_SEPARATOR; - spl_autoload_register(array(__CLASS__, 'autoload'), true, $prepend); - } - - /** - * Handles auto loading of classes. - * - * @param string $class A class name. - */ - public static function autoload($class) - { - if ($class[ 0 ] !== 'S' || strpos($class, 'Smarty') !== 0) { - return; - } - $_class = smarty_strtolower_ascii($class); - if (isset(self::$rootClasses[ $_class ])) { - $file = self::$SMARTY_DIR . self::$rootClasses[ $_class ]; - if (is_file($file)) { - include $file; - } - } else { - $file = self::$SMARTY_SYSPLUGINS_DIR . $_class . '.php'; - if (is_file($file)) { - include $file; - } - } - return; - } -} diff --git a/www/vendor/egrajp/smarty-extended/src/Smarty.class.php b/www/vendor/egrajp/smarty-extended/src/Smarty.class.php deleted file mode 100644 index 1bfc5646..00000000 --- a/www/vendor/egrajp/smarty-extended/src/Smarty.class.php +++ /dev/null @@ -1,1405 +0,0 @@ - - * @author Uwe Tews - * @author Rodney Rehm - * @package Smarty - */ -/** - * set SMARTY_DIR to absolute path to Smarty library files. - * Sets SMARTY_DIR only if user application has not already defined it. - */ -if (!defined('SMARTY_DIR')) { - /** - * - */ - define('SMARTY_DIR', __DIR__ . DIRECTORY_SEPARATOR); -} -/** - * set SMARTY_SYSPLUGINS_DIR to absolute path to Smarty internal plugins. - * Sets SMARTY_SYSPLUGINS_DIR only if user application has not already defined it. - */ -if (!defined('SMARTY_SYSPLUGINS_DIR')) { - /** - * - */ - define('SMARTY_SYSPLUGINS_DIR', SMARTY_DIR . 'sysplugins' . DIRECTORY_SEPARATOR); -} -if (!defined('SMARTY_PLUGINS_DIR')) { - /** - * - */ - define('SMARTY_PLUGINS_DIR', SMARTY_DIR . 'plugins' . DIRECTORY_SEPARATOR); -} -if (!defined('SMARTY_MBSTRING')) { - /** - * - */ - define('SMARTY_MBSTRING', function_exists('mb_get_info')); -} - -/** - * Load helper functions - */ -if (!defined('SMARTY_HELPER_FUNCTIONS_LOADED')) { - include __DIR__ . '/functions.php'; -} - -/** - * Load Smarty_Autoloader - */ -if (!class_exists('Smarty_Autoloader')) { - include __DIR__ . '/bootstrap.php'; -} - -/** - * Load always needed external class files - */ -require_once SMARTY_SYSPLUGINS_DIR . 'smarty_internal_data.php'; -require_once SMARTY_SYSPLUGINS_DIR . 'smarty_internal_extension_handler.php'; -require_once SMARTY_SYSPLUGINS_DIR . 'smarty_internal_templatebase.php'; -require_once SMARTY_SYSPLUGINS_DIR . 'smarty_internal_template.php'; -require_once SMARTY_SYSPLUGINS_DIR . 'smarty_resource.php'; -require_once SMARTY_SYSPLUGINS_DIR . 'smarty_variable.php'; -require_once SMARTY_SYSPLUGINS_DIR . 'smarty_template_source.php'; -require_once SMARTY_SYSPLUGINS_DIR . 'smarty_template_resource_base.php'; -require_once SMARTY_SYSPLUGINS_DIR . 'smarty_internal_resource_file.php'; - -/** - * This is the main Smarty class - * - * @package Smarty - * - * The following methods will be dynamically loaded by the extension handler when they are called. - * They are located in a corresponding Smarty_Internal_Method_xxxx class - * - * @method int clearAllCache(int $exp_time = null, string $type = null) - * @method int clearCache(string $template_name, string $cache_id = null, string $compile_id = null, int $exp_time = null, string $type = null) - * @method int compileAllTemplates(string $extension = '.tpl', bool $force_compile = false, int $time_limit = 0, $max_errors = null) - * @method int compileAllConfig(string $extension = '.conf', bool $force_compile = false, int $time_limit = 0, $max_errors = null) - * @method int clearCompiledTemplate($resource_name = null, $compile_id = null, $exp_time = null) - */ -class Smarty extends Smarty_Internal_TemplateBase -{ - /** - * smarty version - */ - const SMARTY_VERSION = '4.5.1'; - /** - * define variable scopes - */ - const SCOPE_LOCAL = 1; - const SCOPE_PARENT = 2; - const SCOPE_TPL_ROOT = 4; - const SCOPE_ROOT = 8; - const SCOPE_SMARTY = 16; - const SCOPE_GLOBAL = 32; - /** - * define caching modes - */ - const CACHING_OFF = 0; - const CACHING_LIFETIME_CURRENT = 1; - const CACHING_LIFETIME_SAVED = 2; - /** - * define constant for clearing cache files be saved expiration dates - */ - const CLEAR_EXPIRED = -1; - /** - * define compile check modes - */ - const COMPILECHECK_OFF = 0; - const COMPILECHECK_ON = 1; - const COMPILECHECK_CACHEMISS = 2; - /** - * define debug modes - */ - const DEBUG_OFF = 0; - const DEBUG_ON = 1; - const DEBUG_INDIVIDUAL = 2; - - /** - * filter types - */ - const FILTER_POST = 'post'; - const FILTER_PRE = 'pre'; - const FILTER_OUTPUT = 'output'; - const FILTER_VARIABLE = 'variable'; - /** - * plugin types - */ - const PLUGIN_FUNCTION = 'function'; - const PLUGIN_BLOCK = 'block'; - const PLUGIN_COMPILER = 'compiler'; - const PLUGIN_MODIFIER = 'modifier'; - const PLUGIN_MODIFIERCOMPILER = 'modifiercompiler'; - - /** - * assigned global tpl vars - */ - public static $global_tpl_vars = array(); - - /** - * Flag denoting if Multibyte String functions are available - */ - public static $_MBSTRING = SMARTY_MBSTRING; - - /** - * The character set to adhere to (e.g. "UTF-8") - */ - public static $_CHARSET = SMARTY_MBSTRING ? 'UTF-8' : 'ISO-8859-1'; - - /** - * The date format to be used internally - * (accepts date() and strftime()) - */ - public static $_DATE_FORMAT = '%b %e, %Y'; - - /** - * Flag denoting if PCRE should run in UTF-8 mode - */ - public static $_UTF8_MODIFIER = 'u'; - - /** - * Flag denoting if operating system is windows - */ - public static $_IS_WINDOWS = false; - - /** - * auto literal on delimiters with whitespace - * - * @var boolean - */ - public $auto_literal = true; - - /** - * display error on not assigned variables - * - * @var boolean - */ - public $error_unassigned = false; - - /** - * look up relative file path in include_path - * - * @var boolean - */ - public $use_include_path = false; - - /** - * flag if template_dir is normalized - * - * @var bool - */ - public $_templateDirNormalized = false; - - /** - * joined template directory string used in cache keys - * - * @var string - */ - public $_joined_template_dir = null; - - /** - * flag if config_dir is normalized - * - * @var bool - */ - public $_configDirNormalized = false; - - /** - * joined config directory string used in cache keys - * - * @var string - */ - public $_joined_config_dir = null; - - /** - * default template handler - * - * @var callable - */ - public $default_template_handler_func = null; - - /** - * default config handler - * - * @var callable - */ - public $default_config_handler_func = null; - - /** - * default plugin handler - * - * @var callable - */ - public $default_plugin_handler_func = null; - - /** - * flag if template_dir is normalized - * - * @var bool - */ - public $_compileDirNormalized = false; - - /** - * flag if plugins_dir is normalized - * - * @var bool - */ - public $_pluginsDirNormalized = false; - - /** - * flag if template_dir is normalized - * - * @var bool - */ - public $_cacheDirNormalized = false; - - /** - * force template compiling? - * - * @var boolean - */ - public $force_compile = false; - - /** - * use sub dirs for compiled/cached files? - * - * @var boolean - */ - public $use_sub_dirs = false; - - /** - * allow ambiguous resources (that are made unique by the resource handler) - * - * @var boolean - */ - public $allow_ambiguous_resources = false; - - /** - * merge compiled includes - * - * @var boolean - */ - public $merge_compiled_includes = false; - - /* - * flag for behaviour when extends: resource and {extends} tag are used simultaneous - * if false disable execution of {extends} in templates called by extends resource. - * (behaviour as versions < 3.1.28) - * - * @var boolean - */ - public $extends_recursion = true; - - /** - * force cache file creation - * - * @var boolean - */ - public $force_cache = false; - - /** - * template left-delimiter - * - * @var string - */ - public $left_delimiter = "{"; - - /** - * template right-delimiter - * - * @var string - */ - public $right_delimiter = "}"; - - /** - * array of strings which shall be treated as literal by compiler - * - * @var array string - */ - public $literals = array(); - - /** - * class name - * This should be instance of Smarty_Security. - * - * @var string - * @see Smarty_Security - */ - public $security_class = 'Smarty_Security'; - - /** - * implementation of security class - * - * @var Smarty_Security - */ - public $security_policy = null; - - /** - * controls if the php template file resource is allowed - * - * @var bool - */ - public $allow_php_templates = false; - - /** - * debug mode - * Setting this to true enables the debug-console. - * - * @var boolean - */ - public $debugging = false; - - /** - * This determines if debugging is enable-able from the browser. - *
    - *
  • NONE => no debugging control allowed
  • - *
  • URL => enable debugging when SMARTY_DEBUG is found in the URL.
  • - *
- * - * @var string - */ - public $debugging_ctrl = 'NONE'; - - /** - * Name of debugging URL-param. - * Only used when $debugging_ctrl is set to 'URL'. - * The name of the URL-parameter that activates debugging. - * - * @var string - */ - public $smarty_debug_id = 'SMARTY_DEBUG'; - - /** - * Path of debug template. - * - * @var string - */ - public $debug_tpl = null; - - /** - * When set, smarty uses this value as error_reporting-level. - * - * @var int - */ - public $error_reporting = null; - - /** - * Controls whether variables with the same name overwrite each other. - * - * @var boolean - */ - public $config_overwrite = true; - - /** - * Controls whether config values of on/true/yes and off/false/no get converted to boolean. - * - * @var boolean - */ - public $config_booleanize = true; - - /** - * Controls whether hidden config sections/vars are read from the file. - * - * @var boolean - */ - public $config_read_hidden = false; - - /** - * locking concurrent compiles - * - * @var boolean - */ - public $compile_locking = true; - - /** - * Controls whether cache resources should use locking mechanism - * - * @var boolean - */ - public $cache_locking = false; - - /** - * seconds to wait for acquiring a lock before ignoring the write lock - * - * @var float - */ - public $locking_timeout = 10; - - /** - * resource type used if none given - * Must be an valid key of $registered_resources. - * - * @var string - */ - public $default_resource_type = 'file'; - - /** - * caching type - * Must be an element of $cache_resource_types. - * - * @var string - */ - public $caching_type = 'file'; - - /** - * config type - * - * @var string - */ - public $default_config_type = 'file'; - - /** - * check If-Modified-Since headers - * - * @var boolean - */ - public $cache_modified_check = false; - - /** - * registered plugins - * - * @var array - */ - public $registered_plugins = array(); - - /** - * registered objects - * - * @var array - */ - public $registered_objects = array(); - - /** - * registered classes - * - * @var array - */ - public $registered_classes = array(); - - /** - * registered filters - * - * @var array - */ - public $registered_filters = array(); - - /** - * registered resources - * - * @var array - */ - public $registered_resources = array(); - - /** - * registered cache resources - * - * @var array - */ - public $registered_cache_resources = array(); - - /** - * autoload filter - * - * @var array - */ - public $autoload_filters = array(); - - /** - * default modifier - * - * @var array - */ - public $default_modifiers = array(); - - /** - * autoescape variable output - * - * @var boolean - */ - public $escape_html = false; - - /** - * start time for execution time calculation - * - * @var int - */ - public $start_time = 0; - - /** - * required by the compiler for BC - * - * @var string - */ - public $_current_file = null; - - /** - * internal flag to enable parser debugging - * - * @var bool - */ - public $_parserdebug = false; - - /** - * This object type (Smarty = 1, template = 2, data = 4) - * - * @var int - */ - public $_objType = 1; - - /** - * Debug object - * - * @var Smarty_Internal_Debug - */ - public $_debug = null; - - /** - * template directory - * - * @var array - */ - protected $template_dir = array('./templates/'); - - /** - * flags for normalized template directory entries - * - * @var array - */ - protected $_processedTemplateDir = array(); - - /** - * config directory - * - * @var array - */ - protected $config_dir = array('./configs/'); - - /** - * flags for normalized template directory entries - * - * @var array - */ - protected $_processedConfigDir = array(); - - /** - * compile directory - * - * @var string - */ - protected $compile_dir = './templates_c/'; - - /** - * plugins directory - * - * @var array - */ - protected $plugins_dir = array(); - - /** - * cache directory - * - * @var string - */ - protected $cache_dir = './cache/'; - - /** - * removed properties - * - * @var string[] - */ - protected $obsoleteProperties = array( - 'resource_caching', 'template_resource_caching', 'direct_access_security', - '_dir_perms', '_file_perms', 'plugin_search_order', - 'inheritance_merge_compiled_includes', 'resource_cache_mode', - ); - - /** - * List of private properties which will call getter/setter on a direct access - * - * @var string[] - */ - protected $accessMap = array( - 'template_dir' => 'TemplateDir', 'config_dir' => 'ConfigDir', - 'plugins_dir' => 'PluginsDir', 'compile_dir' => 'CompileDir', - 'cache_dir' => 'CacheDir', - ); - - /** - * PHP7 Compatibility mode - * @var bool - */ - private $isMutingUndefinedOrNullWarnings = false; - - /** - * Initialize new Smarty object - */ - public function __construct() - { - $this->_clearTemplateCache(); - parent::__construct(); - if (is_callable('mb_internal_encoding')) { - mb_internal_encoding(Smarty::$_CHARSET); - } - $this->start_time = microtime(true); - if (isset($_SERVER[ 'SCRIPT_NAME' ])) { - Smarty::$global_tpl_vars[ 'SCRIPT_NAME' ] = new Smarty_Variable($_SERVER[ 'SCRIPT_NAME' ]); - } - // Check if we're running on windows - Smarty::$_IS_WINDOWS = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'; - // let PCRE (preg_*) treat strings as ISO-8859-1 if we're not dealing with UTF-8 - if (Smarty::$_CHARSET !== 'UTF-8') { - Smarty::$_UTF8_MODIFIER = ''; - } - } - - /** - * Check if a template resource exists - * - * @param string $resource_name template name - * - * @return bool status - * @throws \SmartyException - */ - public function templateExists($resource_name) - { - // create source object - $source = Smarty_Template_Source::load(null, $this, $resource_name); - return $source->exists; - } - - /** - * Loads security class and enables security - * - * @param string|Smarty_Security $security_class if a string is used, it must be class-name - * - * @return Smarty current Smarty instance for chaining - * @throws \SmartyException - */ - public function enableSecurity($security_class = null) - { - Smarty_Security::enableSecurity($this, $security_class); - return $this; - } - - /** - * Disable security - * - * @return Smarty current Smarty instance for chaining - */ - public function disableSecurity() - { - $this->security_policy = null; - return $this; - } - - /** - * Add template directory(s) - * - * @param string|array $template_dir directory(s) of template sources - * @param string $key of the array element to assign the template dir to - * @param bool $isConfig true for config_dir - * - * @return Smarty current Smarty instance for chaining - */ - public function addTemplateDir($template_dir, $key = null, $isConfig = false) - { - if ($isConfig) { - $processed = &$this->_processedConfigDir; - $dir = &$this->config_dir; - $this->_configDirNormalized = false; - } else { - $processed = &$this->_processedTemplateDir; - $dir = &$this->template_dir; - $this->_templateDirNormalized = false; - } - if (is_array($template_dir)) { - foreach ($template_dir as $k => $v) { - if (is_int($k)) { - // indexes are not merged but appended - $dir[] = $v; - } else { - // string indexes are overridden - $dir[ $k ] = $v; - unset($processed[ $key ]); - } - } - } else { - if ($key !== null) { - // override directory at specified index - $dir[ $key ] = $template_dir; - unset($processed[ $key ]); - } else { - // append new directory - $dir[] = $template_dir; - } - } - return $this; - } - - /** - * Get template directories - * - * @param mixed $index index of directory to get, null to get all - * @param bool $isConfig true for config_dir - * - * @return array|string list of template directories, or directory of $index - */ - public function getTemplateDir($index = null, $isConfig = false) - { - if ($isConfig) { - $dir = &$this->config_dir; - } else { - $dir = &$this->template_dir; - } - if ($isConfig ? !$this->_configDirNormalized : !$this->_templateDirNormalized) { - $this->_normalizeTemplateConfig($isConfig); - } - if ($index !== null) { - return isset($dir[ $index ]) ? $dir[ $index ] : null; - } - return $dir; - } - - /** - * Set template directory - * - * @param string|array $template_dir directory(s) of template sources - * @param bool $isConfig true for config_dir - * - * @return \Smarty current Smarty instance for chaining - */ - public function setTemplateDir($template_dir, $isConfig = false) - { - if ($isConfig) { - $this->config_dir = array(); - $this->_processedConfigDir = array(); - } else { - $this->template_dir = array(); - $this->_processedTemplateDir = array(); - } - $this->addTemplateDir($template_dir, null, $isConfig); - return $this; - } - - /** - * Add config directory(s) - * - * @param string|array $config_dir directory(s) of config sources - * @param mixed $key key of the array element to assign the config dir to - * - * @return Smarty current Smarty instance for chaining - */ - public function addConfigDir($config_dir, $key = null) - { - return $this->addTemplateDir($config_dir, $key, true); - } - - /** - * Get config directory - * - * @param mixed $index index of directory to get, null to get all - * - * @return array configuration directory - */ - public function getConfigDir($index = null) - { - return $this->getTemplateDir($index, true); - } - - /** - * Set config directory - * - * @param $config_dir - * - * @return Smarty current Smarty instance for chaining - */ - public function setConfigDir($config_dir) - { - return $this->setTemplateDir($config_dir, true); - } - - /** - * Adds directory of plugin files - * - * @param null|array|string $plugins_dir - * - * @return Smarty current Smarty instance for chaining - */ - public function addPluginsDir($plugins_dir) - { - if (empty($this->plugins_dir)) { - $this->plugins_dir[] = SMARTY_PLUGINS_DIR; - } - $this->plugins_dir = array_merge($this->plugins_dir, (array)$plugins_dir); - $this->_pluginsDirNormalized = false; - return $this; - } - - /** - * Get plugin directories - * - * @return array list of plugin directories - */ - public function getPluginsDir() - { - if (empty($this->plugins_dir)) { - $this->plugins_dir[] = SMARTY_PLUGINS_DIR; - $this->_pluginsDirNormalized = false; - } - if (!$this->_pluginsDirNormalized) { - if (!is_array($this->plugins_dir)) { - $this->plugins_dir = (array)$this->plugins_dir; - } - foreach ($this->plugins_dir as $k => $v) { - $this->plugins_dir[ $k ] = $this->_realpath(rtrim($v ?? '', '/\\') . DIRECTORY_SEPARATOR, true); - } - $this->_cache[ 'plugin_files' ] = array(); - $this->_pluginsDirNormalized = true; - } - return $this->plugins_dir; - } - - /** - * Set plugins directory - * - * @param string|array $plugins_dir directory(s) of plugins - * - * @return Smarty current Smarty instance for chaining - */ - public function setPluginsDir($plugins_dir) - { - $this->plugins_dir = (array)$plugins_dir; - $this->_pluginsDirNormalized = false; - return $this; - } - - /** - * Get compiled directory - * - * @return string path to compiled templates - */ - public function getCompileDir() - { - if (!$this->_compileDirNormalized) { - $this->_normalizeDir('compile_dir', $this->compile_dir); - $this->_compileDirNormalized = true; - } - return $this->compile_dir; - } - - /** - * - * @param string $compile_dir directory to store compiled templates in - * - * @return Smarty current Smarty instance for chaining - */ - public function setCompileDir($compile_dir) - { - $this->_normalizeDir('compile_dir', $compile_dir); - $this->_compileDirNormalized = true; - return $this; - } - - /** - * Get cache directory - * - * @return string path of cache directory - */ - public function getCacheDir() - { - if (!$this->_cacheDirNormalized) { - $this->_normalizeDir('cache_dir', $this->cache_dir); - $this->_cacheDirNormalized = true; - } - return $this->cache_dir; - } - - /** - * Set cache directory - * - * @param string $cache_dir directory to store cached templates in - * - * @return Smarty current Smarty instance for chaining - */ - public function setCacheDir($cache_dir) - { - $this->_normalizeDir('cache_dir', $cache_dir); - $this->_cacheDirNormalized = true; - return $this; - } - - /** - * creates a template object - * - * @param string $template the resource handle of the template file - * @param mixed $cache_id cache id to be used with this template - * @param mixed $compile_id compile id to be used with this template - * @param object $parent next higher level of Smarty variables - * @param boolean $do_clone flag is Smarty object shall be cloned - * - * @return \Smarty_Internal_Template template object - * @throws \SmartyException - */ - public function createTemplate($template, $cache_id = null, $compile_id = null, $parent = null, $do_clone = true) - { - if ($cache_id !== null && (is_object($cache_id) || is_array($cache_id))) { - $parent = $cache_id; - $cache_id = null; - } - if ($parent !== null && is_array($parent)) { - $data = $parent; - $parent = null; - } else { - $data = null; - } - if (!$this->_templateDirNormalized) { - $this->_normalizeTemplateConfig(false); - } - $_templateId = $this->_getTemplateId($template, $cache_id, $compile_id); - $tpl = null; - if ($this->caching && isset(Smarty_Internal_Template::$isCacheTplObj[ $_templateId ])) { - $tpl = $do_clone ? clone Smarty_Internal_Template::$isCacheTplObj[ $_templateId ] : - Smarty_Internal_Template::$isCacheTplObj[ $_templateId ]; - $tpl->inheritance = null; - $tpl->tpl_vars = $tpl->config_vars = array(); - } elseif (!$do_clone && isset(Smarty_Internal_Template::$tplObjCache[ $_templateId ])) { - $tpl = clone Smarty_Internal_Template::$tplObjCache[ $_templateId ]; - $tpl->inheritance = null; - $tpl->tpl_vars = $tpl->config_vars = array(); - } else { - /* @var Smarty_Internal_Template $tpl */ - $tpl = new $this->template_class($template, $this, null, $cache_id, $compile_id, null, null); - $tpl->templateId = $_templateId; - } - if ($do_clone) { - $tpl->smarty = clone $tpl->smarty; - } - $tpl->parent = $parent ? $parent : $this; - // fill data if present - if (!empty($data) && is_array($data)) { - // set up variable values - foreach ($data as $_key => $_val) { - $tpl->tpl_vars[ $_key ] = new Smarty_Variable($_val); - } - } - if ($this->debugging || $this->debugging_ctrl === 'URL') { - $tpl->smarty->_debug = new Smarty_Internal_Debug(); - // check URL debugging control - if (!$this->debugging && $this->debugging_ctrl === 'URL') { - $tpl->smarty->_debug->debugUrl($tpl->smarty); - } - } - return $tpl; - } - - /** - * Takes unknown classes and loads plugin files for them - * class name format: Smarty_PluginType_PluginName - * plugin filename format: plugintype.pluginname.php - * - * @param string $plugin_name class plugin name to load - * @param bool $check check if already loaded - * - * @return string |boolean filepath of loaded file or false - * @throws \SmartyException - */ - public function loadPlugin($plugin_name, $check = true) - { - return $this->ext->loadPlugin->loadPlugin($this, $plugin_name, $check); - } - - /** - * Get unique template id - * - * @param string $template_name - * @param null|mixed $cache_id - * @param null|mixed $compile_id - * @param null $caching - * @param \Smarty_Internal_Template $template - * - * @return string - * @throws \SmartyException - */ - public function _getTemplateId( - $template_name, - $cache_id = null, - $compile_id = null, - $caching = null, - Smarty_Internal_Template $template = null - ) { - $template_name = (strpos($template_name, ':') === false) ? "{$this->default_resource_type}:{$template_name}" : - $template_name; - $cache_id = $cache_id === null ? $this->cache_id : $cache_id; - $compile_id = $compile_id === null ? $this->compile_id : $compile_id; - $caching = (int)($caching === null ? $this->caching : $caching); - if ((isset($template) && strpos($template_name, ':.') !== false) || $this->allow_ambiguous_resources) { - $_templateId = - Smarty_Resource::getUniqueTemplateName((isset($template) ? $template : $this), $template_name) . - "#{$cache_id}#{$compile_id}#{$caching}"; - } else { - $_templateId = $this->_joined_template_dir . "#{$template_name}#{$cache_id}#{$compile_id}#{$caching}"; - } - if (isset($_templateId[ 150 ])) { - $_templateId = sha1($_templateId); - } - return $_templateId; - } - - /** - * Normalize path - * - remove /./ and /../ - * - make it absolute if required - * - * @param string $path file path - * @param bool $realpath if true - convert to absolute - * false - convert to relative - * null - keep as it is but - * remove /./ /../ - * - * @return string - */ - public function _realpath($path, $realpath = null) - { - $nds = array('/' => '\\', '\\' => '/'); - preg_match( - '%^(?(?:[[:alpha:]]:[\\\\/]|/|[\\\\]{2}[[:alpha:]]+|[[:print:]]{2,}:[/]{2}|[\\\\])?)(?(.*))$%u', - $path, - $parts - ); - $path = $parts[ 'path' ]; - if ($parts[ 'root' ] === '\\') { - $parts[ 'root' ] = substr(getcwd(), 0, 2) . $parts[ 'root' ]; - } else { - if ($realpath !== null && !$parts[ 'root' ]) { - $path = getcwd() . DIRECTORY_SEPARATOR . $path; - } - } - // normalize DIRECTORY_SEPARATOR - $path = str_replace($nds[ DIRECTORY_SEPARATOR ], DIRECTORY_SEPARATOR, $path); - $parts[ 'root' ] = str_replace($nds[ DIRECTORY_SEPARATOR ], DIRECTORY_SEPARATOR, $parts[ 'root' ]); - do { - $path = preg_replace( - array('#[\\\\/]{2}#', '#[\\\\/][.][\\\\/]#', '#[\\\\/]([^\\\\/.]+)[\\\\/][.][.][\\\\/]#'), - DIRECTORY_SEPARATOR, - $path, - -1, - $count - ); - } while ($count > 0); - return $realpath !== false ? $parts[ 'root' ] . $path : str_ireplace(getcwd(), '.', $parts[ 'root' ] . $path); - } - - /** - * Empty template objects cache - */ - public function _clearTemplateCache() - { - Smarty_Internal_Template::$isCacheTplObj = array(); - Smarty_Internal_Template::$tplObjCache = array(); - } - - /** - * @param boolean $use_sub_dirs - */ - public function setUseSubDirs($use_sub_dirs) - { - $this->use_sub_dirs = $use_sub_dirs; - } - - /** - * @param int $error_reporting - */ - public function setErrorReporting($error_reporting) - { - $this->error_reporting = $error_reporting; - } - - /** - * @param boolean $escape_html - */ - public function setEscapeHtml($escape_html) - { - $this->escape_html = $escape_html; - } - - /** - * Return auto_literal flag - * - * @return boolean - */ - public function getAutoLiteral() - { - return $this->auto_literal; - } - - /** - * Set auto_literal flag - * - * @param boolean $auto_literal - */ - public function setAutoLiteral($auto_literal = true) - { - $this->auto_literal = $auto_literal; - } - - /** - * @param boolean $force_compile - */ - public function setForceCompile($force_compile) - { - $this->force_compile = $force_compile; - } - - /** - * @param boolean $merge_compiled_includes - */ - public function setMergeCompiledIncludes($merge_compiled_includes) - { - $this->merge_compiled_includes = $merge_compiled_includes; - } - - /** - * Get left delimiter - * - * @return string - */ - public function getLeftDelimiter() - { - return $this->left_delimiter; - } - - /** - * Set left delimiter - * - * @param string $left_delimiter - */ - public function setLeftDelimiter($left_delimiter) - { - $this->left_delimiter = $left_delimiter; - } - - /** - * Get right delimiter - * - * @return string $right_delimiter - */ - public function getRightDelimiter() - { - return $this->right_delimiter; - } - - /** - * Set right delimiter - * - * @param string - */ - public function setRightDelimiter($right_delimiter) - { - $this->right_delimiter = $right_delimiter; - } - - /** - * @param boolean $debugging - */ - public function setDebugging($debugging) - { - $this->debugging = $debugging; - } - - /** - * @param boolean $config_overwrite - */ - public function setConfigOverwrite($config_overwrite) - { - $this->config_overwrite = $config_overwrite; - } - - /** - * @param boolean $config_booleanize - */ - public function setConfigBooleanize($config_booleanize) - { - $this->config_booleanize = $config_booleanize; - } - - /** - * @param boolean $config_read_hidden - */ - public function setConfigReadHidden($config_read_hidden) - { - $this->config_read_hidden = $config_read_hidden; - } - - /** - * @param boolean $compile_locking - */ - public function setCompileLocking($compile_locking) - { - $this->compile_locking = $compile_locking; - } - - /** - * @param string $default_resource_type - */ - public function setDefaultResourceType($default_resource_type) - { - $this->default_resource_type = $default_resource_type; - } - - /** - * @param string $caching_type - */ - public function setCachingType($caching_type) - { - $this->caching_type = $caching_type; - } - - /** - * Test install - * - * @param null $errors - */ - public function testInstall(&$errors = null) - { - Smarty_Internal_TestInstall::testInstall($this, $errors); - } - - /** - * Get Smarty object - * - * @return Smarty - */ - public function _getSmartyObj() - { - return $this; - } - - /** - * <> Generic getter. - * Calls the appropriate getter function. - * Issues an E_USER_NOTICE if no valid getter is found. - * - * @param string $name property name - * - * @return mixed - */ - public function __get($name) - { - if (isset($this->accessMap[ $name ])) { - $method = 'get' . $this->accessMap[ $name ]; - return $this->{$method}(); - } elseif (isset($this->_cache[ $name ])) { - return $this->_cache[ $name ]; - } elseif (in_array($name, $this->obsoleteProperties)) { - return null; - } else { - trigger_error('Undefined property: ' . get_class($this) . '::$' . $name, E_USER_NOTICE); - } - return null; - } - - /** - * <> Generic setter. - * Calls the appropriate setter function. - * Issues an E_USER_NOTICE if no valid setter is found. - * - * @param string $name property name - * @param mixed $value parameter passed to setter - * - */ - public function __set($name, $value) - { - if (isset($this->accessMap[ $name ])) { - $method = 'set' . $this->accessMap[ $name ]; - $this->{$method}($value); - } elseif (in_array($name, $this->obsoleteProperties)) { - return; - } elseif (is_object($value) && method_exists($value, $name)) { - $this->$name = $value; - } else { - trigger_error('Undefined property: ' . get_class($this) . '::$' . $name, E_USER_NOTICE); - } - } - - /** - * Normalize and set directory string - * - * @param string $dirName cache_dir or compile_dir - * @param string $dir filepath of folder - */ - private function _normalizeDir($dirName, $dir) - { - $this->{$dirName} = $this->_realpath(rtrim($dir ?? '', "/\\") . DIRECTORY_SEPARATOR, true); - } - - /** - * Normalize template_dir or config_dir - * - * @param bool $isConfig true for config_dir - */ - private function _normalizeTemplateConfig($isConfig) - { - if ($isConfig) { - $processed = &$this->_processedConfigDir; - $dir = &$this->config_dir; - } else { - $processed = &$this->_processedTemplateDir; - $dir = &$this->template_dir; - } - if (!is_array($dir)) { - $dir = (array)$dir; - } - foreach ($dir as $k => $v) { - if (!isset($processed[ $k ])) { - $dir[ $k ] = $v = $this->_realpath(rtrim($v ?? '', "/\\") . DIRECTORY_SEPARATOR, true); - $processed[ $k ] = true; - } - } - $isConfig ? $this->_configDirNormalized = true : $this->_templateDirNormalized = true; - $isConfig ? $this->_joined_config_dir = join('#', $this->config_dir) : - $this->_joined_template_dir = join('#', $this->template_dir); - } - - /** - * Mutes errors for "undefined index", "undefined array key" and "trying to read property of null". - * - * @void - */ - public function muteUndefinedOrNullWarnings(): void { - $this->isMutingUndefinedOrNullWarnings = true; - } - - /** - * Indicates if Smarty will mute errors for "undefined index", "undefined array key" and "trying to read property of null". - * @bool - */ - public function isMutingUndefinedOrNullWarnings(): bool { - return $this->isMutingUndefinedOrNullWarnings; - } - -} diff --git a/www/vendor/egrajp/smarty-extended/src/bootstrap.php b/www/vendor/egrajp/smarty-extended/src/bootstrap.php deleted file mode 100644 index a226ac04..00000000 --- a/www/vendor/egrajp/smarty-extended/src/bootstrap.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -/** - * Load and register Smarty Autoloader - */ -if (!class_exists('Smarty_Autoloader')) { - include __DIR__ . '/Autoloader.php'; -} -Smarty_Autoloader::register(true); diff --git a/www/vendor/egrajp/smarty-extended/src/debug.tpl b/www/vendor/egrajp/smarty-extended/src/debug.tpl deleted file mode 100644 index cd932566..00000000 --- a/www/vendor/egrajp/smarty-extended/src/debug.tpl +++ /dev/null @@ -1,173 +0,0 @@ -{capture name='_smarty_debug' assign=debug_output} - - - - Smarty Debug Console - - - - -

Smarty {Smarty::SMARTY_VERSION} Debug Console - - {if isset($template_name)}{$template_name|debug_print_var nofilter} {/if}{if !empty($template_data)}Total Time {$execution_time|string_format:"%.5f"}{/if}

- - {if !empty($template_data)} -

included templates & config files (load time in seconds)

-
- {foreach $template_data as $template} - {$template.name} -
   - (compile {$template['compile_time']|string_format:"%.5f"}) (render {$template['render_time']|string_format:"%.5f"}) (cache {$template['cache_time']|string_format:"%.5f"}) - -
- {/foreach} -
- {/if} - -

assigned template variables

- - - {foreach $assigned_vars as $vars} - - - - - {/foreach} -
-

${$vars@key}

- {if isset($vars['nocache'])}Nocache
{/if} - {if isset($vars['scope'])}Origin: {$vars['scope']|debug_print_var nofilter}{/if} -
-

Value

- {$vars['value']|debug_print_var:10:80 nofilter} -
- {if isset($vars['attributes'])} -

Attributes

- {$vars['attributes']|debug_print_var nofilter} - {/if} -
- -

assigned config file variables

- - - {foreach $config_vars as $vars} - - - - - {/foreach} - -
-

#{$vars@key}#

- {if isset($vars['scope'])}Origin: {$vars['scope']|debug_print_var nofilter}{/if} -
- {$vars['value']|debug_print_var:10:80 nofilter} -
- - -{/capture} - diff --git a/www/vendor/egrajp/smarty-extended/src/functions.php b/www/vendor/egrajp/smarty-extended/src/functions.php deleted file mode 100644 index bac00e52..00000000 --- a/www/vendor/egrajp/smarty-extended/src/functions.php +++ /dev/null @@ -1,51 +0,0 @@ -register_block('t', 'smarty_translate'); - * - * NOTE: native php support for conext sensitive does not exist - * Those jumps are disabled - * - * @package smarty-gettext - * @version $Id: block.t.php 4738 2022-05-06 01:28:48Z clemens $ - * @link http://smarty-gettext.sf.net/ - * @author Sagi Bashari - * @copyright 2004 Sagi Bashari - * @copyright Elan Ruusamäe - * @copyright Clemens Schwaighofer - */ - -/** - * Replaces arguments in a string with their values. - * Arguments are represented by % followed by their number. - * - * @param string $str Source string - * @param mixed mixed Arguments, can be passed in an array or through single variables. - * @return string Modified string - */ -function smarty_gettext_strarg($str/*, $varargs... */) -{ - $tr = []; - $p = 0; - - $nargs = func_num_args(); - for ($i = 1; $i < $nargs; $i++) { - $arg = func_get_arg($i); - - if (is_array($arg)) { - foreach ($arg as $aarg) { - $tr['%' . ++$p] = $aarg; - } - } else { - $tr['%' . ++$p] = $arg; - } - } - - return strtr($str, $tr); -} - -/** - * Smarty block function, provides gettext support for smarty. - * - * The block content is the text that should be translated. - * - * Any parameter that is sent to the function will be represented as %n in the translation text, - * where n is 1 for the first parameter. The following parameters are reserved: - * - escape - sets escape mode: - * - 'html' for HTML escaping, this is the default. - * - 'js' for javascript escaping. - * - 'url' for url escaping. - * - 'no'/'off'/0 - turns off escaping - * - plural - The plural version of the text (2nd parameter of ngettext()) - * - count - The item count for plural mode (3rd parameter of ngettext()) - * - domain - Textdomain to be used, default if skipped (dgettext() instead of gettext()) - * - context - gettext context. reserved for future use. - * - */ - -// cs modified: __ calls instead of direct gettext calls - -function smarty_block_t($params, $text) -{ - if (!isset($text)) { - return $text; - } - - // set escape mode, default html escape - if (isset($params['escape'])) { - $escape = $params['escape']; - unset($params['escape']); - } else { - $escape = 'html'; - } - - // set plural parameters 'plural' and 'count'. - if (isset($params['plural'])) { - $plural = $params['plural']; - unset($params['plural']); - - // set count - if (isset($params['count'])) { - $count = $params['count']; - unset($params['count']); - } - } - - // get domain param - if (isset($params['domain'])) { - $domain = $params['domain']; - unset($params['domain']); - } else { - $domain = null; - } - - // get context param - if (isset($params['context'])) { - $context = $params['context']; - unset($params['context']); - } else { - $context = null; - } - - // use plural if required parameters are set - if (isset($count) && isset($plural)) { - if (isset($domain) && isset($context)) { - if (is_callable('_dnpgettext')) { - $text = _dnpgettext($domain, $context, $text, $plural, $count); - }/* elseif (is_callable('dnpgettext')) { - $text = dnpgettext($domain, $context, $text, $plural, $count); - } */ - } elseif (isset($domain)) { - if (is_callable('_dngettext')) { - $text = _dngettext($domain, $text, $plural, $count); - } elseif (is_callable('dngettext')) { - $text = dngettext($domain, $text, $plural, $count); - } - } elseif (isset($context)) { - if (is_callable('_npgettext')) { - $text = _npgettext($context, $text, $plural, $count); - }/* elseif (is_callable('npgettext')) { - $text = npgettext($context, $text, $plural, $count); - } */ - } else { - if (is_callable('_ngettext')) { - $text = _ngettext($text, $plural, $count); - } elseif (is_callable('ngettext')) { - $text = ngettext($text, $plural, $count); - } - } - } else { // use normal - if (isset($domain) && isset($context)) { - if (is_callable('_dpgettext')) { - $text = _dpgettext($domain, $context, $text); - }/* elseif (is_callable('dpgettext')) { - $text = dpgettext($domain, $context, $text); - } */ - } elseif (isset($domain)) { - if (is_callable('_dgettext')) { - $text = _dgettext($domain, $text); - } elseif (is_callable('dpgettext')) { - $text = dgettext($domain, $text); - } - } elseif (isset($context)) { - if (is_callable('_pgettext')) { - $text = _pgettext($context, $text); - }/* elseif (is_callable('pgettext')) { - $text = pgettext($context, $text); - } */ - } else { - if (is_callable('_gettext')) { - $text = _gettext($text); - } elseif (is_callable('gettext')) { - $text = gettext($text); - } - } - } - - // run strarg if there are parameters - if (count($params)) { - $text = smarty_gettext_strarg($text, $params); - } - - switch ($escape) { - case 'html': - // default - $text = nl2br(htmlspecialchars($text)); - break; - case 'javascript': - case 'js': - // javascript escape - $text = strtr( - $text, - [ - '\\' => '\\\\', - "'" => "\\'", - '"' => '\\"', - "\r" => '\\r', - "\n" => '\\n', - ' '<\/' - ] - ); - break; - case 'url': - // url escape - $text = urlencode($text); - break; - // below is a list for explicit OFF - case 'no': - case 'off': - case 'false': - case '0': - case 0: - // explicit OFF - default: - break; - } - - return $text; -} - -// __END__ diff --git a/www/vendor/egrajp/smarty-extended/src/plugins/block.textformat.php b/www/vendor/egrajp/smarty-extended/src/plugins/block.textformat.php deleted file mode 100644 index fed090e4..00000000 --- a/www/vendor/egrajp/smarty-extended/src/plugins/block.textformat.php +++ /dev/null @@ -1,121 +0,0 @@ - - * @throws \SmartyException - */ -function smarty_block_textformat($params, $content, Smarty_Internal_Template $template, &$repeat) -{ - if (is_null($content)) { - return; - } - if (Smarty::$_MBSTRING) { - $template->_checkPlugins( - array( - array( - 'function' => 'smarty_modifier_mb_wordwrap', - 'file' => SMARTY_PLUGINS_DIR . 'modifier.mb_wordwrap.php' - ) - ) - ); - } - $style = null; - $indent = 0; - $indent_first = 0; - $indent_char = ' '; - $wrap = 80; - $wrap_char = "\n"; - $wrap_cut = false; - $assign = null; - foreach ($params as $_key => $_val) { - switch ($_key) { - case 'style': - case 'indent_char': - case 'wrap_char': - case 'assign': - $$_key = (string)$_val; - break; - case 'indent': - case 'indent_first': - case 'wrap': - $$_key = (int)$_val; - break; - case 'wrap_cut': - $$_key = (bool)$_val; - break; - default: - trigger_error("textformat: unknown attribute '{$_key}'"); - } - } - if ($style === 'email') { - $wrap = 72; - } - // split into paragraphs - $_paragraphs = preg_split('![\r\n]{2}!', $content); - foreach ($_paragraphs as &$_paragraph) { - if (!$_paragraph) { - continue; - } - // convert mult. spaces & special chars to single space - $_paragraph = - preg_replace( - array( - '!\s+!' . Smarty::$_UTF8_MODIFIER, - '!(^\s+)|(\s+$)!' . Smarty::$_UTF8_MODIFIER - ), - array( - ' ', - '' - ), - $_paragraph - ); - // indent first line - if ($indent_first > 0) { - $_paragraph = str_repeat($indent_char, $indent_first) . $_paragraph; - } - // wordwrap sentences - if (Smarty::$_MBSTRING) { - $_paragraph = smarty_modifier_mb_wordwrap($_paragraph, $wrap - $indent, $wrap_char, $wrap_cut); - } else { - $_paragraph = wordwrap($_paragraph, $wrap - $indent, $wrap_char, $wrap_cut); - } - // indent lines - if ($indent > 0) { - $_paragraph = preg_replace('!^!m', str_repeat($indent_char, $indent), $_paragraph); - } - } - $_output = implode($wrap_char . $wrap_char, $_paragraphs); - if ($assign) { - $template->assign($assign, $_output); - } else { - return $_output; - } -} diff --git a/www/vendor/egrajp/smarty-extended/src/plugins/function.counter.php b/www/vendor/egrajp/smarty-extended/src/plugins/function.counter.php deleted file mode 100644 index 54795459..00000000 --- a/www/vendor/egrajp/smarty-extended/src/plugins/function.counter.php +++ /dev/null @@ -1,62 +0,0 @@ - - * @link https://www.smarty.net/manual/en/language.function.counter.php {counter} - * (Smarty online manual) - * - * @param array $params parameters - * @param Smarty_Internal_Template $template template object - * - * @return string|null - */ -function smarty_function_counter($params, $template) -{ - static $counters = array(); - $name = (isset($params[ 'name' ])) ? $params[ 'name' ] : 'default'; - if (!isset($counters[ $name ])) { - $counters[ $name ] = array('start' => 1, 'skip' => 1, 'direction' => 'up', 'count' => 1); - } - $counter =& $counters[ $name ]; - if (isset($params[ 'start' ])) { - $counter[ 'start' ] = $counter[ 'count' ] = (int)$params[ 'start' ]; - } - if (!empty($params[ 'assign' ])) { - $counter[ 'assign' ] = $params[ 'assign' ]; - } - if (isset($counter[ 'assign' ])) { - $template->assign($counter[ 'assign' ], $counter[ 'count' ]); - } - if (isset($params[ 'print' ])) { - $print = (bool)$params[ 'print' ]; - } else { - $print = empty($counter[ 'assign' ]); - } - if ($print) { - $retval = $counter[ 'count' ]; - } else { - $retval = null; - } - if (isset($params[ 'skip' ])) { - $counter[ 'skip' ] = $params[ 'skip' ]; - } - if (isset($params[ 'direction' ])) { - $counter[ 'direction' ] = $params[ 'direction' ]; - } - if ($counter[ 'direction' ] === 'down') { - $counter[ 'count' ] -= $counter[ 'skip' ]; - } else { - $counter[ 'count' ] += $counter[ 'skip' ]; - } - return $retval; -} diff --git a/www/vendor/egrajp/smarty-extended/src/plugins/function.cycle.php b/www/vendor/egrajp/smarty-extended/src/plugins/function.cycle.php deleted file mode 100644 index 79356999..00000000 --- a/www/vendor/egrajp/smarty-extended/src/plugins/function.cycle.php +++ /dev/null @@ -1,92 +0,0 @@ - - * @author credit to Mark Priatel - * @author credit to Gerard - * @author credit to Jason Sweat - * @version 1.3 - * - * @param array $params parameters - * @param Smarty_Internal_Template $template template object - * - * @return string|null - */ -function smarty_function_cycle($params, $template) -{ - static $cycle_vars; - $name = (empty($params[ 'name' ])) ? 'default' : $params[ 'name' ]; - $print = (isset($params[ 'print' ])) ? (bool)$params[ 'print' ] : true; - $advance = (isset($params[ 'advance' ])) ? (bool)$params[ 'advance' ] : true; - $reset = (isset($params[ 'reset' ])) ? (bool)$params[ 'reset' ] : false; - if (!isset($params[ 'values' ])) { - if (!isset($cycle_vars[ $name ][ 'values' ])) { - trigger_error('cycle: missing \'values\' parameter'); - return; - } - } else { - if (isset($cycle_vars[ $name ][ 'values' ]) && $cycle_vars[ $name ][ 'values' ] !== $params[ 'values' ]) { - $cycle_vars[ $name ][ 'index' ] = 0; - } - $cycle_vars[ $name ][ 'values' ] = $params[ 'values' ]; - } - if (isset($params[ 'delimiter' ])) { - $cycle_vars[ $name ][ 'delimiter' ] = $params[ 'delimiter' ]; - } elseif (!isset($cycle_vars[ $name ][ 'delimiter' ])) { - $cycle_vars[ $name ][ 'delimiter' ] = ','; - } - if (is_array($cycle_vars[ $name ][ 'values' ])) { - $cycle_array = $cycle_vars[ $name ][ 'values' ]; - } else { - $cycle_array = explode($cycle_vars[ $name ][ 'delimiter' ], $cycle_vars[ $name ][ 'values' ]); - } - if (!isset($cycle_vars[ $name ][ 'index' ]) || $reset) { - $cycle_vars[ $name ][ 'index' ] = 0; - } - if (isset($params[ 'assign' ])) { - $print = false; - $template->assign($params[ 'assign' ], $cycle_array[ $cycle_vars[ $name ][ 'index' ] ]); - } - if ($print) { - $retval = $cycle_array[ $cycle_vars[ $name ][ 'index' ] ]; - } else { - $retval = null; - } - if ($advance) { - if ($cycle_vars[ $name ][ 'index' ] >= count($cycle_array) - 1) { - $cycle_vars[ $name ][ 'index' ] = 0; - } else { - $cycle_vars[ $name ][ 'index' ]++; - } - } - return $retval; -} diff --git a/www/vendor/egrajp/smarty-extended/src/plugins/function.fetch.php b/www/vendor/egrajp/smarty-extended/src/plugins/function.fetch.php deleted file mode 100644 index 4a3e8819..00000000 --- a/www/vendor/egrajp/smarty-extended/src/plugins/function.fetch.php +++ /dev/null @@ -1,204 +0,0 @@ - - * - * @param array $params parameters - * @param Smarty_Internal_Template $template template object - * - * @throws SmartyException - * @return string|null if the assign parameter is passed, Smarty assigns the result to a template variable - */ -function smarty_function_fetch($params, $template) -{ - if (empty($params[ 'file' ])) { - trigger_error('[plugin] fetch parameter \'file\' cannot be empty', E_USER_NOTICE); - return; - } - // strip file protocol - if (stripos($params[ 'file' ], 'file://') === 0) { - $params[ 'file' ] = substr($params[ 'file' ], 7); - } - $protocol = strpos($params[ 'file' ], '://'); - if ($protocol !== false) { - $protocol = strtolower(substr($params[ 'file' ], 0, $protocol)); - } - if (isset($template->smarty->security_policy)) { - if ($protocol) { - // remote resource (or php stream, …) - if (!$template->smarty->security_policy->isTrustedUri($params[ 'file' ])) { - return; - } - } else { - // local file - if (!$template->smarty->security_policy->isTrustedResourceDir($params[ 'file' ])) { - return; - } - } - } - $content = ''; - if ($protocol === 'http') { - // http fetch - if ($uri_parts = parse_url($params[ 'file' ])) { - // set defaults - $host = $server_name = $uri_parts[ 'host' ]; - $timeout = 30; - $accept = 'image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*'; - $agent = 'Smarty Template Engine ' . Smarty::SMARTY_VERSION; - $referer = ''; - $uri = !empty($uri_parts[ 'path' ]) ? $uri_parts[ 'path' ] : '/'; - $uri .= !empty($uri_parts[ 'query' ]) ? '?' . $uri_parts[ 'query' ] : ''; - $_is_proxy = false; - if (empty($uri_parts[ 'port' ])) { - $port = 80; - } else { - $port = $uri_parts[ 'port' ]; - } - if (!empty($uri_parts[ 'user' ])) { - $user = $uri_parts[ 'user' ]; - } - if (!empty($uri_parts[ 'pass' ])) { - $pass = $uri_parts[ 'pass' ]; - } - // loop through parameters, setup headers - foreach ($params as $param_key => $param_value) { - switch ($param_key) { - case 'file': - case 'assign': - case 'assign_headers': - break; - case 'user': - if (!empty($param_value)) { - $user = $param_value; - } - break; - case 'pass': - if (!empty($param_value)) { - $pass = $param_value; - } - break; - case 'accept': - if (!empty($param_value)) { - $accept = $param_value; - } - break; - case 'header': - if (!empty($param_value)) { - if (!preg_match('![\w\d-]+: .+!', $param_value)) { - trigger_error("[plugin] invalid header format '{$param_value}'", E_USER_NOTICE); - return; - } else { - $extra_headers[] = $param_value; - } - } - break; - case 'proxy_host': - if (!empty($param_value)) { - $proxy_host = $param_value; - } - break; - case 'proxy_port': - if (!preg_match('!\D!', $param_value)) { - $proxy_port = (int)$param_value; - } else { - trigger_error("[plugin] invalid value for attribute '{$param_key }'", E_USER_NOTICE); - return; - } - break; - case 'agent': - if (!empty($param_value)) { - $agent = $param_value; - } - break; - case 'referer': - if (!empty($param_value)) { - $referer = $param_value; - } - break; - case 'timeout': - if (!preg_match('!\D!', $param_value)) { - $timeout = (int)$param_value; - } else { - trigger_error("[plugin] invalid value for attribute '{$param_key}'", E_USER_NOTICE); - return; - } - break; - default: - trigger_error("[plugin] unrecognized attribute '{$param_key}'", E_USER_NOTICE); - return; - } - } - if (!empty($proxy_host) && !empty($proxy_port)) { - $_is_proxy = true; - $fp = fsockopen($proxy_host, $proxy_port, $errno, $errstr, $timeout); - } else { - $fp = fsockopen($server_name, $port, $errno, $errstr, $timeout); - } - if (!$fp) { - trigger_error("[plugin] unable to fetch: $errstr ($errno)", E_USER_NOTICE); - return; - } else { - if ($_is_proxy) { - fputs($fp, 'GET ' . $params[ 'file' ] . " HTTP/1.0\r\n"); - } else { - fputs($fp, "GET $uri HTTP/1.0\r\n"); - } - if (!empty($host)) { - fputs($fp, "Host: $host\r\n"); - } - if (!empty($accept)) { - fputs($fp, "Accept: $accept\r\n"); - } - if (!empty($agent)) { - fputs($fp, "User-Agent: $agent\r\n"); - } - if (!empty($referer)) { - fputs($fp, "Referer: $referer\r\n"); - } - if (isset($extra_headers) && is_array($extra_headers)) { - foreach ($extra_headers as $curr_header) { - fputs($fp, $curr_header . "\r\n"); - } - } - if (!empty($user) && !empty($pass)) { - fputs($fp, 'Authorization: BASIC ' . base64_encode("$user:$pass") . "\r\n"); - } - fputs($fp, "\r\n"); - while (!feof($fp)) { - $content .= fgets($fp, 4096); - } - fclose($fp); - $csplit = preg_split("!\r\n\r\n!", $content, 2); - $content = $csplit[ 1 ]; - if (!empty($params[ 'assign_headers' ])) { - $template->assign($params[ 'assign_headers' ], preg_split("!\r\n!", $csplit[ 0 ])); - } - } - } else { - trigger_error("[plugin fetch] unable to parse URL, check syntax", E_USER_NOTICE); - return; - } - } else { - $content = @file_get_contents($params[ 'file' ]); - if ($content === false) { - throw new SmartyException("{fetch} cannot read resource '" . $params[ 'file' ] . "'"); - } - } - if (!empty($params[ 'assign' ])) { - $template->assign($params[ 'assign' ], $content); - } else { - return $content; - } -} diff --git a/www/vendor/egrajp/smarty-extended/src/plugins/function.html_checkboxes.php b/www/vendor/egrajp/smarty-extended/src/plugins/function.html_checkboxes.php deleted file mode 100644 index 0ed418ec..00000000 --- a/www/vendor/egrajp/smarty-extended/src/plugins/function.html_checkboxes.php +++ /dev/null @@ -1,296 +0,0 @@ -' output=$names} - * {html_checkboxes values=$ids checked=$checked separator='
' output=$names} - * - * Params: - * - * - name (optional) - string default "checkbox" - * - values (required) - array - * - options (optional) - associative array - * - checked (optional) - array default not set - * - separator (optional) - ie
or   - * - output (optional) - the output next to each checkbox - * - assign (optional) - assign the output as an array to this variable - * - escape (optional) - escape the content (not value), defaults to true - * - * @link https://www.smarty.net/manual/en/language.function.html.checkboxes.php {html_checkboxes} - * (Smarty online manual) - * @author Christopher Kvarme - * @author credits to Monte Ohrt - * @version 1.0 - * - * @param array $params parameters - * @param Smarty_Internal_Template $template template object - * - * @return string - * @uses smarty_function_escape_special_chars() - * @throws \SmartyException - */ -function smarty_function_html_checkboxes($params, Smarty_Internal_Template $template) -{ - $template->_checkPlugins( - array( - array( - 'function' => 'smarty_function_escape_special_chars', - 'file' => SMARTY_PLUGINS_DIR . 'shared.escape_special_chars.php' - ) - ) - ); - $name = 'checkbox'; - $values = null; - $options = null; - $selected = array(); - $separator = ''; - $escape = true; - $labels = true; - $label_ids = false; - $output = null; - $pos = null; - $extra = ''; - foreach ($params as $_key => $_val) { - switch ($_key) { - case 'name': - case 'separator': - $$_key = (string)$_val; - break; - case 'escape': - case 'labels': - case 'label_ids': - $$_key = (bool)$_val; - break; - case 'options': - $$_key = (array)$_val; - break; - case 'values': - case 'output': - $$_key = array_values((array)$_val); - break; - case 'checked': - case 'selected': - if (is_array($_val)) { - $selected = array(); - foreach ($_val as $_sel) { - if (is_object($_sel)) { - if (method_exists($_sel, '__toString')) { - $_sel = smarty_function_escape_special_chars((string)$_sel->__toString()); - } else { - trigger_error( - 'html_checkboxes: selected attribute contains an object of class \'' . - get_class($_sel) . '\' without __toString() method', - E_USER_NOTICE - ); - continue; - } - } else { - $_sel = smarty_function_escape_special_chars((string)$_sel); - } - $selected[ $_sel ] = true; - } - } elseif (is_object($_val)) { - if (method_exists($_val, '__toString')) { - $selected = smarty_function_escape_special_chars((string)$_val->__toString()); - } else { - trigger_error( - 'html_checkboxes: selected attribute is an object of class \'' . get_class($_val) . - '\' without __toString() method', - E_USER_NOTICE - ); - } - } else { - $selected = smarty_function_escape_special_chars((string)$_val); - } - break; - case 'checkboxes': - trigger_error( - 'html_checkboxes: the use of the "checkboxes" attribute is deprecated, use "options" instead', - E_USER_WARNING - ); - $options = (array)$_val; - break; - case 'assign': - break; - case 'pos': - $$_key = array_values((array)$_val); - break; - case 'strict': - break; - case 'disabled': - case 'readonly': - if (!empty($params[ 'strict' ])) { - if (!is_scalar($_val)) { - trigger_error( - "html_options: {$_key} attribute must be a scalar, only boolean true or string '{$_key}' will actually add the attribute", - E_USER_NOTICE - ); - } - if ($_val === true || $_val === $_key) { - $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_key) . '"'; - } - break; - } - // omit break; to fall through! - // no break - default: - if (!is_array($_val)) { - $extra .= ' ' . $_key . '="' . smarty_function_escape_special_chars($_val) . '"'; - } else { - trigger_error("html_checkboxes: extra attribute '{$_key}' cannot be an array", E_USER_NOTICE); - } - break; - } - } - if (!isset($options) && !isset($values)) { - return ''; - } /* raise error here? */ - $_html_result = array(); - if (isset($options)) { - foreach ($options as $_key => $_val) { - $_pos = isset($pos[ $_key ]) ? $pos[ $_key ] : ''; - $_html_result[] = - smarty_function_html_checkboxes_output( - $name, - $_key, - $_val, - $selected, - $extra, - $separator, - $labels, - $label_ids, - $_pos, - $escape - ); - } - } else { - foreach ($values as $_i => $_key) { - $_val = isset($output[ $_i ]) ? $output[ $_i ] : ''; - $_pos = isset($pos[ $_i ]) ? $pos[ $_i ] : ''; - $_html_result[] = - smarty_function_html_checkboxes_output( - $name, - $_key, - $_val, - $selected, - $extra, - $separator, - $labels, - $label_ids, - $_pos, - $escape - ); - } - } - if (!empty($params[ 'assign' ])) { - $template->assign($params[ 'assign' ], $_html_result); - } else { - return implode("\n", $_html_result); - } -} - -/** - * @param $name - * @param $value - * @param $output - * @param $selected - * @param $extra - * @param $separator - * @param $labels - * @param $label_ids - * @param $pos - * @param bool $escape - * - * @return string - */ -function smarty_function_html_checkboxes_output( - $name, - $value, - $output, - $selected, - $extra, - $separator, - $labels, - $label_ids, - $pos, - $escape = true -) { - $_output = ''; - if (is_object($value)) { - if (method_exists($value, '__toString')) { - $value = (string)$value->__toString(); - } else { - trigger_error( - 'html_options: value is an object of class \'' . get_class($value) . - '\' without __toString() method', - E_USER_NOTICE - ); - return ''; - } - } else { - $value = (string)$value; - } - if (is_object($output)) { - if (method_exists($output, '__toString')) { - $output = (string)$output->__toString(); - } else { - trigger_error( - 'html_options: output is an object of class \'' . get_class($output) . - '\' without __toString() method', - E_USER_NOTICE - ); - return ''; - } - } else { - $output = (string)$output; - } - if ($labels) { - if ($label_ids) { - $_id = smarty_function_escape_special_chars( - preg_replace( - '![^\w\-\.]!' . Smarty::$_UTF8_MODIFIER, - '_', - $name . '_' . $value - ) - ); - $_output .= '