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/4dev/checking/phpunit.sh b/4dev/checking/phpunit.sh index e331bd6a..0fbf93d8 100755 --- a/4dev/checking/phpunit.sh +++ b/4dev/checking/phpunit.sh @@ -36,7 +36,7 @@ if [ -n "${2}" ] && [ -z "${php_bin}" ]; then fi; # 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}vendor/bin/phpunit ${opt_testdox} -c ${base}phpunit.xml ${base}4dev/tests/"; ${phpunit_call}; diff --git a/4dev/database/function/set_edit_generic.sql b/4dev/database/function/set_edit_generic.sql index c4a892bd..4ec68683 100644 --- a/4dev/database/function/set_edit_generic.sql +++ b/4dev/database/function/set_edit_generic.sql @@ -9,6 +9,7 @@ BEGIN IF TG_OP = 'INSERT' THEN NEW.date_created := 'now'; NEW.cuid := random_string(random_length); + NEW.cuuid := gen_random_uuid(); ELSIF TG_OP = 'UPDATE' THEN NEW.date_updated := 'now'; END IF; 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..0ebf2599 100644 --- a/4dev/database/table/edit_log.sql +++ b/4dev/database/table/edit_log.sql @@ -7,11 +7,13 @@ -- 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, + ecuid VARCHAR, + ecuuid UUID, event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, ip VARCHAR, error TEXT, @@ -21,6 +23,7 @@ CREATE TABLE edit_log ( page VARCHAR, action VARCHAR, action_id VARCHAR, + action_sub_id VARCHAR, action_yes VARCHAR, action_flag VARCHAR, action_menu VARCHAR, 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..abae29c6 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, 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/ACL/CoreLibsACLLoginTest.php b/4dev/tests/ACL/CoreLibsACLLoginTest.php index e2a28b0e..92d3d978 100644 --- a/4dev/tests/ACL/CoreLibsACLLoginTest.php +++ b/4dev/tests/ACL/CoreLibsACLLoginTest.php @@ -243,6 +243,8 @@ final class CoreLibsACLLoginTest extends TestCase [], [ 'EUID' => 1, + 'ECUID' => 'abc', + 'ECUUID' => '1233456-1234-1234-1234-123456789012', ], 2, [], @@ -260,6 +262,8 @@ final class CoreLibsACLLoginTest extends TestCase [], [ 'EUID' => 1, + 'ECUID' => 'abc', + 'ECUUID' => '1233456-1234-1234-1234-123456789012', 'USER_NAME' => '', 'GROUP_NAME' => '', 'ADMIN' => 1, @@ -1085,9 +1089,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 () { @@ -1788,9 +1792,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 +1906,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 +1994,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 +2090,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..d3a8d0ea 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 := 'now'; + NEW.cuid := random_string(random_length); + NEW.cuuid := gen_random_uuid(); + ELSIF TG_OP = 'UPDATE' THEN + NEW.date_updated := 'now'; + 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 SERIAL 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 SERIAL 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 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 ) 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 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 ) 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 SERIAL 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 SERIAL 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 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 ) 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 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 ) 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 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 ) 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 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 ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_page_content.sql -- START: table/edit_user.sql @@ -549,63 +551,63 @@ 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 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) ) INHERITS (edit_generic) WITHOUT OIDS; -- create unique index @@ -650,37 +652,40 @@ 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 SERIAL PRIMARY KEY, + euid INT, -- this is a foreign key, but I don't nedd to reference to it + ecuid VARCHAR, + ecuuid UUID, + FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL, + username VARCHAR, + password VARCHAR, + event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, + ip VARCHAR, + error TEXT, + event TEXT, + data_binary BYTEA, + data TEXT, + page VARCHAR, + action VARCHAR, + action_id VARCHAR, + action_sub_id VARCHAR, + action_yes VARCHAR, + action_flag VARCHAR, + action_menu VARCHAR, + action_loaded VARCHAR, + action_value VARCHAR, + action_type VARCHAR, + action_error VARCHAR, + 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 ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_log.sql -- START: table/edit_log_overflow.sql @@ -707,15 +712,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 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 ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_access.sql -- START: table/edit_access_user.sql @@ -728,15 +733,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 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 ) INHERITS (edit_generic) WITHOUT OIDS; -- END: table/edit_access_user.sql -- START: table/edit_access_data.sql @@ -749,12 +754,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 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 ) INHERITS (edit_generic) WITHOUT OIDS; -- create a unique index for each attached data block for each edit access can diff --git a/4dev/tests/Convert/CoreLibsConvertMathTest.php b/4dev/tests/Convert/CoreLibsConvertMathTest.php index 6441ca79..5df06aa0 100644 --- a/4dev/tests/Convert/CoreLibsConvertMathTest.php +++ b/4dev/tests/Convert/CoreLibsConvertMathTest.php @@ -319,6 +319,36 @@ final class CoreLibsConvertMathTest extends TestCase [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], + ] + ], ]; } diff --git a/4dev/tests/Create/CoreLibsCreateSessionTest.php b/4dev/tests/Create/CoreLibsCreateSessionTest.php index f9c89109..2ac833fd 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,10 @@ final class CoreLibsCreateSessionTest extends TestCase 'getSessionId' => '1234abcd4567' ], 'sessionNameGlobals', - null, - null, - '', + false, ], - 'session name default' => [ - '', - 'd', + 'auto write close' => [ + 'sessionNameAutoWriteClose', [ 'checkCliStatus' => false, 'getSessionStatus' => PHP_SESSION_NONE, @@ -75,109 +65,8 @@ final class CoreLibsCreateSessionTest extends TestCase 'checkActiveSession' => [false, true], 'getSessionId' => '1234abcd4567' ], - '', - null, - null, - '', - ], - // error checks - // 1: we are in cli - 'on cli error' => [ - '', - 'd', - [ - 'checkCliStatus' => true, - 'getSessionStatus' => PHP_SESSION_NONE, - 'setSessionName' => true, - 'checkActiveSession' => [false, true], - 'getSessionId' => '1234abcd4567' - ], - '', - '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' + 'sessionNameAutoWriteClose', + true, ], ]; } @@ -190,32 +79,23 @@ 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 * @return void */ public function testStartSession( string $input, - string $type, array $mock_data, string $expected, - ?string $exception, - ?int $exception_code, - string $expected_error + ?bool $auto_write_close, ): 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 +114,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 +123,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 +142,73 @@ 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/' + ], + 'not a valid session id returned' => [ + \UnexpectedValueException::class, + 5, + '/^\[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); + new \CoreLibs\Create\Session($session_name); + } + /** * provider for session name check * @@ -347,109 +272,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 +426,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/CoreLibsDBIOTest.php b/4dev/tests/DB/CoreLibsDBIOTest.php index 93703db8..ec8a28e1 100644 --- a/4dev/tests/DB/CoreLibsDBIOTest.php +++ b/4dev/tests/DB/CoreLibsDBIOTest.php @@ -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', @@ -159,15 +160,12 @@ final class CoreLibsDBIOTest extends TestCase // create the tables $db->dbExec( // primary key name is table + '_id' + // table_with_primary_key_id SERIAL PRIMARY KEY, <<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 +3291,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 +3326,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 +3356,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 +3389,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 +3413,7 @@ final class CoreLibsDBIOTest extends TestCase 'count' => 0, 'query' => '', 'returning_id' => false, + 'placeholder_converted' => [], ], ], // no query (prepare 11) @@ -3435,6 +3438,7 @@ final class CoreLibsDBIOTest extends TestCase 'count' => 0, 'query' => '', 'returning_id' => false, + 'placeholder_converted' => [], ], ], // no db connection (prepare/execute 16) @@ -3464,6 +3468,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 +3494,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 +3520,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 +3670,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), @@ -5031,8 +5039,151 @@ 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, + ] + ]; + } + + /** + * 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 +5227,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 +5331,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/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..f4e36ec3 --- /dev/null +++ b/4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql @@ -0,0 +1,26 @@ +-- 20241203: update edit tables +ALTER TABLE edit_generic ADD cuuid UUID DEFAULT gen_random_uuid(); +ALTER TABLE edit_log ADD ecuid VARCHAR; +ALTER TABLE edit_log ADD ecuuid VARCHAR; +ALTER TABLE edit_log ADD action_sub_id VARCHAR; + +-- 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 := 'now'; + NEW.cuid := random_string(random_length); + NEW.cuuid := gen_random_uuid(); + ELSIF TG_OP = 'UPDATE' THEN + NEW.date_updated := 'now'; + END IF; + RETURN NEW; +END; +$$ +LANGUAGE 'plpgsql'; diff --git a/composer.json b/composer.json index 2a90156b..9d009a99 100644 --- a/composer.json +++ b/composer.json @@ -2,16 +2,17 @@ "name": "egrajp/development-corelibs-dev", "version": "dev-master", "description": "CoreLibs: Development package", + "keywords": ["corelib", "logging", "database", "templating", "tools"], "type": "library", "require": { "php": ">=8.3" }, "require-dev": { - "phpstan/phpstan": "^1.12", - "phan/phan": "^5.4", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/extension-installer": "^1.4", + "phan/phan": "^5.4", "phpunit/phpunit": "^9", - "phpstan/phpstan-deprecation-rules": "^1.2", "yamadashy/phpstan-friendly-formatter": "^1.1" }, "config": { diff --git a/phpstan.neon b/phpstan.neon index a84ae62c..1d6c4ff5 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,6 +2,7 @@ includes: - phpstan-conditional.php #- ./vendor/yamadashy/phpstan-friendly-formatter/extension.neon + # - phar://phpstan.phar/conf/bleedingEdge.neon parameters: tmpDir: %currentWorkingDirectory%/tmp/phpstan-corelibs #errorFormat: friendly @@ -13,6 +14,9 @@ parameters: # allRules: false checkMissingCallableSignature: true treatPhpDocTypesAsCertain: false + # phpVersion: + # min: 80200 # PHP 8.2.0 + # max: 80300 # PHP latest paths: - %currentWorkingDirectory%/www bootstrapFiles: diff --git a/www/admin/class_test.admin.backend.php b/www/admin/class_test.admin.backend.php index c117aec1..492bbd8e 100644 --- a/www/admin/class_test.admin.backend.php +++ b/www/admin/class_test.admin.backend.php @@ -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,10 +69,30 @@ print '

