From 9edfc2acb67a6c76e3303a5b4bff058dfdda914c Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 18 Nov 2024 17:08:28 +0900 Subject: [PATCH 01/22] phpstan 2.0 update checks --- 4dev/checking/phpunit.sh | 2 +- phpstan.neon | 1 + www/admin/class_test.array.php | 3 - www/admin/class_test.db.dbReturn.php | 15 ++-- www/admin/class_test.db.php | 8 +- www/includes/edit_base.php | 2 +- www/lib/CoreLibs/ACL/Login.php | 13 +-- www/lib/CoreLibs/Admin/Backend.php | 5 +- www/lib/CoreLibs/Check/Colors.php | 11 ++- www/lib/CoreLibs/Convert/Color/CieXyz.php | 2 +- www/lib/CoreLibs/Create/Uids.php | 2 +- www/lib/CoreLibs/DB/Extended/ArrayIO.php | 4 +- www/lib/CoreLibs/DB/IO.php | 4 +- .../CoreLibs/Language/Core/GetTextReader.php | 8 +- www/lib/CoreLibs/Output/Form/Generate.php | 80 +++++++------------ www/lib/CoreLibs/Output/Image.php | 18 +++-- www/lib/CoreLibs/UrlRequests/Curl.php | 6 +- 17 files changed, 79 insertions(+), 105 deletions(-) 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/phpstan.neon b/phpstan.neon index a84ae62c..052adcbf 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 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.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..158c0fff 100644
--- a/www/admin/class_test.db.php
+++ b/www/admin/class_test.db.php
@@ -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/includes/edit_base.php b/www/includes/edit_base.php index c5968e96..fd2256d8 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 'config.php'; /** @phpstan-ignore-line Is path, is symlinked */ // 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..2ed56c09 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -960,10 +960,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 @@ -1303,11 +1300,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 diff --git a/www/lib/CoreLibs/Admin/Backend.php b/www/lib/CoreLibs/Admin/Backend.php index 9f705c7b..5fb2918b 100644 --- a/www/lib/CoreLibs/Admin/Backend.php +++ b/www/lib/CoreLibs/Admin/Backend.php @@ -425,10 +425,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', 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/Create/Uids.php b/www/lib/CoreLibs/Create/Uids.php index 47691338..42339797 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) { 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..cae7cf50 100644 --- a/www/lib/CoreLibs/DB/IO.php +++ b/www/lib/CoreLibs/DB/IO.php @@ -914,7 +914,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 @@ -1737,7 +1737,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(); 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/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); From ad070ebdf4c278485edd1eec99304ddc187ef760 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 18 Nov 2024 18:33:04 +0900 Subject: [PATCH 02/22] Composer phpstan update 2.0 --- composer.json | 6 +++--- phpstan.neon | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 2a90156b..1b1a396c 100644 --- a/composer.json +++ b/composer.json @@ -7,11 +7,11 @@ "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 052adcbf..869c5c48 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,7 +2,7 @@ includes: - phpstan-conditional.php #- ./vendor/yamadashy/phpstan-friendly-formatter/extension.neon - - phar://phpstan.phar/conf/bleedingEdge.neon + # - phar://phpstan.phar/conf/bleedingEdge.neon parameters: tmpDir: %currentWorkingDirectory%/tmp/phpstan-corelibs #errorFormat: friendly From 8de112ba7ea263bfe120416df1a94d3d21aa2327 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 19 Nov 2024 10:24:37 +0900 Subject: [PATCH 03/22] Math Matrix multiplication fix for unbalanced array rows Test for unbalanced arrays to matrix multiplication and fix unbalanced a array --- .../tests/Convert/CoreLibsConvertMathTest.php | 30 +++++++++++++++++++ www/lib/CoreLibs/Convert/Math.php | 4 ++- 2 files changed, 33 insertions(+), 1 deletion(-) 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/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]), From 529b6a75baabd42dbe8447830d0a62f63d1666df Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 19 Nov 2024 15:43:00 +0900 Subject: [PATCH 04/22] Set base path for config file to load in edit_base.php --- www/includes/edit_base.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/includes/edit_base.php b/www/includes/edit_base.php index fd2256d8..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'; /** @phpstan-ignore-line Is path, is symlinked */ +require getcwd() . DIRECTORY_SEPARATOR . 'config.php'; // should be utf8 header("Content-type: text/html; charset=" . DEFAULT_ENCODING); From ae044bee6f4dceff5c4da6eacd9fa378d8d80fb0 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 20 Nov 2024 18:58:59 +0900 Subject: [PATCH 05/22] DB IO Placeholder convert fixers and updates Add more checks in phpunit for this, Update the placeholder check and convert and move all regex into the placeholder convert support class Move $ placeholder count function to the SQL\PgSQL class Note: further moves of PgSQL only stuff have to be done for SQLite SQL class add --- 4dev/tests/DB/CoreLibsDBIOTest.php | 172 +++++++- .../class_test.db.convert-placeholder.php | 233 ++++++++++ www/admin/class_test.php | 1 + www/lib/CoreLibs/DB/IO.php | 78 ++-- www/lib/CoreLibs/DB/SQL/PgSQL.php | 34 +- .../DB/Support/ConvertPlaceholder.php | 404 +++++++++++------- 6 files changed, 728 insertions(+), 194 deletions(-) create mode 100644 www/admin/class_test.db.convert-placeholder.php diff --git a/4dev/tests/DB/CoreLibsDBIOTest.php b/4dev/tests/DB/CoreLibsDBIOTest.php index 93703db8..d6fa9408 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', @@ -570,11 +571,11 @@ final class CoreLibsDBIOTest extends TestCase ); $db->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 +3294,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 +3329,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 +3359,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 +3392,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 +3416,7 @@ final class CoreLibsDBIOTest extends TestCase 'count' => 0, 'query' => '', 'returning_id' => false, + 'placeholder_converted' => [], ], ], // no query (prepare 11) @@ -3435,6 +3441,7 @@ final class CoreLibsDBIOTest extends TestCase 'count' => 0, 'query' => '', 'returning_id' => false, + 'placeholder_converted' => [], ], ], // no db connection (prepare/execute 16) @@ -3464,6 +3471,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 +3497,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 +3523,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 +3673,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 +5042,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 +5230,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 +5334,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/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.php b/www/admin/class_test.php index a5032e9a..a6355cec 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.convert.colors.php' => 'Class Test: CONVERT COLORS', 'class_test.check.colors.php' => 'Class Test: CHECK COLORS', 'class_test.mime.php' => 'Class Test: MIME', diff --git a/www/lib/CoreLibs/DB/IO.php b/www/lib/CoreLibs/DB/IO.php index cae7cf50..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 @@ -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); } /** @@ -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/PgSQL.php b/www/lib/CoreLibs/DB/SQL/PgSQL.php index fa4c7e89..53b97250 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 @@ -966,6 +968,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, ]; From b080727ff3759fdee7d4d363b15ebdee098da2d9 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 21 Nov 2024 10:40:24 +0900 Subject: [PATCH 06/22] Add missing PgSQL to the Interface --- .../DB/SQL/Interface/SqlFunctions.php | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) 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__ From 3c4c5d31066abe44180b06918e9918c885da36df Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 22 Nov 2024 17:21:07 +0900 Subject: [PATCH 07/22] Upgrade PostgreSQL serial to identity columns function Function to help update PostgreSQL serial columns to identity --- .../function/upgrade_serial_to_identity.sql | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 4dev/database/function/upgrade_serial_to_identity.sql 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..77585e3b --- /dev/null +++ b/4dev/database/function/upgrade_serial_to_identity.sql @@ -0,0 +1,105 @@ +-- Upgrae serial to identity type +-- +-- @param reclass tbl The table where the column is located +-- @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 +-- @raises EXCEPTON on column not found, no linked sequence, more than one linked sequence found +-- +CREATE OR REPLACE FUNCTION upgrade_serial_to_identity( + tbl regclass, + col name, + identity_type varchar = 'a', + col_type varchar = '' +) +RETURNS void +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; +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; + + -- 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; + RAISE NOTICE 'Update to identity for table "%" and columen "%" with type "%"', tbl, col, identity_type; + + -- set type if requested and not empty + IF col_type <> '' THEN + IF col_type IN ('smallint', 'int', 'bigint', 'int2', 'int4', 'int8') 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 + RAISE NOTICE '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; + ELSE + RAISE NOTICE 'Invalid col type: %', col_type; + END IF; + END IF; +END; +$$; From 87f35f23c358b56d2cef27422c3f6f374a6336c1 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 22 Nov 2024 17:24:34 +0900 Subject: [PATCH 08/22] edit_* table update for serial to identity columns --- 4dev/database/table/edit_access.sql | 2 +- 4dev/database/table/edit_access_data.sql | 2 +- 4dev/database/table/edit_access_right.sql | 2 +- 4dev/database/table/edit_access_user.sql | 2 +- 4dev/database/table/edit_group.sql | 2 +- 4dev/database/table/edit_language.sql | 2 +- 4dev/database/table/edit_log.sql | 2 +- 4dev/database/table/edit_menu_group.sql | 2 +- 4dev/database/table/edit_page.sql | 2 +- 4dev/database/table/edit_page_access.sql | 2 +- 4dev/database/table/edit_page_content.sql | 2 +- 4dev/database/table/edit_query_string.sql | 2 +- 4dev/database/table/edit_scheme.sql | 2 +- 4dev/database/table/edit_user.sql | 2 +- 4dev/database/table/edit_visible_group.sql | 2 +- 15 files changed, 15 insertions(+), 15 deletions(-) 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_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..97e22546 100644 --- a/4dev/database/table/edit_log.sql +++ b/4dev/database/table/edit_log.sql @@ -7,7 +7,7 @@ -- 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, diff --git a/4dev/database/table/edit_menu_group.sql b/4dev/database/table/edit_menu_group.sql index 11727cbd..6ed9e6d0 100644 --- a/4dev/database/table/edit_menu_group.sql +++ b/4dev/database/table/edit_menu_group.sql @@ -7,7 +7,7 @@ -- 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 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..2e5caf44 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, 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; From 54541332396c8a59c13f26b92be00f08fe189ea7 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 22 Nov 2024 17:25:22 +0900 Subject: [PATCH 09/22] Update SQL\PgSQL with param calls and heredoc, primary key search method update The primary key currval select is udpated to use proper calls so it works with serial and identity columns --- 4dev/tests/DB/CoreLibsDBIOTest.php | 7 +-- www/lib/CoreLibs/DB/SQL/PgSQL.php | 89 +++++++++++++++++++++--------- 2 files changed, 66 insertions(+), 30 deletions(-) diff --git a/4dev/tests/DB/CoreLibsDBIOTest.php b/4dev/tests/DB/CoreLibsDBIOTest.php index d6fa9408..ec8a28e1 100644 --- a/4dev/tests/DB/CoreLibsDBIOTest.php +++ b/4dev/tests/DB/CoreLibsDBIOTest.php @@ -160,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( <<__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; @@ -451,26 +447,67 @@ 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); + $q .= <<__dbQueryParams(str_replace( + ['{PG_NAMESPACE}', '{NSPNAME}'], + $replace, + $q + ), $params); if ($cursor !== false) { $__db_fetch_array = $this->__dbFetchArray($cursor); if (!is_array($__db_fetch_array)) { @@ -895,11 +932,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; From 83738adcb6b53155535ce3d4284f760d4c633d50 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 27 Nov 2024 14:32:34 +0900 Subject: [PATCH 10/22] Remove old code --- www/lib/CoreLibs/DB/SQL/PgSQL.php | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/www/lib/CoreLibs/DB/SQL/PgSQL.php b/www/lib/CoreLibs/DB/SQL/PgSQL.php index 78336663..a343c8bc 100644 --- a/www/lib/CoreLibs/DB/SQL/PgSQL.php +++ b/www/lib/CoreLibs/DB/SQL/PgSQL.php @@ -451,37 +451,6 @@ class PgSQL implements Interface\SqlFunctions $replace = ['', '']; // read from table the PK name // faster primary key get - /* $q = << Date: Mon, 2 Dec 2024 15:36:21 +0900 Subject: [PATCH 11/22] Add a uuid4 validate method --- .../function/upgrade_serial_to_identity.sql | 6 ++-- 4dev/tests/Create/CoreLibsCreateUidsTest.php | 18 ++++++---- www/admin/class_test.uids.php | 8 +++++ www/lib/CoreLibs/Create/Uids.php | 34 ++++++++----------- 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/4dev/database/function/upgrade_serial_to_identity.sql b/4dev/database/function/upgrade_serial_to_identity.sql index 77585e3b..2c2ac212 100644 --- a/4dev/database/function/upgrade_serial_to_identity.sql +++ b/4dev/database/function/upgrade_serial_to_identity.sql @@ -1,6 +1,8 @@ --- Upgrae serial to identity type +-- Upgrade serial to identity type -- --- @param reclass tbl The table where the column is located +-- 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 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/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/lib/CoreLibs/Create/Uids.php b/www/lib/CoreLibs/Create/Uids.php index 42339797..09bdc86b 100644 --- a/www/lib/CoreLibs/Create/Uids.php +++ b/www/lib/CoreLibs/Create/Uids.php @@ -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 * From cee3b5c2d16b353724185ac1ca517479f037b362 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Mon, 2 Dec 2024 15:45:47 +0900 Subject: [PATCH 12/22] HSB Colorspace skip phpstan colorspace variable never read --- www/admin/class_test.db.php | 2 +- www/lib/CoreLibs/Convert/Color/Coordinates/HSB.php | 2 +- www/lib/CoreLibs/Convert/Color/Coordinates/HSL.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/www/admin/class_test.db.php b/www/admin/class_test.db.php index 158c0fff..6212c768 100644 --- a/www/admin/class_test.db.php +++ b/www/admin/class_test.db.php @@ -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 = << Date: Mon, 2 Dec 2024 17:09:02 +0900 Subject: [PATCH 13/22] Add edit user cuid to session and ACL read This is for phasing out the EUID and replace it with an UUIDv4 for any user settings --- 4dev/tests/ACL/CoreLibsACLLoginTest.php | 2 ++ www/lib/CoreLibs/ACL/Login.php | 25 +++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/4dev/tests/ACL/CoreLibsACLLoginTest.php b/4dev/tests/ACL/CoreLibsACLLoginTest.php index e2a28b0e..c73d12d8 100644 --- a/4dev/tests/ACL/CoreLibsACLLoginTest.php +++ b/4dev/tests/ACL/CoreLibsACLLoginTest.php @@ -243,6 +243,7 @@ final class CoreLibsACLLoginTest extends TestCase [], [ 'EUID' => 1, + 'ECUID' => 'abc', ], 2, [], @@ -260,6 +261,7 @@ final class CoreLibsACLLoginTest extends TestCase [], [ 'EUID' => 1, + 'ECUID' => 'abc', 'USER_NAME' => '', 'GROUP_NAME' => '', 'ADMIN' => 1, diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 2ed56c09..dcbee940 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -75,6 +75,8 @@ 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 _GET/_POST loginUserId parameter for non password login */ private string $login_user_id = ''; /** @var string source, either _GET or _POST or empty */ @@ -757,7 +759,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.username, eu.password, " . "eu.edit_group_id, " . "eg.name AS edit_group_name, eu.admin, " // additinal acl lists @@ -889,6 +891,7 @@ class Login // normal user processing // set class var and session var $_SESSION['EUID'] = $this->euid = (int)$res['edit_user_id']; + $_SESSION['ECUID'] = $this->ecuid = (string)$res['cuid']; // check if user is okay $this->loginCheckPermissions(); if ($this->login_error == 0) { @@ -1132,6 +1135,8 @@ 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']; // set additional acl $this->acl['additional_acl'] = [ 'user' => $_SESSION['USER_ADDITIONAL_ACL'], @@ -1862,6 +1867,8 @@ HTML; } // if there is none, there is none, saves me POST/GET check $this->euid = array_key_exists('EUID', $_SESSION) ? (int)$_SESSION['EUID'] : 0; + // TODO: allow load from cuid + // $this->ecuid = array_key_exists('ECUID', $_SESSION) ? (string)$_SESSION['ECUID'] : ''; // get login vars, are so, can't be changed // prepare // pass on vars to Object vars @@ -2111,6 +2118,7 @@ HTML; $this->session->sessionDestroy(); // unset euid $this->euid = null; + $this->ecuid = null; // then prints the login screen again $this->permission_okay = false; } @@ -2128,11 +2136,12 @@ HTML; if (empty($this->euid)) { return $this->permission_okay; } + // euid must match ecuid // 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, " // base lock flags . "eu.deleted, eu.enabled, eu.locked, " // date based lock @@ -2198,6 +2207,8 @@ HTML; } else { $this->login_error = 103; } + // set ECUID + $_SESSION['ECUID'] = $this->ecuid = (string)$res['cuid']; // if called from public, so we can check if the permissions are ok return $this->permission_okay; } @@ -2503,6 +2514,16 @@ HTML; { return (string)$this->euid; } + + /** + * Get the current set ECUID (edit user cuid) + * + * @return string ECUID as string + */ + public function loginGetEcid(): string + { + return (string)$this->ecuid; + } } // __END__ From 7354632479fa5e2b34271eac2e38c08b3f60eb16 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Tue, 3 Dec 2024 13:12:22 +0900 Subject: [PATCH 14/22] ACL Login update with cuuid and cuid add/update and move write log to login class Add a UUIDv4 column to edit_generic as cuuid, add the cuid column to all reads with the cuuid too The cuuid will replace the cuid and remove the EUID as the session login var Moved the adbEditLog to login class as writeLog and renamed the current private writeLog to writeEditLog which is only for internal logging in the class The Backend log class is deprecated and a new get all action var method has been added to get the action vars into the edit log --- 4dev/database/function/set_edit_generic.sql | 1 + 4dev/database/table/edit_generic.sql | 1 + 4dev/database/table/edit_log.sql | 3 + 4dev/tests/ACL/CoreLibsACLLoginTest.php | 2 + .../CoreLibsACLLogin_database_create_data.sql | 811 +++++++++--------- .../edit_tables_cuid_cuuid_update_add.sql | 26 + www/admin/class_test.admin.backend.php | 46 +- www/admin/class_test.login.php | 12 + www/admin/class_test.php | 3 + www/includes/admin_header.php | 2 +- www/lib/CoreLibs/ACL/Login.php | 257 +++++- www/lib/CoreLibs/Admin/Backend.php | 46 +- 12 files changed, 749 insertions(+), 461 deletions(-) create mode 100644 4dev/update/20241203_update_edit_tables/edit_tables_cuid_cuuid_update_add.sql 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/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_log.sql b/4dev/database/table/edit_log.sql index 97e22546..0ebf2599 100644 --- a/4dev/database/table/edit_log.sql +++ b/4dev/database/table/edit_log.sql @@ -12,6 +12,8 @@ CREATE TABLE edit_log ( 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/tests/ACL/CoreLibsACLLoginTest.php b/4dev/tests/ACL/CoreLibsACLLoginTest.php index c73d12d8..675ce307 100644 --- a/4dev/tests/ACL/CoreLibsACLLoginTest.php +++ b/4dev/tests/ACL/CoreLibsACLLoginTest.php @@ -244,6 +244,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'EUID' => 1, 'ECUID' => 'abc', + 'ECUUID' => '1233456-1234-1234-1234-123456789012', ], 2, [], @@ -262,6 +263,7 @@ final class CoreLibsACLLoginTest extends TestCase [ 'EUID' => 1, 'ECUID' => 'abc', + 'ECUUID' => '1233456-1234-1234-1234-123456789012', 'USER_NAME' => '', 'GROUP_NAME' => '', 'ADMIN' => 1, 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/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/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.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 a6355cec..3b75efc8 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -205,6 +205,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['ECUID'] . "
"; +print "ECUUID: " . $_SESSION['ECUUID'] . "
"; + print ""; # __END__ 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/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index dcbee940..48a73282 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -69,6 +69,7 @@ declare(strict_types=1); namespace CoreLibs\ACL; use CoreLibs\Security\Password; +use CoreLibs\Create\Uids; use CoreLibs\Convert\Json; class Login @@ -77,6 +78,8 @@ class Login 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 */ @@ -195,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 = []; @@ -381,6 +390,8 @@ class Login $_SESSION['DEFAULT_ACL_LIST'] = $this->default_acl_list; $_SESSION['DEFAULT_ACL_LIST_TYPE'] = $this->default_acl_list_type; + $this->loginSetEditLogWriteTypeAvailable(); + // this will be deprecated if ($this->options['auto_login'] === true) { $this->loginMainCall(); @@ -759,7 +770,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.cuid, 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 @@ -892,6 +903,7 @@ class Login // set class var and session var $_SESSION['EUID'] = $this->euid = (int)$res['edit_user_id']; $_SESSION['ECUID'] = $this->ecuid = (string)$res['cuid']; + $_SESSION['ECUUID'] = $this->ecuuid = (string)$res['cuuid']; // check if user is okay $this->loginCheckPermissions(); if ($this->login_error == 0) { @@ -1137,6 +1149,7 @@ class Login $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'], @@ -1430,7 +1443,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 +1584,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 +1740,8 @@ HTML; } } + // MARK: LOGGING + /** * writes detailed data into the edit user log table (keep log what user does) * @@ -1736,7 +1751,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 +1769,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) ? $_SESSION['USER_NAME'] ?? '' : $username, + !empty($_SESSION['EUID']) && is_numeric($_SESSION['EUID']) ? + $_SESSION['EUID'] : null, + !empty($_SESSION['ECUID']) && is_string($_SESSION['ECUID']) ? + $_SESSION['ECUID'] : null, + !empty($_SESSION['ECUUID']) && Uids::validateUuuidv4($_SESSION['ECUUID']) ? + $_SESSION['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() !== false ? + $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 @@ -1869,6 +2025,7 @@ HTML; $this->euid = array_key_exists('EUID', $_SESSION) ? (int)$_SESSION['EUID'] : 0; // TODO: allow load from cuid // $this->ecuid = array_key_exists('ECUID', $_SESSION) ? (string)$_SESSION['ECUID'] : ''; + // $this->ecuuid = array_key_exists('ECUUID', $_SESSION) ? (string)$_SESSION['ECUUID'] : ''; // get login vars, are so, can't be changed // prepare // pass on vars to Object vars @@ -1949,6 +2106,8 @@ HTML; $this->loginSetAcl(); } + // MARK: setters/getters + /** * Returns current set login_html content * @@ -2119,6 +2278,7 @@ HTML; // unset euid $this->euid = null; $this->ecuid = null; + $this->ecuuid = null; // then prints the login screen again $this->permission_okay = false; } @@ -2136,12 +2296,12 @@ HTML; if (empty($this->euid)) { return $this->permission_okay; } - // euid must match ecuid + // 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, eu.cuid, " + $q = "SELECT ep.filename, eu.cuid, eu.cuuid, " // base lock flags . "eu.deleted, eu.enabled, eu.locked, " // date based lock @@ -2209,6 +2369,7 @@ HTML; } // set ECUID $_SESSION['ECUID'] = $this->ecuid = (string)$res['cuid']; + $_SESSION['ECUUID'] = $this->ecuuid = (string)$res['cuuid']; // if called from public, so we can check if the permissions are ok return $this->permission_okay; } @@ -2520,10 +2681,20 @@ HTML; * * @return string ECUID as string */ - public function loginGetEcid(): 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 5fb2918b..1c088cae 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']) ? + '', + !empty($_SESSION['EUID']) && is_numeric($_SESSION['EUID']) ? $_SESSION['EUID'] : null, + !empty($_SESSION['ECUID']) && is_string($_SESSION['ECUID']) ? + $_SESSION['ECUID'] : null, + !empty($_SESSION['ECUUID']) && Uids::validateUuuidv4($_SESSION['ECUID']) ? + $_SESSION['ECUID'] : null, (string)$event, + '', $data_write, $data_binary, (string)$this->page_name, @@ -379,6 +408,7 @@ class Backend // row 4 $this->action ?? '', $this->action_id ?? '', + $this->action_sub_id ?? '', $this->action_yes ?? '', $this->action_flag ?? '', $this->action_menu ?? '', From 75e69932fc623c431d787d770cab4a7dd2593657 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 4 Dec 2024 14:03:38 +0900 Subject: [PATCH 15/22] Session class rewrite create new session on class call, there is no need to delay that at all new option to auto write close a session session_id and session_name are stored as class vars deprecate the __set/__get part because we do not want to set via ->session_var_name but use the set()/get() methods. They have been renamed from setS/getS... to set/get alone --- .../Create/CoreLibsCreateSessionTest.php | 307 ++++++------------ www/admin/class_test.db.php | 4 +- www/admin/class_test.session.php | 85 ++--- www/admin/class_test.session.read.php | 25 +- www/lib/CoreLibs/ACL/Login.php | 2 +- www/lib/CoreLibs/Admin/Backend.php | 2 +- www/lib/CoreLibs/Create/Session.php | 192 +++++++---- 7 files changed, 279 insertions(+), 338 deletions(-) diff --git a/4dev/tests/Create/CoreLibsCreateSessionTest.php b/4dev/tests/Create/CoreLibsCreateSessionTest.php index f9c89109..d3a4f735 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 * @@ -369,6 +294,8 @@ final class CoreLibsCreateSessionTest extends TestCase ]; } + // NOTE: with auto start session, we cannot test this in the command line + /** * method call test * @@ -384,74 +311,32 @@ final class CoreLibsCreateSessionTest extends TestCase * @param mixed $expected * @return void */ - public function testMethodSetGet($name, $input, $expected): void +/* public function testMethodSetGet($name, $input, $expected): void { - $session = new \CoreLibs\Create\Session(); - $session->setS($name, $input); + $session = new \CoreLibs\Create\Session('TEST_METHOD'); + $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 $this->assertFalse( - $session->issetS($name), + $session->isset($name), 'method isset assert false' ); - } - - /** - * magic call test - * - * @covers ::__set - * @covers ::__get - * @covers ::__isset - * @covers ::__unset - * @dataProvider sessionDataProvider - * @testdox __set/__get/__iseet/__unset $name with $input is $expected [$_dataName] - * - * @param string|int $name - * @param mixed $input - * @param mixed $expected - * @return void - */ - public function testMagicSetGet($name, $input, $expected): void - { - $session = new \CoreLibs\Create\Session(); - $session->$name = $input; - $this->assertEquals( - $expected, - $session->$name, - 'magic set assert' - ); - // isset true - $this->assertTrue( - isset($session->$name), - 'magic isset assert ok' - ); - unset($session->$name); - $this->assertEquals( - '', - $session->$name, - 'magic unset assert' - ); - // isset true - $this->assertFalse( - isset($session->$name), - 'magic isset assert false' - ); - } + } */ /** * unset all test @@ -461,33 +346,33 @@ final class CoreLibsCreateSessionTest extends TestCase * * @return void */ - public function testUnsetAll(): void +/* public function testUnsetAll(): void { $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->unsetAll(); // check unset foreach (array_keys($test_values) as $name) { $this->assertEquals( '', - $session->getS($name), + $session->get($name), 'unsert assert: ' . $name ); } - } + } */ } // __END__ diff --git a/www/admin/class_test.db.php b/www/admin/class_test.db.php index 6212c768..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) . "
"; @@ -255,7 +255,7 @@ SQL; $db->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) . "
"; diff --git a/www/admin/class_test.session.php b/www/admin/class_test.session.php index b0a9ba46..49f5b3ee 100644 --- a/www/admin/class_test.session.php +++ b/www/admin/class_test.session.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'; print ""; @@ -56,50 +55,30 @@ print ''; print '

