From b3e35b5d94a220ea012257b9ea4eabbccd86cb39 Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Fri, 16 Jun 2023 13:25:07 +0900 Subject: [PATCH] CoreLibs updates --- www/composer.lock | 2 +- www/vendor/composer/installed.json | 2 +- www/vendor/composer/installed.php | 2 +- .../publish/last.published | 2 +- .../src/Admin/EditBase.php | 3 +- .../src/DB/Extended/ArrayIO.php | 5 +- .../corelibs-composer-all/src/DB/IO.php | 421 ++++++++++++++++-- .../src/DB/Options/Convert.php | 63 +++ .../src/Logging/Logging.php | 44 +- .../src/Output/Form/Generate.php | 3 +- .../src/Output/Image.php | 11 +- .../test/phpunit/DB/CoreLibsDBIOTest.php | 214 ++++++++- .../Logging/CoreLibsLoggingLoggingTest.php | 38 ++ 13 files changed, 753 insertions(+), 57 deletions(-) create mode 100644 www/vendor/egrajp/corelibs-composer-all/src/DB/Options/Convert.php diff --git a/www/composer.lock b/www/composer.lock index 5d6bc103..d9009567 100644 --- a/www/composer.lock +++ b/www/composer.lock @@ -12,7 +12,7 @@ "dist": { "type": "path", "url": "/storage/var/www/html/developers/clemens/core_data/composer-packages/CoreLibs-Composer-All", - "reference": "b82e08ba05efc11dd75108b3b762becf32d4eb19" + "reference": "e0f8bad2d97bb7f3a5332528f799bae2942354c8" }, "require": { "php": ">=8.1", diff --git a/www/vendor/composer/installed.json b/www/vendor/composer/installed.json index 25970fca..0fed1d96 100644 --- a/www/vendor/composer/installed.json +++ b/www/vendor/composer/installed.json @@ -7,7 +7,7 @@ "dist": { "type": "path", "url": "/storage/var/www/html/developers/clemens/core_data/composer-packages/CoreLibs-Composer-All", - "reference": "b82e08ba05efc11dd75108b3b762becf32d4eb19" + "reference": "e0f8bad2d97bb7f3a5332528f799bae2942354c8" }, "require": { "php": ">=8.1", diff --git a/www/vendor/composer/installed.php b/www/vendor/composer/installed.php index 84cbb1cc..0b9c39fa 100644 --- a/www/vendor/composer/installed.php +++ b/www/vendor/composer/installed.php @@ -13,7 +13,7 @@ 'egrajp/corelibs-composer-all' => array( 'pretty_version' => 'dev-development', 'version' => 'dev-development', - 'reference' => 'b82e08ba05efc11dd75108b3b762becf32d4eb19', + 'reference' => 'e0f8bad2d97bb7f3a5332528f799bae2942354c8', 'type' => 'library', 'install_path' => __DIR__ . '/../egrajp/corelibs-composer-all', 'aliases' => array(), diff --git a/www/vendor/egrajp/corelibs-composer-all/publish/last.published b/www/vendor/egrajp/corelibs-composer-all/publish/last.published index 222f909c..47da986f 100644 --- a/www/vendor/egrajp/corelibs-composer-all/publish/last.published +++ b/www/vendor/egrajp/corelibs-composer-all/publish/last.published @@ -1 +1 @@ -9.0.8 +9.1.0 diff --git a/www/vendor/egrajp/corelibs-composer-all/src/Admin/EditBase.php b/www/vendor/egrajp/corelibs-composer-all/src/Admin/EditBase.php index 6a18572b..05538daf 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/Admin/EditBase.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/Admin/EditBase.php @@ -41,7 +41,8 @@ class EditBase /** * construct form generator * - * @param array $db_config db config array, mandatory + * phpcs:ignore + * @param array{db_name:string,db_user:string,db_pass:string,db_host:string,db_port:int,db_schema:string,db_encoding:string,db_type:string,db_ssl:string,db_convert_type?:string[]} $db_config db config array, mandatory * @param \CoreLibs\Logging\Logging $log Logging class, null auto set * @param \CoreLibs\Language\L10n $l10n l10n language class, null auto set * @param \CoreLibs\ACL\Login $login login class for ACL settings diff --git a/www/vendor/egrajp/corelibs-composer-all/src/DB/Extended/ArrayIO.php b/www/vendor/egrajp/corelibs-composer-all/src/DB/Extended/ArrayIO.php index 2dce7bb7..9b6b734c 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/DB/Extended/ArrayIO.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/DB/Extended/ArrayIO.php @@ -54,7 +54,8 @@ class ArrayIO extends \CoreLibs\DB\IO * constructor for the array io class, set the * primary key name automatically (from array) * - * @param array $db_config db connection config + * phpcs:ignore + * @param array{db_name:string,db_user:string,db_pass:string,db_host:string,db_port:int,db_schema:string,db_encoding:string,db_type:string,db_ssl:string,db_convert_type?:string[]} $db_config db connection config * @param array $table_array table array config * @param string $table_name table name string * @param \CoreLibs\Logging\Logging $log Logging class @@ -587,7 +588,7 @@ class ArrayIO extends \CoreLibs\DB\IO // get it at the end, cause now we can be more sure of no double IDs, etc reset($this->table_array); // create select part & addition FK part - foreach ($this->table_array as $column => $data_array) { + foreach (array_keys($this->table_array) as $column) { // check FK ... if ( isset($this->table_array[$column]['fk']) && diff --git a/www/vendor/egrajp/corelibs-composer-all/src/DB/IO.php b/www/vendor/egrajp/corelibs-composer-all/src/DB/IO.php index 39dd0d0a..622c4f72 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/DB/IO.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/DB/IO.php @@ -259,6 +259,8 @@ namespace CoreLibs\DB; use CoreLibs\Create\Hash; use CoreLibs\Debug\Support; use CoreLibs\Create\Uids; +use CoreLibs\Convert\Json; +use CoreLibs\DB\Options\Convert; // 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 @@ -328,6 +330,17 @@ class IO private string $db_type; /** @var string ssl flag (for postgres only), disable, allow, prefer, require */ private string $db_ssl; + /** @var array flag for converting types from settings */ + private array $db_convert_type = []; + // convert type settings + // 0: OFF (CONVERT_OFF) + // >0: ON + // 1: convert intN/bool (CONVERT_ON) + // 2: convert json/jsonb to array (CONVERT_JSON) + // 4: convert numeric/floatN to float (CONVERT_NUMERIC) + // 8: convert bytea to string data (CONVERT_BYTEA) + /** @var int type settings as bit mask, 0 for off, anything >2 will aways set 1 too */ + private int $convert_type = Convert::off->value; // FOR BELOW: (This should be private and only readable through some method) // cursor array for cached readings /** @var array extended cursoers string index with content */ @@ -343,6 +356,8 @@ class IO private array $field_names = []; /** @var array field type names */ private array $field_types = []; + /** @var array field name to type */ + private array $field_name_types = []; /** @var array always return as array, even if only one */ private array $insert_id_arr = []; /** @var string primary key name for insert recovery from insert_id_arr */ @@ -392,8 +407,11 @@ class IO public \CoreLibs\Logging\Logging $log; /** - * main DB concstructor with auto connection to DB and failure set on failed connection - * @param array $db_config DB configuration array + * main DB concstructor with auto connection to DB + * and failure set on failed connection + * + * phpcs:ignore + * @param array{db_name:string,db_user:string,db_pass:string,db_host:string,db_port:int,db_schema:string,db_encoding:string,db_type:string,db_ssl:string,db_convert_type?:string[]} $db_config DB configuration array * @param \CoreLibs\Logging\Logging $log Logging class */ public function __construct( @@ -402,17 +420,8 @@ class IO ) { // attach logger $this->log = $log; - // sets the names (for connect/reconnect) - $this->db_name = $db_config['db_name'] ?? ''; - $this->db_user = $db_config['db_user'] ?? ''; - $this->db_pwd = $db_config['db_pass'] ?? ''; - $this->db_host = $db_config['db_host'] ?? ''; - $this->db_port = !empty($db_config['db_port']) ? (int)$db_config['db_port'] : 5432; - // do not set to 'public' if not set, because the default is already public - $this->db_schema = !empty($db_config['db_schema']) ? $db_config['db_schema'] : ''; - $this->db_encoding = !empty($db_config['db_encoding']) ? $db_config['db_encoding'] : ''; - $this->db_type = $db_config['db_type'] ?? ''; - $this->db_ssl = !empty($db_config['db_ssl']) ? $db_config['db_ssl'] : 'allow'; + // set the config options + $this->__setConfigOptions($db_config); // set debug, either via global var, or from config, else set to false $this->dbSetDebug( // set if logging level is Debug @@ -497,6 +506,84 @@ class IO // PRIVATE METHODS // ************************************************************* + /** + * Setup DB config and options + * + * phpcs:ignore + * @param array{db_name:string,db_user:string,db_pass:string,db_host:string,db_port:int,db_schema:string,db_encoding:string,db_type:string,db_ssl:string,db_convert_type?:string[]} $db_config + * @return bool + */ + private function __setConfigOptions(array $db_config): bool + { + // sets the names (for connect/reconnect) + $this->db_name = $db_config['db_name'] ?? ''; + $this->db_user = $db_config['db_user'] ?? ''; + $this->db_pwd = $db_config['db_pass'] ?? ''; + $this->db_host = $db_config['db_host'] ?? ''; + // port + $this->db_port = !empty($db_config['db_port']) ? + (int)$db_config['db_port'] : 5432; + if ($this->db_port < 0 || $this->db_port > 65535) { + $this->db_port = 5432; + } + // do not set to 'public' if not set, because the default is already public + $this->db_schema = !empty($db_config['db_schema']) ? + $db_config['db_schema'] : ''; + $this->db_encoding = !empty($db_config['db_encoding']) ? + $db_config['db_encoding'] : ''; + // db type + $this->db_type = $db_config['db_type'] ?? ''; + if (!in_array($this->db_type, ['pgsql'])) { + $this->db_type = 'pgsql'; + } + // ssl setting + $this->db_ssl = !empty($db_config['db_ssl']) ? + $db_config['db_ssl'] : 'allow'; + if (!in_array($this->db_ssl, ['allow', 'disable', 'require', 'prefer'])) { + $this->db_ssl = 'allow'; + } + // trigger convert type + // ['on', 'json', 'numeric', 'bytea'] allowed + // if on is not set but other valid than on is assumed + foreach ($db_config['db_convert_type'] ?? [] as $db_convert_type) { + if (!in_array($db_convert_type, ['on', 'json', 'numeric', 'bytea'])) { + continue; + } + $this->db_convert_type[] = $db_convert_type; + $this->__setConvertType($db_convert_type); + } + + // return status true: ok, false: options error + return true; + } + + /** + * Set the convert bit flags + * + * @param string $db_convert_type One of 'on', 'json', 'numeric', 'bytea' + * @return void + */ + private function __setConvertType(string $db_convert_type): void + { + switch ($db_convert_type) { + case 'on': + $this->convert_type |= Convert::on->value; + break; + case 'json': + $this->convert_type |= Convert::on->value; + $this->convert_type |= Convert::json->value; + break; + case 'numeric': + $this->convert_type |= Convert::on->value; + $this->convert_type |= Convert::numeric->value; + break; + case 'bytea': + $this->convert_type |= Convert::on->value; + $this->convert_type |= Convert::bytea->value; + break; + } + } + /** * based on $this->db_type * here we need to load the db pgsql include one @@ -525,8 +612,10 @@ class IO } /** - * internal connection function. Used to connect to the DB if there is no connection done yet. + * internal connection function. + * Used to connect to the DB if there is no connection done yet. * Called before any execute + * * @return bool true on successfull connect, false if failed */ private function __connectToDB(): bool @@ -571,6 +660,7 @@ class IO /** * close db connection * only used by the deconstructor + * * @return void has no return */ private function __closeDB(): void @@ -585,6 +675,7 @@ class IO * checks if query is a SELECT, SHOW or WITH, if not error, 0 return * NOTE: * Query needs to start with SELECT, SHOW or WITH + * * @param string $query query to check * @return bool true if matching, false if not */ @@ -602,6 +693,7 @@ class IO * if pure is set to true, only when INSERT is set will return true * NOTE: * Queries need to start with INSERT, UPDATE, DELETE. Anything else is ignored + * * @param string $query query to check * @param bool $pure pure check (only insert), default false * @return bool true if matching, false if not @@ -620,6 +712,7 @@ class IO /** * returns true if the query starts with UPDATE * query NEEDS to start with UPDATE + * * @param string $query query to check * @return bool returns true if the query starts with UPDATE */ @@ -635,6 +728,7 @@ class IO * internal funktion that creates the array * NOTE: * used in db_dump_data only + * * @param array $array array to print * @return string string with printed and formated array */ @@ -698,15 +792,46 @@ class IO . \CoreLibs\Debug\Support::prAr($error_data) . ']'; } + // we need to trace back where the first non class call was done + // add this stack trace to the context + $call_stack = []; + foreach (array_reverse(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)) as $call_trace) { + if ( + !empty($call_trace['file']) && + str_ends_with($call_trace['file'], '/DB/IO.php') + ) { + break; + } + $call_stack[] = + ($call_trace['file'] ?? 'n/f') . ':' + . ($call_trace['line'] ?? '-') . ':' + . (!empty($call_trace['class']) ? $call_trace['class'] . '->' : '') + . $call_trace['function']; + } + $context = [ + 'call_trace' => array_reverse($call_stack) + ]; switch ($id) { case 'DB_ERROR': - $this->log->error($debug_id . ' :' . $prefix . $error_string); + $this->log->error( + $debug_id . ' :' . $prefix . $error_string, + $context + ); break; case 'DB_WARNING': - $this->log->warning($debug_id . ' :' . $prefix . $error_string); + $this->log->warning( + $debug_id . ' :' . $prefix . $error_string, + $context + ); break; default: - $this->log->debug($debug_id, $error_string, $prefix); + // used named arguments so we can easy change the order of debug + $this->log->debug( + group_id: $debug_id, + message: $error_string, + prefix: $prefix, + context: $context + ); break; } } @@ -740,6 +865,7 @@ class IO * Is called on base queries to reset error before each run * Recent error history can be checked with * dbGetErrorHistory or dbGetWarningHistory + * * @return void */ private function __dbErrorReset(): void @@ -751,6 +877,7 @@ class IO /** * Check if there is a cursor and write this cursors error info + * * @param \PgSql\Result|false $cursor current cursor for pg_result_error, * pg_last_error too, but pg_result_error * is more accurate (PgSql\Result) @@ -811,6 +938,7 @@ class IO * error level, source as :: separated string * additional pg error message if exists and optional msg given on error call * all error messages are grouped by error_history_id set when errors are reset + * * @param string $level * @param string $error_id * @param string $where_called @@ -841,6 +969,7 @@ class IO /** * write an error + * * @param integer $error_id Any Error ID, used in debug message string * @param \PgSql\Result|false $cursor Optional cursor, passed on to preprocessor * @param string $msg optional message added to debug @@ -868,6 +997,7 @@ class IO /** * write a warning + * * @param integer $warning_id Integer warning id added to debug * @param \PgSql\Result|false $cursor Optional cursor, passed on to preprocessor * @param string $msg optional message added to debug @@ -895,6 +1025,7 @@ class IO /** * if there is the 'to_encoding' var set, * and the field is in the wrong encoding converts it to the target + * * @param array|false $row Array from fetch_row * @return array|false Convert fetch_row array, or false */ @@ -903,11 +1034,8 @@ class IO if (is_bool($row)) { return false; } - // only do if array, else pass through row (can be false) - if ( - !is_array($row) || - empty($this->to_encoding) - ) { + // do not do anything if no to encoding is set + if (empty($this->to_encoding)) { return $row; } // go through each row and convert the encoding if needed @@ -929,8 +1057,70 @@ class IO return $row; } + /** + * Convert column content to the type in the name/pos field + * Note that on default it will only convert types that 100% map to PHP + * - intN + * - bool + * everything else will stay string. + * Fruther flags in the conert_type allow to convert: + * - json/jsonb to array + * - bytea to string + * Dangerous convert: + * - numeric/float to float (precision can be lost) + * + * @param array|false $row + * @return array|false + */ + private function __dbConvertType(array|false $row): array|false + { + if (is_bool($row)) { + return false; + } + // if convert type is not turned on + if (!$this->convert_type) { + return $row; + } + foreach ($row as $key => $value) { + // always bool/int + if ( + $this->dbGetFieldType($key) != 'interval' && + str_starts_with($this->dbGetFieldType($key) ?: '', 'int') + ) { + $row[$key] = (int)$value; + } + if ($this->dbGetFieldType($key) == 'bool') { + $row[$key] = $this->dbBoolean($value); + } + if ( + $this->convert_type & Convert::json->value && + str_starts_with($this->dbGetFieldType($key) ?: '', 'json') + ) { + $row[$key] = Json::jsonConvertToArray($value); + } + if ( + $this->convert_type & Convert::numeric->value && + ( + str_starts_with($this->dbGetFieldType($key) ?: '', 'numeric') || + str_starts_with($this->dbGetFieldType($key) ?: '', 'float') + // $this->dbGetFieldType($key) == 'real' + ) + ) { + $row[$key] = (float)$value; + } + if ( + $this->convert_type & Convert::bytea->value && + $this->dbGetFieldType($key) == 'bytea' + ) { + $row[$key] = $this->dbUnescapeBytea($value); + } + } + return $row; + } + /** * for debug purpose replaces $1, $2, etc with actual data + * * @param string $query Query to replace values in * @param array $data The data array * @return string string of query with data inside @@ -964,7 +1154,9 @@ class IO } /** - * extracts schema and table from the query, if no schema returns just empty string + * extracts schema and table from the query, + * if no schema returns just empty string + * * @param string $query insert/select/update/delete query * @return array array with schema and table */ @@ -1003,6 +1195,7 @@ class IO /** * check if there is another query running, or do we hang after a * PHP error + * * @param integer $timeout_seconds For complex timeout waits, default 3 seconds * @return bool True for connection OK, else false */ @@ -1021,6 +1214,7 @@ class IO /** * dbReturn * Read data from previous written data cache + * * @param string $query_hash The hash for the current query * @param bool $assoc_only Only return assoc value (key named) * @return array Current position query data from cache @@ -1099,6 +1293,7 @@ class IO * - checks for insert if returning is set/pk name * - sets internal hash for query * - checks multiple call count + * * @param string $query Query string * @param array $params Query params, needed for hash creation * @param string $pk_name primary key @@ -1183,7 +1378,7 @@ class IO ) { $this->returning_id = true; } - // $this->debug('DB IO', 'Q: '.$this->query.', RETURN: '.$this->returning_id); + // $this->debug('DB IO', 'Q: ' . $this->query . ', RETURN: ' . $this->returning_id); // for DEBUG, only on first time ;) $this->__dbDebug( 'db', @@ -1229,6 +1424,7 @@ class IO /** * runs post execute for rows affected, field names, inserted primary key, etc + * * @return bool true on success or false if an error occured */ private function __dbPostExec(): bool @@ -1254,11 +1450,18 @@ class IO $this->field_names = []; for ($i = 0; $i < $this->num_fields; $i++) { $this->field_names[] = $this->db_functions->__dbFieldName($this->cursor, $i) ?: ''; + // if (!empty($this->field_names[$i])) + // $this->field_name_types[$this->field_names[$i]] = null; } $this->field_types = []; for ($i = 0; $i < $this->num_fields; $i++) { $this->field_types[] = $this->db_functions->__dbFieldType($this->cursor, $i) ?: ''; } + // combined array + $this->field_name_types = array_combine( + $this->field_names, + $this->field_types + ); } elseif ($this->__checkQueryForInsert($this->query)) { // if not select do here // count affected rows @@ -1292,6 +1495,7 @@ class IO * insert_id_ext [DEPRECATED, all in insert_id_arr] * - holds all returning as array * TODO: Only use insert_id_arr and use functions to get ok array or single + * * @param bool $returning_id * @param string $query * @param string|null $pk_name @@ -1384,6 +1588,7 @@ class IO * closes the db_connection * normally this is not used, as the class deconstructor closes * the connection down + * * @return void has no return */ public function dbClose(): void @@ -1405,6 +1610,7 @@ class IO * returns the db init error * if failed to connect it is set to false * else true + * * @return bool Connection status */ public function dbGetConnectionStatus(): bool @@ -1414,6 +1620,7 @@ class IO /** * get certain settings like username, db name + * * @param string $name what setting to query * @return int|string|bool setting value, if not allowed name return false */ @@ -1458,6 +1665,7 @@ class IO /** * prints out status info from the connected DB (might be usefull for debug stuff) + * * @param bool $log Show db connection info, default true * if set to false won't write to error_msg var * @param bool $strip Strip all HTML @@ -1497,6 +1705,7 @@ class IO /** * Server version as integer value + * * @return integer Version as integer */ public function dbVersionNumeric(): int @@ -1506,6 +1715,7 @@ class IO /** * return current database version (server side) as string + * * @return string database version as string */ public function dbVersion(): string @@ -1515,6 +1725,7 @@ class IO /** * extended version info, can access all additional information data + * * @param string $parameter Array parameter name, if not valid returns * empty string * @param bool $strip Strip extended server info string, default true @@ -1528,6 +1739,7 @@ class IO /** * All possible parameter names for dbVersionInfo + * * @return array List of all parameter names */ public function dbVersionInfoParameters(): array @@ -1537,6 +1749,7 @@ class IO /** * returns bool true or false if the string matches the database version + * * @param string $compare string to match in type =X.Y, >X.Y, =X.Y * @return bool true for ok, false on not ok */ @@ -1605,6 +1818,7 @@ class IO /** * dumps ALL data for this query, OR if no query given all in cursor_ext array + * * @param string $query Query, if given, only from this quey (if found) * else current cursor * @return string Formated string with all the data in the array @@ -1632,6 +1846,7 @@ class IO /** * neutral function to escape a string for DB writing + * * @param string|int|float|bool $string string to escape * @return string escaped string */ @@ -1643,6 +1858,7 @@ class IO /** * neutral function to escape a string for DB writing * this one adds '' quotes around the string + * * @param string|int|float|bool $string string to escape * @return string escaped string */ @@ -1653,6 +1869,7 @@ class IO /** * string escape for column and table names + * * @param string $string string to escape * @return string escaped string */ @@ -1663,6 +1880,7 @@ class IO /** * escape data for writing to bytea type column field + * * @param string $data data to escape to bytea * @return string escaped bytea string */ @@ -1673,6 +1891,7 @@ class IO /** * unescape bytea data back to normal binrary data + * * @param string $bytea bytea data stream * @return string binary data string */ @@ -1683,6 +1902,7 @@ class IO /** * clear up any data for valid DB insert + * * @param int|float|string|bool|null $value to escape data * @param string $kbn escape trigger type * @return string escaped value @@ -1749,10 +1969,11 @@ class IO * if the input is a single char 't' or 'f * it will return the bool value instead * also converts smallint 1/0 to true false + * * @param string|bool|int $string 't' / 'f' or any string, or bool true/false * @param bool $rev do reverse (bool to string) - * @return bool|string correct php bool true/false - * or postgresql 't'/'f' + * @return bool|string [default=false]: corretc postgresql -> php, + * true: convert php to postgresql */ public function dbBoolean(string|bool|int $string, bool $rev = false): bool|string { @@ -1784,6 +2005,7 @@ class IO /** * only for postgres. pretty formats an age or datetime difference string + * * @param string $interval Age or interval/datetime difference * @param bool $show_micro micro on off (default false) * @return string Y/M/D/h/m/s formatted string (like timeStringFormat) @@ -1835,6 +2057,7 @@ class IO /** * this is only needed for Postgresql. Converts postgresql arrays to PHP * Recommended to rather user 'array_to_json' instead and convet JSON in PHP + * * @param string $text input text to parse to an array * @return array PHP array of the parsed data * @deprecated Recommended to use 'array_to_json' in PostgreSQL instead @@ -1851,6 +2074,7 @@ class IO /** * returns an array of the table with columns and values. FALSE on no table found + * * @param string $table table name * @param string $schema optional schema name * @return array|false array of table data, false on error (table not found) @@ -1954,6 +2178,10 @@ class IO 'data' => [], // field names as array 'field_names' => [], + // field types as array (pos in field names is pos here) + 'field_types' => [], + // name to type assoc array (from field names and field types) + 'field_name_types' => [], // number of fields (field names) 'num_fields' => 0, // number of rows that will be maximum returned @@ -2117,6 +2345,12 @@ class IO ); } $this->field_types = $this->cursor_ext[$query_hash]['field_types']; + // combined name => type + $this->cursor_ext[$query_hash]['field_name_types'] = array_combine( + $this->field_names, + $this->field_types + ); + $this->field_name_types = $this->cursor_ext[$query_hash]['field_name_types']; // reset first call var $first_call = false; // reset the internal pos counter @@ -2139,9 +2373,11 @@ class IO !is_int($this->cursor_ext[$query_hash]['cursor']) ) { $return = $this->__dbConvertEncoding( - $this->db_functions->__dbFetchArray( - $this->cursor_ext[$query_hash]['cursor'], - $this->db_functions->__dbResultType($assoc_only) + $this->__dbConvertType( + $this->db_functions->__dbFetchArray( + $this->cursor_ext[$query_hash]['cursor'], + $this->db_functions->__dbResultType($assoc_only) + ) ) ); $this->cursor_ext[$query_hash]['log'][] = 'DB Reading data: ' @@ -2238,6 +2474,7 @@ class IO * for INSERT INTO queries it is highly recommended to set the pk_name to avoid an * additional read from the database for the PK NAME * Wrapper for dbExecParams without params + * * @param string $query the query, if not given, * the query class var will be used * if this was not set, method will quit with false @@ -2307,10 +2544,9 @@ class IO } } - // add adbExecParams(string $query = '', array $params = [], string $pk_name = ') - /** * executes a cursor and returns the data, if no more data 0 will be returned + * * @param \PgSql\Result|false $cursor the cursor from db_exec or * pg_query/pg_exec/mysql_query * if not set will use internal cursor, @@ -2333,9 +2569,11 @@ class IO return false; } return $this->__dbConvertEncoding( - $this->db_functions->__dbFetchArray( - $cursor, - $this->db_functions->__dbResultType($assoc_only) + $this->__dbConvertType( + $this->db_functions->__dbFetchArray( + $cursor, + $this->db_functions->__dbResultType($assoc_only) + ) ) ); } @@ -2343,6 +2581,7 @@ class IO /** * returns the FIRST row of the given query * wrapper for dbReturnRowParms + * * @param string $query the query to be executed * @param bool $assoc_only if true, only return assoc entry (default false) * @return array|false row array or false on error @@ -2388,6 +2627,7 @@ class IO /** * creates an array of hashes of the query (all data) * Wrapper for dbReturnArrayParams + * * @param string $query the query to be executed * @param bool $assoc_only if true, only name ref are returned (default true) * @return array|false array of hashes (row -> fields), false on error @@ -2432,6 +2672,20 @@ class IO return $rows; } + // *************************** + // CURSOR RETURN + // *************************** + + /** + * Get current set cursor or false if not set or error + * + * @return \PgSql\Result|false + */ + public function dbGetCursor(): \PgSql\Result|false + { + return $this->cursor; + } + // *************************** // CURSOR EXT CACHE RESET // *************************** @@ -2557,6 +2811,7 @@ class IO /** * gets how often a query was called already + * * @param string $query query string * @param array $params If the query is params type we need params * data to create a unique call one, optional @@ -2581,6 +2836,7 @@ class IO * for INSERT INTO queries it is highly recommended * to set the pk_name to avoid an additional * read from the database for the PK NAME + * * @param string $stm_name statement name * @param string $query queryt string to run * @param string $pk_name optional primary key @@ -2693,6 +2949,7 @@ class IO /** * runs a prepare query + * * @param string $stm_name statement name for the query to run * @param array $data data to run for this query, empty array for none * @return \PgSql\Result|false false on error, or result on OK @@ -2791,6 +3048,7 @@ class IO * executes the query async so other methods can be run at the same time * Wrapper for dbExecParamsAsync * NEEDS : dbCheckAsync + * * @param string $query query to run * @param string $pk_name optional primary key name, only used with * insert for returning call @@ -2890,6 +3148,7 @@ class IO /** * checks a previous async query and returns data if finished * NEEDS : dbExecAsync + * * @return \PgSql\Result|bool cursor resource if the query is still running, * false if an error occured or cursor of that query */ @@ -2930,6 +3189,7 @@ class IO /** * Returns the current async running query hash + * * @return string Current async running query hash */ public function dbGetAsyncRunning(): string @@ -2948,6 +3208,7 @@ class IO /** * writes into one table based on array of table columns + * * @param array $write_array list of elements to write * @param array $not_write_array list of elements not to write * @param int $primary_key id key to decide if we write insert or update @@ -3163,6 +3424,54 @@ class IO return $this->db_debug; } + /** + * Undocumented function + * + * @param Convert $convert + * @return void + */ + public function dbSetConvertFlag(Convert $convert): void + { + $this->convert_type |= $convert->value; + } + + /** + * Undocumented function + * + * @param Convert $convert + * @return void + */ + public function dbUnsetConvertFlag(Convert $convert): void + { + $this->convert_type &= ~$convert->value; + } + + /** + * Reset to origincal config file set + * + * @return void + */ + public function dbResetConvertFlag(): void + { + foreach ($this->db_convert_type as $db_convert_type) { + $this->__setConvertType($db_convert_type); + } + } + + /** + * Undocumented function + * + * @param Convert $convert + * @return bool + */ + public function dbGetConvertFlag(Convert $convert): bool + { + if ($this->convert_type & $convert->value) { + return true; + } + return false; + } + /** * set max query calls, set to -1 to disable loop * protection. this will generate a warning @@ -3314,12 +3623,11 @@ class IO * Alternative use dbSetEcnoding to trigger encoding change on the DB side * Set to empty string to turn off * @param string $encoding PHP Valid encoding to set - * @return string Current set encoding + * @return void */ - public function dbSetToEncoding(string $encoding): string + public function dbSetToEncoding(string $encoding): void { $this->to_encoding = $encoding; - return $this->to_encoding; } /** @@ -3555,6 +3863,45 @@ class IO return $this->field_types; } + /** + * Get the field name to type connection list + * + * @return array + */ + public function dbGetFieldNameTypes(): array + { + return $this->field_name_types; + } + + /** + * Get the field name for a position + * + * @param int $pos Position number in query + * @return false|string Field name or false for not found + */ + public function dbGetFieldName(int $pos): false|string + { + return $this->field_names[$pos] ?? false; + } + + /** + * Return a field type for a field name or pos, + * will return false if field is not found in list + * + * @param string|int $name_pos Field name or pos to get the type for + * @return false|string Either the field type or + * false for not found in list + */ + public function dbGetFieldType(int|string $name_pos): false|string + { + if (is_numeric($name_pos)) { + $field_type = $this->field_types[$name_pos] ?? false; + } else { + $field_type = $this->field_name_types[$name_pos] ?? false; + } + return $field_type; + } + /** * Returns the value for given key in statement * Will write error if statemen id does not exist diff --git a/www/vendor/egrajp/corelibs-composer-all/src/DB/Options/Convert.php b/www/vendor/egrajp/corelibs-composer-all/src/DB/Options/Convert.php new file mode 100644 index 00000000..5e4cefd4 --- /dev/null +++ b/www/vendor/egrajp/corelibs-composer-all/src/DB/Options/Convert.php @@ -0,0 +1,63 @@ + self::off, + 'On', 'on', 'ON', 'convert_on', 'CONVERT_ON' => self::on, + 'Json', 'json', 'JSON', 'convert_json', 'CONVERT_JSON' => self::json, + 'Numeric', 'numeric', 'NUMERIC', 'convert_numeric', 'CONVERT_NUMERIC' => self::numeric, + 'Bytea', 'bytea', 'BYTEA', 'convert_bytea', 'CONVERT_BYTEA' => self::bytea, + default => self::off, + }; + } + + /** + * Get internal name from int value + * + * @param int $value + * @return self + */ + public static function fromValue(int $value): self + { + return self::from($value); + } +} + +// __END__ diff --git a/www/vendor/egrajp/corelibs-composer-all/src/Logging/Logging.php b/www/vendor/egrajp/corelibs-composer-all/src/Logging/Logging.php index ad9feed8..6c748452 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/Logging/Logging.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/Logging/Logging.php @@ -567,7 +567,7 @@ class Logging $context_str = ''; if ($context != []) { // TODO this here has to be changed to something better - $context_str = ' ' . print_r($context, true); + $context_str = ' :' . print_r($context, true); } // build log string return '[' . $timestamp . '] ' @@ -910,6 +910,42 @@ class Logging // MAIN CALLS // ********************************************************************* + /** + * Commong log interface + * + * extended with group_id, prefix that are ONLY used for debug level + * + * @param Level $level + * @param string|\Stringable $message + * @param mixed[] $context + * @param string $group_id + * @param string $prefix + * @return bool + */ + public function log( + Level $level, + string|\Stringable $message, + array $context = [], + string $group_id = '', + string $prefix = '', + ): bool { + // if we are not debug, ignore group_id and prefix + if ($level != Level::Debug) { + $group_id = ''; + $prefix = ''; + } + return $this->writeErrorMsg( + $level, + $this->prepareLog( + $level, + $prefix . $message, + $context, + $group_id + ), + $group_id + ); + } + /** * DEBUG: 100 * @@ -917,18 +953,18 @@ class Logging * * @param string $group_id id for error message, groups messages together * @param string|Stringable $message the actual error message + * @param mixed[] $context * @param string $prefix Attach some block before $string. * Will not be stripped even * when strip is true * if strip is false, recommended to add that to $string - * @param mixed[] $context * @return bool True if logged, false if not logged */ public function debug( string $group_id, string|\Stringable $message, - string $prefix = '', - array $context = [] + array $context = [], + string $prefix = '' ): bool { return $this->writeErrorMsg( Level::Debug, diff --git a/www/vendor/egrajp/corelibs-composer-all/src/Output/Form/Generate.php b/www/vendor/egrajp/corelibs-composer-all/src/Output/Form/Generate.php index 52884871..ba23f1ea 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/Output/Form/Generate.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/Output/Form/Generate.php @@ -308,7 +308,8 @@ class Generate extends \CoreLibs\DB\Extended\ArrayIO /** * construct form generator * - * @param array $db_config db config array, mandatory + * phpcs:ignore + * @param array{db_name:string,db_user:string,db_pass:string,db_host:string,db_port:int,db_schema:string,db_encoding:string,db_type:string,db_ssl:string,db_convert_type?:string[]} $db_config db config array, mandatory * @param \CoreLibs\Logging\Logging $log Logging class * @param \CoreLibs\Language\L10n $l10n l10n language class * @param array $login_acl Login ACL array, diff --git a/www/vendor/egrajp/corelibs-composer-all/src/Output/Image.php b/www/vendor/egrajp/corelibs-composer-all/src/Output/Image.php index 664f3a96..f530ec01 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/Output/Image.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/Output/Image.php @@ -36,6 +36,7 @@ class Image ): string|false { // get image type flags $image_types = [ + 0 => 'UNKOWN-IMAGE', 1 => 'gif', 2 => 'jpg', 3 => 'png' @@ -69,7 +70,7 @@ class Image } // does this picture exist and is it a picture if (file_exists($filename) && is_file($filename)) { - [$width, $height, $type] = getimagesize($filename) ?: []; + [$width, $height, $type] = getimagesize($filename) ?: [0, 0, 0]; $convert_prefix = ''; $create_file = false; $delete_filename = ''; @@ -98,7 +99,7 @@ class Image if (!is_file($filename)) { $filename .= '-0'; } - [$width, $height, $type] = getimagesize($filename) ?: []; + [$width, $height, $type] = getimagesize($filename) ?: [0, 0, 0]; } // if no size given, set size to original if (!$size_x || $size_x < 1) { @@ -117,7 +118,7 @@ class Image $status = exec($convert_string, $output, $return); // get the size of the converted data, if converted if (is_file($thumbnail)) { - [$width, $height, $type] = getimagesize($thumbnail) ?: []; + [$width, $height, $type] = getimagesize($thumbnail) ?: [0, 0, 0]; } } if ($height > $size_y) { @@ -217,7 +218,7 @@ class Image return $thumbnail; } // $this->debug('IMAGE PREPARE', "FILENAME OK, THUMB WIDTH/HEIGHT OK"); - [$inc_width, $inc_height, $img_type] = getimagesize($filename) ?: []; + [$inc_width, $inc_height, $img_type] = getimagesize($filename) ?: [0, 0, null]; $thumbnail_write_path = null; $thumbnail_web_path = null; // path set first @@ -447,7 +448,7 @@ class Image if (!function_exists('exif_read_data') || !is_writeable($filename)) { return; } - [$inc_width, $inc_height, $img_type] = getimagesize($filename) ?: []; + [$inc_width, $inc_height, $img_type] = getimagesize($filename) ?: [0, 0, null]; // add @ to avoid "file not supported error" $exif = @exif_read_data($filename); $orientation = null; diff --git a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/DB/CoreLibsDBIOTest.php b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/DB/CoreLibsDBIOTest.php index bd145ffb..bb24f270 100644 --- a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/DB/CoreLibsDBIOTest.php +++ b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/DB/CoreLibsDBIOTest.php @@ -38,6 +38,7 @@ namespace tests; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; use CoreLibs\Logging\Logger\Level; +use CoreLibs\DB\Options\Convert; /** * Test class for DB\IO + DB\SQL\PgSQL @@ -4557,6 +4558,176 @@ final class CoreLibsDBIOTest extends TestCase $db->dbClose(); } + // testing auto convert + + /** + * Undocumented function + * + * @covers ::dbSetConvertFlag + * @testdox Check convert type works + * + * @return void + */ + public function testConvertType(): void + { + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + $bytea_data = $db->dbEscapeBytea( + file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'CoreLibsDBIOTest.php') ?: '' + ); + $query_insert = <<dbExecParams( + $query_insert, + [ + 'CONVERT_TYPE_TEST', + 1, 1.5, 'varchar', 'varchar literla', + json_encode(['json', 'a', 1, true, 'sub' => ['b', 'c']]), + json_encode(['jsonb', 'a', 1, true, 'sub' => ['b', 'c']]), + $bytea_data, date('Y-m-d H:i:s'), date('Y-m-d'), date('H:m:s'), + '{1,2,3}', '{"a","b","c"}' + ] + ); + $type_layout = [ + 'uid' => 'string', + 'row_int' => 'int', + 'row_numeric' => 'float', + 'row_varchar' => 'string', + 'row_varchar_literal' => 'string', + 'row_json' => 'json', + 'row_jsonb' => 'json', + 'row_bytea' => 'bytea', + 'row_timestamp' => 'string', + 'row_date' => 'string', + 'row_interval' => 'string', + 'row_array_int' => 'string', + 'row_array_varchar' => 'string' + ]; + $query_select = <<dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); + // all hast to be string + foreach ($res as $key => $value) { + $this->assertIsString($value, 'Aseert string for column: ' . $key); + } + // convert base only + $db->dbSetConvertFlag(Convert::on); + $res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); + foreach ($res as $key => $value) { + if (is_numeric($key)) { + $name = $db->dbGetFieldName($key); + } else { + $name = $key; + } + switch ($type_layout[$name]) { + case 'int': + $this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); + break; + default: + $this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); + break; + } + } + $db->dbSetConvertFlag(Convert::numeric); + $res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); + foreach ($res as $key => $value) { + if (is_numeric($key)) { + $name = $db->dbGetFieldName($key); + } else { + $name = $key; + } + switch ($type_layout[$name]) { + case 'int': + $this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); + break; + case 'float': + $this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); + break; + default: + $this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); + break; + } + } + $db->dbSetConvertFlag(Convert::json); + $res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); + foreach ($res as $key => $value) { + if (is_numeric($key)) { + $name = $db->dbGetFieldName($key); + } else { + $name = $key; + } + switch ($type_layout[$name]) { + case 'int': + $this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); + break; + case 'float': + $this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); + break; + case 'json': + case 'jsonb': + $this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name); + break; + default: + $this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); + break; + } + } + $db->dbSetConvertFlag(Convert::bytea); + $res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); + foreach ($res as $key => $value) { + if (is_numeric($key)) { + $name = $db->dbGetFieldName($key); + } else { + $name = $key; + } + switch ($type_layout[$name]) { + case 'int': + $this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); + break; + case 'float': + $this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); + break; + case 'json': + case 'jsonb': + $this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name); + break; + case 'bytea': + // for hex types it must not start with \x + $this->assertStringStartsNotWith( + '\x', + $value, + 'Aseert bytes not starts with \x for column: ' . $key . '/' . $name + ); + break; + default: + $this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); + break; + } + } + } + // - internal read data (post exec) // dbGetNumRows, dbGetNumFields, dbGetFieldNames, // dbGetQuery, dbGetQueryHash, dbGetDbh @@ -4588,7 +4759,7 @@ final class CoreLibsDBIOTest extends TestCase . "('Foxtrott', 'Tango', 789, '1982-10-15') ", null, // - 2, + 3, 4, ['row_varchar', 'row_varchar_literal', 'row_int', 'row_date'], ['varchar', 'varchar', 'int4', 'date'], @@ -4705,9 +4876,16 @@ final class CoreLibsDBIOTest extends TestCase $db->dbExecParams($query, $params); } + $this->assertInstanceOf( + 'PgSql\Result', + $db->dbGetCursor(), + 'Failed assert dbGetCursor' + ); + $this->assertEquals( $compare_query ?? $query, - $db->dbGetQuery() + $db->dbGetQuery(), + 'Failed assert dbGetQuery' ); $this->assertEquals( // perhaps move that somewhere else? @@ -4720,7 +4898,8 @@ final class CoreLibsDBIOTest extends TestCase ($params === null ? $db->dbGetQueryHash($query) : $db->dbGetQueryHash($query, $params) - ) + ), + 'Failed assertdbGetQueryHash ' ); $this->assertEquals( $expected_rows, @@ -4742,6 +4921,35 @@ final class CoreLibsDBIOTest extends TestCase $db->dbGetFieldTypes(), 'Failed assert dbGetFieldTypes' ); + // check FieldNameTypes matches + $this->assertEquals( + array_combine( + $expected_col_names, + $expected_col_types + ), + $db->dbGetFieldNameTypes(), + 'Failed assert dbGetFieldNameTypes' + ); + // check pos matches name + // name matches type + // pos matches type + foreach ($expected_col_names as $pos => $name) { + $this->assertEquals( + $name, + $db->dbGetFieldName($pos), + 'Failed assert dbGetFieldName: ' . $pos . ' => ' . $name + ); + $this->assertEquals( + $expected_col_types[$pos], + $db->dbGetFieldType($name), + 'Failed assert dbGetFieldType: ' . $name . ' => ' . $expected_col_types[$pos] + ); + $this->assertEquals( + $expected_col_types[$pos], + $db->dbGetFieldType($pos), + 'Failed assert dbGetFieldType: ' . $pos . ' => ' . $expected_col_types[$pos] + ); + } $dbh = $db->dbGetDbh(); $this->assertIsObject( $dbh diff --git a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Logging/CoreLibsLoggingLoggingTest.php b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Logging/CoreLibsLoggingLoggingTest.php index f98e67d4..e60efc52 100644 --- a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Logging/CoreLibsLoggingLoggingTest.php +++ b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Logging/CoreLibsLoggingLoggingTest.php @@ -691,6 +691,11 @@ final class CoreLibsLoggingLoggingTest extends TestCase ); } + /** + * Undocumented function + * + * @return array + */ public function providerLoggingLevelWrite(): array { return [ @@ -796,6 +801,39 @@ final class CoreLibsLoggingLoggingTest extends TestCase ); } + // check log level that writer writes in correct level + // also that non debug ignores prefix/group + + /** + * Undocumented function + * + * @covers ::log + * @testdox log() general call test + * + * @return void + */ + public function testLoggingLog(): void + { + // init logger + $log = new \CoreLibs\Logging\Logging([ + 'log_file_id' => 'testLoggingLog', + 'log_folder' => self::LOG_FOLDER, + 'log_per_level' => true, + ]); + $log_ok = $log->log(Level::Debug, 'DEBUG', group_id: 'GROUP_ID', prefix: 'PREFIX:'); + $this->assertTrue($log_ok, 'assert ::log (debug) OK'); + $this->assertEquals( + $log->getLogFile(), + $log->getLogFileId() . '_DEBUG.log' + ); + $log_ok = $log->log(Level::Info, 'INFO', group_id: 'GROUP_ID', prefix: 'PREFIX:'); + $this->assertTrue($log_ok, 'assert ::log (info) OK'); + $this->assertEquals( + $log->getLogFile(), + $log->getLogFileId() . '_INFO.log' + ); + } + // must test flow: // init normal // log -> check file name