' . $PAGE_NAME . '

'; print "SETACL[]:
"; $backend->setACL(['EMPTY' => 'EMPTY']); print "ADBEDITLOG:
"; -$backend->adbEditLog('CLASSTEST-ADMIN-BINARY', 'Some info string', 'BINARY'); -$backend->adbEditLog('CLASSTEST-ADMIN-ZLIB', 'Some info string', 'ZLIB'); -$backend->adbEditLog('CLASSTEST-ADMIN-SERIAL', 'Some info string', 'SERIAL'); -$backend->adbEditLog('CLASSTEST-ADMIN-INVALID', 'Some info string', 'INVALID'); +$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'; @@ -69,10 +103,10 @@ $backend->action_loaded = 'TEST ACTION LOADED'; $backend->action_value = 'TEST ACTION VALUE'; $backend->action_type = 'TEST ACTION TYPE'; $backend->action_error = 'TEST ACTION ERROR'; -$backend->adbEditLog('CLASSTEST-ADMIN-JSON', [ +$login->writeLog('CLASSTEST-ADMIN-JSON', [ "_GET" => $_GET, "_POST" => $_POST, -], 'JSON'); +], $backend->adbGetActionSet(), write_type:'JSON'); print "ADBTOPMENU(0): " . Support::printAr($backend->adbTopMenu(CONTENT_PATH)) . "
"; print "ADBMSG:
"; diff --git a/www/admin/class_test.array.php b/www/admin/class_test.array.php index cadf8117..85dbfc0a 100644 --- a/www/admin/class_test.array.php +++ b/www/admin/class_test.array.php @@ -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) { 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..8cca56a9 --- /dev/null +++ b/www/admin/class_test.db.convert-placeholder.php @@ -0,0 +1,233 @@ + 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..17166558 100644 --- a/www/admin/class_test.db.dbReturn.php +++ b/www/admin/class_test.db.dbReturn.php @@ -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..1326a023 100644
--- a/www/admin/class_test.db.php
+++ b/www/admin/class_test.db.php
@@ -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) . "
"; @@ -316,7 +316,8 @@ print "EOM STRING EXEC RETURN TEST: " . print_r( $db->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 @@ -674,7 +676,7 @@ echo "
"; 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 "
"; diff --git a/www/admin/class_test.lang.php b/www/admin/class_test.lang.php index 756a5992..43a217e3 100644 --- a/www/admin/class_test.lang.php +++ b/www/admin/class_test.lang.php @@ -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'; @@ -70,10 +72,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 +90,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, diff --git a/www/admin/class_test.login.php b/www/admin/class_test.login.php index af400089..3d1327d5 100644 --- a/www/admin/class_test.login.php +++ b/www/admin/class_test.login.php @@ -58,4 +58,16 @@ 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 "ECUID: " . $login->loginGetEcuid() . "
"; +echo "ECUUID: " . $login->loginGetEcuuid() . "
"; + +$login->writeLog( + 'TEST LOG', + [ + 'test' => 'TEST A' + ], + error:'No Error', + write_type:'JSON' +); + print ""; diff --git a/www/admin/class_test.php b/www/admin/class_test.php index 9a56a0e8..d511bcf8 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -73,6 +73,7 @@ $test_files = [ 'class_test.db.query-placeholder.php' => 'Class Test: DB query placeholder convert', 'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn', 'class_test.db.single.php' => 'Class Test: DB single query tests', + 'class_test.db.convert-placeholder.php' => 'Class Test: DB convert placeholder', 'class_test.db.sqlite.php' => 'Class Test: DB: SqLite', 'class_test.convert.colors.php' => 'Class Test: CONVERT COLORS', 'class_test.check.colors.php' => 'Class Test: CHECK COLORS', @@ -205,6 +206,9 @@ print "HOST: " . HOST_NAME . " => DB HOST: " . DB_CONFIG_NAME . " => " . Support print "DS is: " . DIRECTORY_SEPARATOR . "
"; print "SERVER HOST: " . $_SERVER['HTTP_HOST'] . "
"; +print "ECUID: " . $session->get('ECUID') . "
"; +print "ECUUID: " . $session->get('ECUUID') . "
"; + print ""; # __END__ diff --git a/www/admin/class_test.session.php b/www/admin/class_test.session.php index b0a9ba46..ed9439ef 100644 --- a/www/admin/class_test.session.php +++ b/www/admin/class_test.session.php @@ -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..b2e6e8e3 100644 --- a/www/admin/class_test.session.read.php +++ b/www/admin/class_test.session.read.php @@ -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.uids.php b/www/admin/class_test.uids.php index 3801eaed..3302fe8b 100644 --- a/www/admin/class_test.uids.php +++ b/www/admin/class_test.uids.php @@ -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/includes/admin_header.php b/www/includes/admin_header.php index 50797179..d6176102 100644 --- a/www/includes/admin_header.php +++ b/www/includes/admin_header.php @@ -116,7 +116,7 @@ $data = [ // log action // no log if login if (!$login->loginActionRun()) { - $cms->adbEditLog('Submit', $data, 'BINARY'); + $login->writeLog('Submit', $data, $cms->adbGetActionSet(), '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..691b03d6 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -69,12 +69,17 @@ 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; + /** @var ?string the user cuid (note will be super seeded with uuid v4 later) */ + private ?string $ecuid; + /** @var ?string UUIDv4, will superseed the ecuid and replace euid as login id */ + private ?string $ecuuid; /** @var string _GET/_POST loginUserId parameter for non password login */ private string $login_user_id = ''; /** @var string source, either _GET or _POST or empty */ @@ -193,6 +198,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 = []; @@ -361,9 +372,6 @@ class Login ], ]; - // 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"; @@ -376,8 +384,12 @@ class Login $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; + $this->session->setMany([ + 'DEFAULT_ACL_LIST' => $this->default_acl_list, + 'DEFAULT_ACL_LIST_TYPE' => $this->default_acl_list_type, + ]); + + $this->loginSetEditLogWriteTypeAvailable(); // this will be deprecated if ($this->options['auto_login'] === true) { @@ -567,7 +579,7 @@ class Login // set path $options['locale_path'] = BASE . INCLUDES . LOCALE; } - $_SESSION['LOCALE_PATH'] = $options['locale_path']; + $this->session->set('LOCALE_PATH', $options['locale_path']); // LANG: LOCALE if (empty($options['site_locale'])) { trigger_error( @@ -602,7 +614,7 @@ class Login $options['set_domain'] = str_replace(DIRECTORY_SEPARATOR, '', CONTENT_PATH); } } - $_SESSION['DEFAULT_DOMAIN'] = $options['site_domain']; + $this->session->set('DEFAULT_DOMAIN', $options['site_domain']); // LANG: ENCODING if (empty($options['site_encoding'])) { trigger_error( @@ -757,7 +769,7 @@ class Login } // have to get the global stuff here for setting it later // we have to get the themes in here too - $q = "SELECT eu.edit_user_id, eu.username, eu.password, " + $q = "SELECT eu.edit_user_id, eu.cuid, eu.cuuid, eu.username, eu.password, " . "eu.edit_group_id, " . "eg.name AS edit_group_name, eu.admin, " // additinal acl lists @@ -888,7 +900,14 @@ class Login } // normal user processing // set class var and session var - $_SESSION['EUID'] = $this->euid = (int)$res['edit_user_id']; + $this->euid = (int)$res['edit_user_id']; + $this->ecuid = (string)$res['cuid']; + $this->ecuuid = (string)$res['cuuid']; + $this->session->setMany([ + 'EUID' => $this->euid, + 'ECUID' => $this->ecuid, + 'ECUUID' => $this->ecuuid, + ]); // check if user is okay $this->loginCheckPermissions(); if ($this->login_error == 0) { @@ -901,27 +920,39 @@ class Login . "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']; + $locale = $res['locale'] ?? 'en'; + $encoding = $res['encoding'] ?? 'UTF-8'; + $this->session->setMany([ + // now set all session vars and read page permissions + 'DEBUG_ALL' => $this->db->dbBoolean($res['debug']), + 'DB_DEBUG' => $this->db->dbBoolean($res['db_debug']), + // general info for user logged in + 'USER_NAME' => $res['username'], + 'ADMIN' => $res['admin'], + 'GROUP_NAME' => $res['edit_group_name'], + 'USER_ACL_LEVEL' => $res['user_level'], + 'USER_ACL_TYPE' => $res['user_type'], + 'USER_ADDITIONAL_ACL' => Json::jsonConvertToArray($res['user_additional_acl']), + 'GROUP_ACL_LEVEL' => $res['group_level'], + 'GROUP_ACL_TYPE' => $res['group_type'], + 'GROUP_ADDITIONAL_ACL' => Json::jsonConvertToArray($res['group_additional_acl']), + // deprecated TEMPLATE setting + 'TEMPLATE' => $res['template'] ? $res['template'] : '', + 'HEADER_COLOR' => !empty($res['second_header_color']) ? + $res['second_header_color'] : + $res['first_header_color'], + // LANGUAGE/LOCALE/ENCODING: + '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 (preg_match("/^[\dA-Fa-f]{6,8}$/", $_SESSION['HEADER_COLOR'])) { - $_SESSION['HEADER_COLOR'] = '#' . $_SESSION['HEADER_COLOR']; + if ( + !empty($this->session->get('HEADER_COLOR')) && + preg_match("/^[\dA-Fa-f]{6,8}$/", $this->session->get('HEADER_COLOR')) + ) { + $this->session->set('HEADER_COLOR', '#' . $this->session->get('HEADER_COLOR')); } // TODO: make sure that header color is valid: // # + 6 hex @@ -930,13 +961,6 @@ class Login // 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 " @@ -960,10 +984,7 @@ class Login . "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; - } + while (is_array($res = $this->db->dbReturn($q))) { // page id array for sub data readout $edit_page_ids[$res['edit_page_id']] = $res['cuid']; // create the array for pages @@ -1029,8 +1050,10 @@ class Login ]; } // write back the pages data to the output array - $_SESSION['PAGES'] = $pages; - $_SESSION['PAGES_ACL_LEVEL'] = $pages_acl; + $this->session->setMany([ + 'PAGES' => $pages, + '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 " @@ -1042,6 +1065,7 @@ class Login $unit_access = []; $eauid = []; $unit_acl = []; + $unit_uid = []; 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 " @@ -1065,16 +1089,19 @@ class Login ]; // set the default unit if ($res['edit_default']) { - $_SESSION['UNIT_DEFAULT'] = (int)$res['edit_access_id']; + $this->session->set('UNIT_DEFAULT', (int)$res['edit_access_id']); } - $_SESSION['UNIT_UID'][$res['uid']] = (int)$res['edit_access_id']; + $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; + $this->session->setMany([ + 'UNIT_UID' => $unit_uid, + 'UNIT' => $unit_access, + 'UNIT_ACL_LEVEL' => $unit_acl, + 'EAID' => $eauid, + ]); } // user has permission to THIS page } // user was not enabled or other login error if ($this->login_error && is_array($res)) { @@ -1135,6 +1162,9 @@ class Login // username (login), group name $this->acl['user_name'] = $_SESSION['USER_NAME']; $this->acl['group_name'] = $_SESSION['GROUP_NAME']; + // edit user cuid + $this->acl['ecuid'] = $_SESSION['ECUID']; + $this->acl['ecuuid'] = $_SESSION['ECUUID']; // set additional acl $this->acl['additional_acl'] = [ 'user' => $_SESSION['USER_ADDITIONAL_ACL'], @@ -1167,7 +1197,7 @@ class Login $this->acl['base'] = (int)$_SESSION['USER_ACL_LEVEL']; } } - $_SESSION['BASE_ACL_LEVEL'] = $this->acl['base']; + $this->session->set('BASE_ACL_LEVEL', $this->acl['base']); // set the current page acl // start with base acl @@ -1303,11 +1333,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,7 +1458,7 @@ 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); } /** @@ -1571,7 +1599,7 @@ class Login $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(); @@ -1727,6 +1755,8 @@ HTML; } } + // MARK: LOGGING + /** * writes detailed data into the edit user log table (keep log what user does) * @@ -1736,7 +1766,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 +1784,191 @@ 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('USER_NAME') ?? '' : $username, + is_numeric($this->session->get('EUID')) ? + $this->session->get('EUID') : null, + is_string($this->session->get('ECUID')) ? + $this->session->get('ECUID') : null, + !empty($this->session->get('ECUUID')) && Uids::validateUuuidv4($this->session->get('ECUUID')) ? + $this->session->get('ECUUID') : null, + (string)$event, + (string)$error, + $data_write, + $data_binary, + (string)$this->page_name, + // row 2 + $_SERVER["REMOTE_ADDR"] ?? null, + $_SERVER['HTTP_USER_AGENT'] ?? null, + $_SERVER['HTTP_REFERER'] ?? null, + $_SERVER['SCRIPT_FILENAME'] ?? null, + $_SERVER['QUERY_STRING'] ?? null, + $_SERVER['SERVER_NAME'] ?? null, + $_SERVER['HTTP_HOST'] ?? null, + // row 3 + $_SERVER['HTTP_ACCEPT'] ?? null, + $_SERVER['HTTP_ACCEPT_CHARSET'] ?? null, + $_SERVER['HTTP_ACCEPT_ENCODING'] ?? null, + $this->session->getSessionId() !== '' ? + $this->session->getSessionId() : null, + // row 4 + $action_set['action'] ?? null, + $action_set['action_id'] ?? null, + $action_set['action_sub_id'] ?? null, + $action_set['action_yes'] ?? null, + $action_set['action_flag'] ?? null, + $action_set['action_menu'] ?? null, + $action_set['action_loaded'] ?? null, + $action_set['action_value'] ?? null, + $action_set['action_type'] ?? null, + $action_set['action_error'] ?? null, + ], + '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: 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 @@ -1866,7 +2037,10 @@ 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->euid = (int)($this->session->get('EUID') ?? 0); + // TODO: allow load from cuid + // $this->ecuid = (string)($this->session->get('ECUID') ?? ''); + // $this->ecuuid = (string)($this->session->get('ECUUID') ?? ''); // get login vars, are so, can't be changed // prepare // pass on vars to Object vars @@ -1947,6 +2121,8 @@ HTML; $this->loginSetAcl(); } + // MARK: setters/getters + /** * Returns current set login_html content * @@ -2116,6 +2292,8 @@ HTML; $this->session->sessionDestroy(); // unset euid $this->euid = null; + $this->ecuid = null; + $this->ecuuid = null; // then prints the login screen again $this->permission_okay = false; } @@ -2133,11 +2311,12 @@ HTML; if (empty($this->euid)) { return $this->permission_okay; } + // euid must match ecuid and ecuuid // 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, " + $q = "SELECT ep.filename, eu.cuid, eu.cuuid, " // base lock flags . "eu.deleted, eu.enabled, eu.locked, " // date based lock @@ -2203,6 +2382,13 @@ HTML; } else { $this->login_error = 103; } + // set ECUID + $this->ecuid = (string)$res['cuid']; + $this->ecuuid = (string)$res['cuuid']; + $this->session->setMany([ + 'ECUID' => $this->ecuid, + 'ECUUID' => $this->ecuuid, + ]); // if called from public, so we can check if the permissions are ok return $this->permission_okay; } @@ -2348,13 +2534,12 @@ HTML; { 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('UNIT')) && + !array_key_exists($edit_access_id, $this->session->get('UNIT')) ) { $edit_access_id = null; - if (is_numeric($_SESSION['UNIT_DEFAULT'])) { - $edit_access_id = (int)$_SESSION['UNIT_DEFAULT']; + if (is_numeric($this->session->get('UNIT_DEFAULT'))) { + $edit_access_id = (int)$this->session->get('UNIT_DEFAULT'); } } return $edit_access_id; @@ -2485,7 +2670,7 @@ HTML; */ public function loginGetHeaderColor(): ?string { - return $_SESSION['HEADER_COLOR'] ?? null; + return $this->session->get('HEADER_COLOR'); } /** @@ -2496,7 +2681,7 @@ HTML; public function loginGetPages(): array { - return $_SESSION['PAGES'] ?? []; + return $this->session->get('PAGES'); } /** @@ -2508,6 +2693,26 @@ HTML; { return (string)$this->euid; } + + /** + * Get the current set ECUID (edit user cuid) + * + * @return string ECUID as string + */ + public function loginGetEcuid(): string + { + return (string)$this->ecuid; + } + + /** + * Get the current set ECUUID (edit user cuuid) + * + * @return string ECUUID as string + */ + public function loginGetEcuuid(): string + { + return (string)$this->ecuuid; + } } // __END__ diff --git a/www/lib/CoreLibs/Admin/Backend.php b/www/lib/CoreLibs/Admin/Backend.php index 9f705c7b..6f02cd25 100644 --- a/www/lib/CoreLibs/Admin/Backend.php +++ b/www/lib/CoreLibs/Admin/Backend.php @@ -31,6 +31,7 @@ declare(strict_types=1); namespace CoreLibs\Admin; +use CoreLibs\Create\Uids; use CoreLibs\Convert\Json; class Backend @@ -258,6 +259,27 @@ 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 * @@ -267,6 +289,7 @@ class Backend * JSON, STRING/SERIEAL, BINARY/BZIP or ZLIB * @param string|null $db_schema [default=null] override target schema * @return void + * @deprecated Use $login->writeLog() and set action_set from ->adbGetActionSet() */ public function adbEditLog( string $event = '', @@ -335,17 +358,17 @@ class Backend } $q = <<db->dbExecParams( @@ -356,9 +379,15 @@ class Backend ), [ // row 1 - isset($_SESSION['EUID']) && is_numeric($_SESSION['EUID']) ? - $_SESSION['EUID'] : null, + '', + 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, @@ -374,11 +403,12 @@ class Backend $_SERVER['HTTP_ACCEPT'] ?? '', $_SERVER['HTTP_ACCEPT_CHARSET'] ?? '', $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '', - $this->session->getSessionId() !== false ? + $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 ?? '', @@ -425,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', @@ -441,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 = []; } 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/Convert/Color/CieXyz.php b/www/lib/CoreLibs/Convert/Color/CieXyz.php index 2784f3e8..40c90bb0 100644 --- a/www/lib/CoreLibs/Convert/Color/CieXyz.php +++ b/www/lib/CoreLibs/Convert/Color/CieXyz.php @@ -246,7 +246,7 @@ class CieXyz self::convertArray(array_map( fn ($k, $v) => $v * $d50[$k], array_keys($xyz), - array_values($xyz), + $xyz, )), options: ["whitepoint" => 'D50'] ); diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php b/www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php index 1b15bd98..09bd57e9 100644 --- a/www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php @@ -26,7 +26,7 @@ class HSB implements Interface\CoordinatesInterface private float $B = 0.0; /** @var string color space: either ok or cie */ - private string $colorspace = ''; + private string $colorspace = ''; /** @phpstan-ignore-line */ /** * HSB (HSV) color coordinates diff --git a/www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php b/www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php index 1adbe5c9..5fbcf29f 100644 --- a/www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php +++ b/www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php @@ -25,7 +25,7 @@ class HSL implements Interface\CoordinatesInterface /** @var float lightness (luminance) */ private float $L = 0.0; - /** @var string color space: either ok or cie */ + /** @var string color space: either sRGB */ private string $colorspace = ''; /** diff --git a/www/lib/CoreLibs/Convert/Math.php b/www/lib/CoreLibs/Convert/Math.php index 41eb7463..daf4bf07 100644 --- a/www/lib/CoreLibs/Convert/Math.php +++ b/www/lib/CoreLibs/Convert/Math.php @@ -158,6 +158,8 @@ class Math * [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 * @@ -186,7 +188,7 @@ class Math // so that we can multiply row by row $bCols = array_map( callback: fn ($k) => array_map( - (fn ($i) => is_array($i) ? $i[$k] : 0), + (fn ($i) => is_array($i) ? $i[$k] ?? 0 : 0), $b, ), array: array_keys($b[0]), diff --git a/www/lib/CoreLibs/Create/Session.php b/www/lib/CoreLibs/Create/Session.php index ca3607e6..6873ee27 100644 --- a/www/lib/CoreLibs/Create/Session.php +++ b/www/lib/CoreLibs/Create/Session.php @@ -15,19 +15,27 @@ 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; + /** * 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 */ - public function __construct(string $session_name = '') + public function __construct(string $session_name, bool $auto_write_close = false) { - if (!empty($session_name)) { - $this->startSession($session_name); - } + $this->initSession($session_name); + $this->auto_write_close = $auto_write_close; } + // MARK: private methods + /** * Start session * startSession should be called for complete check @@ -36,36 +44,32 @@ 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; } /** @@ -94,15 +98,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 +138,85 @@ 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); + } + } 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] getSessionId did not return a session id', 6); + } + // set session id + $this->session_id = $session_id; + // 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 void + */ + public function setAutoWriteClose(bool $flag): void + { + $this->auto_write_close = $flag; + } + + /** + * return the auto write close flag + * + * @return bool + */ + public function checkAutoWriteClose(): bool + { + return $this->auto_write_close; } /** @@ -175,6 +244,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 +285,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 +322,92 @@ 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 void */ - public function setS(string|int $name, mixed $value): void + public function set(string $name, mixed $value): void { + $this->checkValidSessionEntryKey($name); + $this->restartSession(); $_SESSION[$name] = $value; + $this->closeSessionCall(); + } + + /** + * 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 setMany(array $set): void + { + $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 +415,35 @@ 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 void */ - public function unsetS(string|int $name): void + public function unset(string $name): void { - if (isset($_SESSION[$name])) { - unset($_SESSION[$name]); + if (!isset($_SESSION[$name])) { + return; } + $this->restartSession(); + unset($_SESSION[$name]); + $this->closeSessionCall(); } - // set/get below - // ->var = value; - /** - * Undocumented function + * reset many session entry * - * @param string|int $name - * @param mixed $value + * @param array $set list of session keys to reset * @return void */ - public function __set(string|int $name, mixed $value): void + public function unsetMany(array $set): 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 d3ecc374..4daad5b8 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 @@ -914,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 @@ -1311,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); } /** @@ -1737,7 +1719,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(); @@ -3160,7 +3142,8 @@ class IO 'count' => 0, '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)) { @@ -3200,6 +3183,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; @@ -3735,7 +3751,7 @@ class IO } /** - * convert db values (set) + * convert db values (set) to php matching types * * @param Convert $convert * @return void @@ -3746,7 +3762,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 @@ -3757,7 +3773,7 @@ class IO } /** - * Reset to origincal config file set + * Reset to original config file set for converting db to php matching type * * @return void */ @@ -3769,7 +3785,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 @@ -3783,7 +3799,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 @@ -4294,7 +4310,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 @@ -4302,7 +4318,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( @@ -4313,7 +4329,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, 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..0b9542b2 100644 --- a/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php +++ b/www/lib/CoreLibs/DB/Support/ConvertPlaceholder.php @@ -14,6 +14,67 @@ namespace CoreLibs\DB\Support; class ConvertPlaceholder { + /** @var string split regex */ + private const PATTERN_QUERY_SPLIT = '[(<>=,?-]|->|->>|#>|#>>|@>|<@|\?\|\?\&|\|\||#-'; + /** @var string the main regex including the pattern query split */ + private const PATTERN_ELEMENT = '(?:\'.*?\')?\s*(?:\?\?|' . self::PATTERN_QUERY_SPLIT . ')\s*'; + /** @var string parts to ignore in the SQL */ + 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_IGNORE + . self::PATTERN_NAMED + . ')' + . '/s'; + /** @var string replace regex for question mark (?) entries */ + public const REGEX_REPLACE_QUESTION_MARK = '/' + . '(' . self::PATTERN_ELEMENT . ')' + . '(' + . 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_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 + // 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 +88,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 +118,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 +129,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/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/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/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/UrlRequests/Curl.php b/www/lib/CoreLibs/UrlRequests/Curl.php index fcc8bc14..ced5129f 100644 --- a/www/lib/CoreLibs/UrlRequests/Curl.php +++ b/www/lib/CoreLibs/UrlRequests/Curl.php @@ -599,7 +599,7 @@ class Curl implements Interface\RequestsInterface // for post we set POST option if ($type == "post") { curl_setopt($handle, CURLOPT_POST, true); - } elseif (in_array($type, self::CUSTOM_REQUESTS)) { + } 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 @@ -700,12 +700,12 @@ class Curl implements Interface\RequestsInterface // 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, $this->config['timeout'] * 1000); + 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, $this->config['connection_timeout'] * 1000); + 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);