' . $PAGE_NAME . '

'; $session_name = 'class-test-session'; +print "Valid session name static check for '" . $session_name . "': " + . \CoreLibs\Debug\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,12 +90,12 @@ 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') . "
"; +$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') . "
"; // test __get/__set $session->setwrap = 'YES, magic set _SESSION var'; /** @phpstan-ignore-line GET/SETTER */ print "[READ MAGIC] A setwrap: " . ($session->setwrap ?? '') . "
"; @@ -125,15 +104,16 @@ unset($session->setwrap); print "[READ MAGIC] unset setwrap: " . ($session->setwrap ?? '') . "
"; print "[READ MAGIC] unset Isset: " . (isset($session->setwrap) ? 'Yes' : 'No') . "
"; +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) . "
"; @@ -141,32 +121,39 @@ print "[ALL SESSION]: " . \CoreLibs\Debug\Support::printAr($_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]: " . \CoreLibs\Debug\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/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index 48a73282..fa8cd99f 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -1913,7 +1913,7 @@ HTML; $_SERVER['HTTP_ACCEPT'] ?? null, $_SERVER['HTTP_ACCEPT_CHARSET'] ?? null, $_SERVER['HTTP_ACCEPT_ENCODING'] ?? null, - $this->session->getSessionId() !== false ? + $this->session->getSessionId() !== '' ? $this->session->getSessionId() : null, // row 4 $action_set['action'] ?? null, diff --git a/www/lib/CoreLibs/Admin/Backend.php b/www/lib/CoreLibs/Admin/Backend.php index 1c088cae..82d36807 100644 --- a/www/lib/CoreLibs/Admin/Backend.php +++ b/www/lib/CoreLibs/Admin/Backend.php @@ -403,7 +403,7 @@ 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 ?? '', diff --git a/www/lib/CoreLibs/Create/Session.php b/www/lib/CoreLibs/Create/Session.php index ca3607e6..1bf910f5 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; } /** @@ -93,16 +97,18 @@ class Session return true; } + // MARK: init session (on class start) + /** - * start session with given session name if set + * 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 +121,82 @@ 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); + $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 +224,19 @@ 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(); + } + + // 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,6 +250,8 @@ class Session return session_write_close(); } + // MARK: session close and clean up + /** * Proper destroy a session * - unset the _SESSION array @@ -236,18 +300,20 @@ class Session return session_status(); } - // _SESSION set/unset methods + // MARK: _SESSION set/unset methods /** * unset all _SESSION entries * * @return void */ - public function unsetAllS(): void + public function unsetAll(): void { + $this->restartSession(); foreach (array_keys($_SESSION ?? []) as $name) { unset($_SESSION[$name]); } + $this->closeSessionCall(); } /** @@ -257,9 +323,11 @@ class 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|int $name, mixed $value): void { + $this->restartSession(); $_SESSION[$name] = $value; + $this->closeSessionCall(); } /** @@ -268,9 +336,9 @@ class Session * @param string|int $name value key to get from _SESSION * @return mixed value stored in _SESSION */ - public function getS(string|int $name): mixed + public function get(string|int $name): mixed { - return $_SESSION[$name] ?? ''; + return $_SESSION[$name] ?? null; } /** @@ -279,7 +347,7 @@ class Session * @param string|int $name Name to check for * @return bool True for set, False fornot set */ - public function issetS(string|int $name): bool + public function isset(string|int $name): bool { return isset($_SESSION[$name]); } @@ -290,14 +358,17 @@ class Session * @param string|int $name _SESSION key name to remove * @return void */ - public function unsetS(string|int $name): void + public function unset(string|int $name): void { - if (isset($_SESSION[$name])) { - unset($_SESSION[$name]); + if (!isset($_SESSION[$name])) { + return; } + $this->restartSession(); + unset($_SESSION[$name]); + $this->closeSessionCall(); } - // set/get below + // MARK: [DEPRECATED] __set/__get magic methods // ->var = value; /** @@ -306,10 +377,13 @@ class Session * @param string|int $name * @param mixed $value * @return void + * @deprecated use ->set() */ public function __set(string|int $name, mixed $value): void { + $this->restartSession(); $_SESSION[$name] = $value; + $this->closeSessionCall(); } /** @@ -317,6 +391,7 @@ class Session * * @param string|int $name * @return mixed If name is not found, it will return null + * @deprecated use ->get() */ public function __get(string|int $name): mixed { @@ -331,6 +406,7 @@ class Session * * @param string|int $name * @return bool + * @deprecated use ->isset() */ public function __isset(string|int $name): bool { @@ -342,12 +418,16 @@ class Session * * @param string|int $name * @return void + * @deprecated use ->unset() */ public function __unset(string|int $name): void { - if (isset($_SESSION[$name])) { - unset($_SESSION[$name]); + if (!isset($_SESSION[$name])) { + return; } + $this->restartSession(); + unset($_SESSION[$name]); + $this->closeSessionCall(); } } From f78c67c3786d3d186b8d87e22787c9feaaa0e050 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Wed, 4 Dec 2024 14:17:16 +0900 Subject: [PATCH 16/22] Fix ACL Login phpunit test --- 4dev/tests/ACL/CoreLibsACLLoginTest.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/4dev/tests/ACL/CoreLibsACLLoginTest.php b/4dev/tests/ACL/CoreLibsACLLoginTest.php index 675ce307..92d3d978 100644 --- a/4dev/tests/ACL/CoreLibsACLLoginTest.php +++ b/4dev/tests/ACL/CoreLibsACLLoginTest.php @@ -1089,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 () { @@ -1792,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 () { @@ -1906,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 () { @@ -1994,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 () { @@ -2090,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 () { From 2e1b767a85ecd8bc13779f7401c6c298914ed539 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Dec 2024 12:09:58 +0900 Subject: [PATCH 17/22] Fix Session class with Many update and get Update Login and Backend class to use interface when writing to avoid problems with not written _SESSION vars with session is in write close status --- .../Create/CoreLibsCreateSessionTest.php | 105 +++++++++-- www/admin/class_test.session.php | 36 +++- www/lib/CoreLibs/ACL/Login.php | 137 ++++++++------ www/lib/CoreLibs/Admin/Backend.php | 14 +- www/lib/CoreLibs/Create/Session.php | 171 +++++++++--------- 5 files changed, 294 insertions(+), 169 deletions(-) diff --git a/4dev/tests/Create/CoreLibsCreateSessionTest.php b/4dev/tests/Create/CoreLibsCreateSessionTest.php index d3a4f735..49b9720d 100644 --- a/4dev/tests/Create/CoreLibsCreateSessionTest.php +++ b/4dev/tests/Create/CoreLibsCreateSessionTest.php @@ -272,24 +272,27 @@ 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, ] ]; } @@ -299,21 +302,28 @@ final class CoreLibsCreateSessionTest extends TestCase /** * 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 { + 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, @@ -331,12 +341,80 @@ final class CoreLibsCreateSessionTest extends TestCase $session->get($name), 'method unset assert' ); - // iset false + // isset false $this->assertFalse( $session->isset($name), 'method isset assert false' ); - } */ + } + + /** + * Undocumented function + * + * @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 + * + * @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 testMany($set, $expected, $exception): void + { + 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->getMany(array_keys($set)), + 'set many failed' + ); + $session->unsetMany(array_keys($set)); + $this->assertEquals( + [], + $session->getMany(array_keys($set)), + 'unset many failed' + ); + } /** * unset all test @@ -346,8 +424,11 @@ final class CoreLibsCreateSessionTest extends TestCase * * @return void */ -/* public function testUnsetAll(): void + public function testUnsetAll(): void { + if (\CoreLibs\Get\System::checkCLI()) { + $this->markTestSkipped('Cannot run testUnsetAll in CLI'); + } $test_values = [ 'foo' => 'abc', 'bar' => '123' @@ -372,7 +453,7 @@ final class CoreLibsCreateSessionTest extends TestCase 'unsert assert: ' . $name ); } - } */ + } } // __END__ diff --git a/www/admin/class_test.session.php b/www/admin/class_test.session.php index 49f5b3ee..ed9439ef 100644 --- a/www/admin/class_test.session.php +++ b/www/admin/class_test.session.php @@ -45,6 +45,7 @@ $log = new CoreLibs\Logging\Logging([ 'log_file_id' => $LOG_FILE_ID, 'log_per_date' => true, ]); +use CoreLibs\Debug\Support; use CoreLibs\Create\Session; $PAGE_NAME = 'TEST CLASS: SESSION'; @@ -56,7 +57,7 @@ print '

' . $PAGE_NAME . '

'; $session_name = 'class-test-session'; print "Valid session name static check for '" . $session_name . "': " - . \CoreLibs\Debug\Support::prBl(Session::checkValidSessionName($session_name)) . "
"; + . Support::prBl(Session::checkValidSessionName($session_name)) . "
"; $var = 'foo'; $value = 'bar'; $session = new Session($session_name); @@ -96,13 +97,27 @@ print "[READ WRAP] Isset: " . ($session->isset('setwrap') ? 'Yes' : 'No') . "
unset('setwrap'); print "[READ WRAP] unset setwrap: " . $session->get('setwrap') . "
"; print "[READ WRAP] unset Isset: " . ($session->isset('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('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 @@ -115,7 +130,8 @@ try { print "[3 FAILED] Session start failed:
" . $e->getMessage() . "
" . $e . "

"; } -print "[ALL SESSION]: " . \CoreLibs\Debug\Support::printAr($_SESSION) . "
"; + +print "[ALL SESSION]: " . Support::printAr($_SESSION) . "
"; // close session $session->writeClose(); @@ -142,7 +158,7 @@ try { } $_SESSION['will_be_written_again'] = 'Full'; -print "[ALL SESSION]: " . \CoreLibs\Debug\Support::printAr($_SESSION) . "
"; +print "[ALL SESSION]: " . Support::printAr($_SESSION) . "
"; // close session $session->writeClose(); diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index fa8cd99f..ccf028ef 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -372,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"; @@ -387,8 +384,10 @@ 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(); @@ -580,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( @@ -615,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( @@ -901,9 +900,14 @@ class Login } // normal user processing // set class var and session var - $_SESSION['EUID'] = $this->euid = (int)$res['edit_user_id']; - $_SESSION['ECUID'] = $this->ecuid = (string)$res['cuid']; - $_SESSION['ECUUID'] = $this->ecuuid = (string)$res['cuuid']; + $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) { @@ -916,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 @@ -945,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 " @@ -1041,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 " @@ -1054,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 " @@ -1077,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)) { @@ -1182,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 @@ -1889,13 +1904,13 @@ HTML; ), [ // row 1 - empty($username) ? $_SESSION['USER_NAME'] ?? '' : $username, - !empty($_SESSION['EUID']) && is_numeric($_SESSION['EUID']) ? - $_SESSION['EUID'] : null, - !empty($_SESSION['ECUID']) && is_string($_SESSION['ECUID']) ? - $_SESSION['ECUID'] : null, - !empty($_SESSION['ECUUID']) && Uids::validateUuuidv4($_SESSION['ECUUID']) ? - $_SESSION['ECUUID'] : null, + 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, @@ -2022,10 +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 = array_key_exists('ECUID', $_SESSION) ? (string)$_SESSION['ECUID'] : ''; - // $this->ecuuid = array_key_exists('ECUUID', $_SESSION) ? (string)$_SESSION['ECUUID'] : ''; + // $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 @@ -2368,8 +2383,12 @@ HTML; $this->login_error = 103; } // set ECUID - $_SESSION['ECUID'] = $this->ecuid = (string)$res['cuid']; - $_SESSION['ECUUID'] = $this->ecuuid = (string)$res['cuuid']; + $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; } @@ -2652,7 +2671,7 @@ HTML; */ public function loginGetHeaderColor(): ?string { - return $_SESSION['HEADER_COLOR'] ?? null; + return $this->session->get('HEADER_COLOR'); } /** @@ -2663,7 +2682,7 @@ HTML; public function loginGetPages(): array { - return $_SESSION['PAGES'] ?? []; + return $this->session->get('PAGES'); } /** diff --git a/www/lib/CoreLibs/Admin/Backend.php b/www/lib/CoreLibs/Admin/Backend.php index 82d36807..6f02cd25 100644 --- a/www/lib/CoreLibs/Admin/Backend.php +++ b/www/lib/CoreLibs/Admin/Backend.php @@ -380,12 +380,12 @@ class Backend [ // row 1 '', - !empty($_SESSION['EUID']) && is_numeric($_SESSION['EUID']) ? - $_SESSION['EUID'] : null, - !empty($_SESSION['ECUID']) && is_string($_SESSION['ECUID']) ? - $_SESSION['ECUID'] : null, - !empty($_SESSION['ECUUID']) && Uids::validateUuuidv4($_SESSION['ECUID']) ? - $_SESSION['ECUID'] : 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, @@ -468,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/Create/Session.php b/www/lib/CoreLibs/Create/Session.php index 1bf910f5..f993735c 100644 --- a/www/lib/CoreLibs/Create/Session.php +++ b/www/lib/CoreLibs/Create/Session.php @@ -97,6 +97,23 @@ class Session return true; } + /** + * 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) /** @@ -162,6 +179,9 @@ class Session public function restartSession(): bool { if (!$this->checkActiveSession()) { + if (empty($this->session_name)) { + throw new \RuntimeException('[SESSION] Cannot restart session without a session name', 1); + } $this->startSessionCall(); return true; } @@ -235,6 +255,21 @@ class Session 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 /** @@ -256,13 +291,14 @@ class Session * 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 */ public function sessionDestroy(): bool { - $_SESSION = []; + $this->unsetAll(); if ( ini_get('session.use_cookies') && !ini_get('session.use_strict_mode') @@ -282,24 +318,12 @@ 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(); - } - // MARK: _SESSION set/unset methods /** @@ -310,8 +334,8 @@ class Session public function unsetAll(): void { $this->restartSession(); - foreach (array_keys($_SESSION ?? []) as $name) { - unset($_SESSION[$name]); + if (!empty($_SESSION)) { + $_SESSION = []; } $this->closeSessionCall(); } @@ -319,35 +343,64 @@ class Session /** * 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 set(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 get(string|int $name): mixed + public function get(string $name): mixed { 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 isset(string|int $name): bool + public function isset(string $name): bool { return isset($_SESSION[$name]); } @@ -355,10 +408,10 @@ 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 unset(string|int $name): void + public function unset(string $name): void { if (!isset($_SESSION[$name])) { return; @@ -368,65 +421,21 @@ class Session $this->closeSessionCall(); } - // MARK: [DEPRECATED] __set/__get magic methods - // ->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 - * @deprecated use ->set() */ - public function __set(string|int $name, mixed $value): void + public function unsetMany(array $set): void { $this->restartSession(); - $_SESSION[$name] = $value; - $this->closeSessionCall(); - } - - /** - * Undocumented function - * - * @param string|int $name - * @return mixed If name is not found, it will return null - * @deprecated use ->get() - */ - public function __get(string|int $name): mixed - { - if (isset($_SESSION[$name])) { - return $_SESSION[$name]; + foreach ($set as $key) { + if (!isset($_SESSION[$key])) { + continue; + } + unset($_SESSION[$key]); } - return null; - } - - /** - * Undocumented function - * - * @param string|int $name - * @return bool - * @deprecated use ->isset() - */ - public function __isset(string|int $name): bool - { - return isset($_SESSION[$name]); - } - - /** - * Undocumented function - * - * @param string|int $name - * @return void - * @deprecated use ->unset() - */ - public function __unset(string|int $name): void - { - if (!isset($_SESSION[$name])) { - return; - } - $this->restartSession(); - unset($_SESSION[$name]); $this->closeSessionCall(); } } From 0e5f6370528f858c2a16c9f023eee325c89d4131 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Dec 2024 12:11:07 +0900 Subject: [PATCH 18/22] Update Serial to Identity function Return status as varchar from change. clean up edit table SQL files with too many empty lines --- .../function/upgrade_serial_to_identity.sql | 69 ++++++++++--------- 4dev/database/table/edit_menu_group.sql | 2 - 4dev/database/table/edit_page_access.sql | 2 - 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/4dev/database/function/upgrade_serial_to_identity.sql b/4dev/database/function/upgrade_serial_to_identity.sql index 2c2ac212..0429f407 100644 --- a/4dev/database/function/upgrade_serial_to_identity.sql +++ b/4dev/database/function/upgrade_serial_to_identity.sql @@ -6,7 +6,8 @@ -- @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 --- @raises EXCEPTON on column not found, no linked sequence, more than one linked sequence found +-- @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, @@ -14,17 +15,18 @@ CREATE OR REPLACE FUNCTION upgrade_serial_to_identity( identity_type varchar = 'a', col_type varchar = '' ) -RETURNS void +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; + 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 @@ -59,6 +61,10 @@ BEGIN 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'; @@ -74,34 +80,31 @@ BEGIN SET attidentity = identity_type WHERE attrelid = tbl AND attname = col; - RAISE NOTICE 'Update to identity for table "%" and columen "%" with type "%"', tbl, col, identity_type; + 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 - IF col_type IN ('smallint', 'int', 'bigint', 'int2', 'int4', 'int8') 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 - RAISE NOTICE '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; - ELSE - RAISE NOTICE 'Invalid col type: %', col_type; + -- 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_menu_group.sql b/4dev/database/table/edit_menu_group.sql index 6ed9e6d0..6731052b 100644 --- a/4dev/database/table/edit_menu_group.sql +++ b/4dev/database/table/edit_menu_group.sql @@ -12,5 +12,3 @@ CREATE TABLE edit_menu_group ( flag VARCHAR, order_number INT NOT NULL ) INHERITS (edit_generic) WITHOUT OIDS; - - diff --git a/4dev/database/table/edit_page_access.sql b/4dev/database/table/edit_page_access.sql index 2e5caf44..a403e05a 100644 --- a/4dev/database/table/edit_page_access.sql +++ b/4dev/database/table/edit_page_access.sql @@ -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; - - From e57c336dba26c6bffbf64ea60efaf14171dc52e2 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Dec 2024 13:25:08 +0900 Subject: [PATCH 19/22] Clean up to use session methods and not _SESSION directly Add session_unset for unsetAll and rename this method to "clear" --- .../Create/CoreLibsCreateSessionTest.php | 2 +- www/admin/class_test.lang.php | 22 ++++++++++++------- www/admin/class_test.php | 4 ++-- www/lib/CoreLibs/ACL/Login.php | 9 ++++---- www/lib/CoreLibs/Create/Session.php | 13 ++++++++--- www/lib/CoreLibs/Output/Form/Token.php | 2 +- 6 files changed, 32 insertions(+), 20 deletions(-) diff --git a/4dev/tests/Create/CoreLibsCreateSessionTest.php b/4dev/tests/Create/CoreLibsCreateSessionTest.php index 49b9720d..2ac833fd 100644 --- a/4dev/tests/Create/CoreLibsCreateSessionTest.php +++ b/4dev/tests/Create/CoreLibsCreateSessionTest.php @@ -444,7 +444,7 @@ final class CoreLibsCreateSessionTest extends TestCase ); } // unset all - $session->unsetAll(); + $session->clear(); // check unset foreach (array_keys($test_values) as $name) { $this->assertEquals( 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.php b/www/admin/class_test.php index 3b75efc8..8b88bfe6 100644 --- a/www/admin/class_test.php +++ b/www/admin/class_test.php @@ -205,8 +205,8 @@ print "HOST: " . HOST_NAME . " => DB HOST: " . DB_CONFIG_NAME . " => " . Support print "DS is: " . DIRECTORY_SEPARATOR . "
"; print "SERVER HOST: " . $_SERVER['HTTP_HOST'] . "
"; -print "ECUID: " . $_SESSION['ECUID'] . "
"; -print "ECUUID: " . $_SESSION['ECUUID'] . "
"; +print "ECUID: " . $session->get('ECUID') . "
"; +print "ECUUID: " . $session->get('ECUUID') . "
"; print ""; diff --git a/www/lib/CoreLibs/ACL/Login.php b/www/lib/CoreLibs/ACL/Login.php index ccf028ef..691b03d6 100644 --- a/www/lib/CoreLibs/ACL/Login.php +++ b/www/lib/CoreLibs/ACL/Login.php @@ -2534,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; diff --git a/www/lib/CoreLibs/Create/Session.php b/www/lib/CoreLibs/Create/Session.php index f993735c..6873ee27 100644 --- a/www/lib/CoreLibs/Create/Session.php +++ b/www/lib/CoreLibs/Create/Session.php @@ -294,11 +294,15 @@ class Session * - unset session_name and session_id internal vars * - destroy session * - * @return bool + * @return bool True on successful session destroy */ public function sessionDestroy(): bool { - $this->unsetAll(); + // 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') @@ -331,9 +335,12 @@ class Session * * @return void */ - public function unsetAll(): void + public function clear(): void { $this->restartSession(); + if (!session_unset()) { + throw new \RuntimeException('[SESSION] Cannot unset session vars', 1); + } if (!empty($_SESSION)) { $_SESSION = []; } 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); From d070c4e46180dbf6caa9684913da3d250686e24f Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Dec 2024 13:59:20 +0900 Subject: [PATCH 20/22] phan min php set to 8.2 --- .phan/config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From eeaff3042e9123425aaded45149690ed7e9b6545 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Dec 2024 14:16:57 +0900 Subject: [PATCH 21/22] phpstan config file update with phpVersion information --- phpstan.neon | 3 +++ 1 file changed, 3 insertions(+) diff --git a/phpstan.neon b/phpstan.neon index 869c5c48..1d6c4ff5 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -14,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: From 5f89917abd3e84b36dd3f07e03cceff3975a0a2d Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 5 Dec 2024 14:30:12 +0900 Subject: [PATCH 22/22] Add composer keywords --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 1b1a396c..9d009a99 100644 --- a/composer.json +++ b/composer.json @@ -2,6 +2,7 @@ "name": "egrajp/development-corelibs-dev", "version": "dev-master", "description": "CoreLibs: Development package", + "keywords": ["corelib", "logging", "database", "templating", "tools"], "type": "library", "require": { "php": ">=8.3"