From 5ebe7dc06cf20e1636ac4529644766e2dcc1e59a Mon Sep 17 00:00:00 2001 From: Clemens Schwaighofer Date: Thu, 7 Mar 2024 15:02:25 +0900 Subject: [PATCH] Code updates --- www/admin/class_test.image.php | 8 +- www/composer.lock | 12 +- www/vendor/composer/installed.json | 14 +- www/vendor/composer/installed.php | 10 +- .../corelibs-composer-all/.phive/phars.xml | 4 +- .../egrajp/corelibs-composer-all/phpcs.xml | 18 + .../publish/last.published | 2 +- .../src/Admin/EditBase.php | 2 +- .../corelibs-composer-all/src/Check/File.php | 17 + .../src/Combined/ArrayHandler.php | 48 ++ .../src/Combined/DateTime.php | 256 +++++- .../src/Convert/Byte.php | 8 +- .../src/Convert/Strings.php | 16 + .../src/DB/Extended/ArrayIO.php | 2 +- .../corelibs-composer-all/src/DB/IO.php | 808 +++++++++++++----- .../src/DB/Support/ConvertPlaceholder.php | 220 +++++ .../src/Debug/Support.php | 42 +- .../src/Output/Form/Generate.php | 25 +- .../src/Output/Image.php | 6 +- .../src/Output/ProgressBar.php | 4 +- .../phpunit/Check/CoreLibsCheckFileTest.php | 62 +- .../CoreLibsCombinedArrayHandlerTest.php | 95 +- .../Combined/CoreLibsCombinedDateTimeTest.php | 715 +++++++++++----- .../Convert/CoreLibsConvertByteTest.php | 6 +- .../Convert/CoreLibsConvertStringsTest.php | 74 ++ .../test/phpunit/DB/CoreLibsDBIOTest.php | 334 +++++--- .../Debug/CoreLibsDebugSupportTest.php | 27 +- www/vendor/egrajp/smarty-extended/.gitignore | 1 + www/vendor/egrajp/smarty-extended/ReadMe.md | 46 + .../egrajp/smarty-extended/composer.json | 6 +- .../egrajp/smarty-extended/publish/.gitignore | 1 + .../smarty-extended/publish/last.published | 1 + .../publish/package-download/.gitignore | 1 + .../egrajp/smarty-extended/publish/publish.sh | 88 ++ .../smarty-extended/src/Smarty.class.php | 2 +- .../egrajp/smarty-extended/src/debug.tpl | 4 +- .../src/plugins/function.math.php | 2 +- .../src/plugins/modifier.escape.php | 4 +- .../src/plugins/modifier.implode.php | 15 + .../src/plugins/modifier.truncate.php | 2 +- .../modifiercompiler.count_characters.php | 4 +- .../plugins/modifiercompiler.count_words.php | 2 +- .../src/plugins/modifiercompiler.escape.php | 4 +- .../plugins/modifiercompiler.json_encode.php | 11 + .../src/plugins/modifiercompiler.lower.php | 4 +- .../plugins/modifiercompiler.strip_tags.php | 2 +- .../src/plugins/modifiercompiler.substr.php | 12 + .../src/plugins/modifiercompiler.upper.php | 4 +- .../plugins/outputfilter.trimwhitespace.php | 2 +- .../plugins/shared.escape_special_chars.php | 2 +- .../variablefilter.htmlspecialchars.php | 2 +- ...ernal_compile_private_print_expression.php | 2 +- .../src/sysplugins/smarty_internal_debug.php | 9 +- .../smarty_internal_errorhandler.php | 15 +- .../sysplugins/smarty_template_compiled.php | 2 +- 55 files changed, 2441 insertions(+), 644 deletions(-) create mode 100644 www/vendor/egrajp/corelibs-composer-all/phpcs.xml create mode 100644 www/vendor/egrajp/corelibs-composer-all/src/DB/Support/ConvertPlaceholder.php create mode 100644 www/vendor/egrajp/smarty-extended/ReadMe.md create mode 100644 www/vendor/egrajp/smarty-extended/publish/.gitignore create mode 100644 www/vendor/egrajp/smarty-extended/publish/last.published create mode 100644 www/vendor/egrajp/smarty-extended/publish/package-download/.gitignore create mode 100755 www/vendor/egrajp/smarty-extended/publish/publish.sh create mode 100644 www/vendor/egrajp/smarty-extended/src/plugins/modifier.implode.php create mode 100644 www/vendor/egrajp/smarty-extended/src/plugins/modifiercompiler.json_encode.php create mode 100644 www/vendor/egrajp/smarty-extended/src/plugins/modifiercompiler.substr.php diff --git a/www/admin/class_test.image.php b/www/admin/class_test.image.php index 0ae756ba..21e8ee71 100644 --- a/www/admin/class_test.image.php +++ b/www/admin/class_test.image.php @@ -41,9 +41,9 @@ print '

' . $PAGE_NAME . '

'; $thumb_width = 250; $thumb_height = 300; // class -$image = BASE . LAYOUT . CONTENT_PATH . IMAGES . 'no_picture_square.jpg'; +$image = BASE . CONTENT_PATH . LAYOUT . IMAGES . 'no_picture_square.jpg'; // folders -$cache_folder = BASE . LAYOUT . CONTENT_PATH . CACHE . IMAGES; +$cache_folder = BASE . CONTENT_PATH . LAYOUT . CACHE . IMAGES; $web_folder = LAYOUT . CACHE . IMAGES; // rotate image first try { @@ -58,7 +58,7 @@ echo "
CLASS->CREATETHUMBNAILSIMPLE: " . basename($image) . ": WIDTH: $thumb_width
"; // static -$image = BASE . LAYOUT . CONTENT_PATH . IMAGES . 'no_picture.jpg'; +$image = BASE . CONTENT_PATH . LAYOUT . IMAGES . 'no_picture.jpg'; // rotate image first try { $image_class::correctImageOrientation($image); @@ -94,7 +94,7 @@ $images = array( // return mime type ala mimetype $finfo = new finfo(FILEINFO_MIME_TYPE); foreach ($images as $image) { - $image = BASE . LAYOUT . CONTENT_PATH . IMAGES . $image; + $image = BASE . CONTENT_PATH . LAYOUT . IMAGES . $image; list ($height, $width, $img_type) = \CoreLibs\Convert\SetVarType::setArray(getimagesize($image)); echo "
IMAGE INFO: " . $height . "x" . $width . ", TYPE: " . \CoreLibs\Debug\Support::dumpVar($img_type) . " [" . $finfo->file($image) . "]
"; diff --git a/www/composer.lock b/www/composer.lock index 33f958f8..8758f4ab 100644 --- a/www/composer.lock +++ b/www/composer.lock @@ -8,11 +8,11 @@ "packages": [ { "name": "egrajp/corelibs-composer-all", - "version": "dev-master", + "version": "dev-development", "dist": { "type": "path", "url": "/storage/var/www/html/developers/clemens/core_data/composer-packages/CoreLibs-Composer-All", - "reference": "4ab382990ecb9db90386f8ec1a4d87ac8f7c5dd7" + "reference": "d3d4cf512f62f139c61629d5aa21cf22ad726241" }, "require": { "php": ">=8.2", @@ -45,11 +45,11 @@ }, { "name": "egrajp/smarty-extended", - "version": "4.3.0", + "version": "4.4.1", "dist": { "type": "zip", - "url": "https://git.egplusww.jp/api/packages/Composer/composer/files/egrajp%2Fsmarty-extended/4.3.0/egrajp-smarty-extended.4.3.0.zip", - "shasum": "d41bda35c0d52da35cf911ab0b018655a09f072b" + "url": "https://git.egplusww.jp/api/packages/Composer/composer/files/egrajp%2Fsmarty-extended/4.4.1/egrajp-smarty-extended.4.4.1.zip", + "shasum": "edd7a0960e49bfcc709e0a525729aaaf9ed0db75" }, "type": "library", "autoload": { @@ -71,7 +71,7 @@ "keywords": [ "templating" ], - "time": "2023-02-17T14:14:10+09:00" + "time": "2024-03-06T18:43:44+09:00" }, { "name": "gullevek/dotenv", diff --git a/www/vendor/composer/installed.json b/www/vendor/composer/installed.json index c12e083e..0ed99cde 100644 --- a/www/vendor/composer/installed.json +++ b/www/vendor/composer/installed.json @@ -2,12 +2,12 @@ "packages": [ { "name": "egrajp/corelibs-composer-all", - "version": "dev-master", - "version_normalized": "dev-master", + "version": "dev-development", + "version_normalized": "dev-development", "dist": { "type": "path", "url": "/storage/var/www/html/developers/clemens/core_data/composer-packages/CoreLibs-Composer-All", - "reference": "4ab382990ecb9db90386f8ec1a4d87ac8f7c5dd7" + "reference": "d3d4cf512f62f139c61629d5aa21cf22ad726241" }, "require": { "php": ">=8.2", @@ -42,12 +42,14 @@ }, { "name": "egrajp/smarty-extended", - "version": "4.3.0", - "version_normalized": "4.3.0.0", + "version": "4.4.1", + "version_normalized": "4.4.1.0", "dist": { "type": "zip", - "url": "https://git.egplusww.jp/Composer/Smarty-Extended/archive/v4.3.0.zip" + "url": "https://git.egplusww.jp/api/packages/Composer/composer/files/egrajp%2Fsmarty-extended/4.4.1/egrajp-smarty-extended.4.4.1.zip", + "shasum": "edd7a0960e49bfcc709e0a525729aaaf9ed0db75" }, + "time": "2024-03-06T18:43:44+09:00", "type": "library", "installation-source": "dist", "autoload": { diff --git a/www/vendor/composer/installed.php b/www/vendor/composer/installed.php index a0209274..6dc428d7 100644 --- a/www/vendor/composer/installed.php +++ b/www/vendor/composer/installed.php @@ -11,9 +11,9 @@ ), 'versions' => array( 'egrajp/corelibs-composer-all' => array( - 'pretty_version' => 'dev-master', - 'version' => 'dev-master', - 'reference' => '4ab382990ecb9db90386f8ec1a4d87ac8f7c5dd7', + 'pretty_version' => 'dev-development', + 'version' => 'dev-development', + 'reference' => 'd3d4cf512f62f139c61629d5aa21cf22ad726241', 'type' => 'library', 'install_path' => __DIR__ . '/../egrajp/corelibs-composer-all', 'aliases' => array(), @@ -29,8 +29,8 @@ 'dev_requirement' => false, ), 'egrajp/smarty-extended' => array( - 'pretty_version' => '4.3.0', - 'version' => '4.3.0.0', + 'pretty_version' => '4.4.1', + 'version' => '4.4.1.0', 'reference' => null, 'type' => 'library', 'install_path' => __DIR__ . '/../egrajp/smarty-extended', diff --git a/www/vendor/egrajp/corelibs-composer-all/.phive/phars.xml b/www/vendor/egrajp/corelibs-composer-all/.phive/phars.xml index f6d3d32b..72b47dc1 100644 --- a/www/vendor/egrajp/corelibs-composer-all/.phive/phars.xml +++ b/www/vendor/egrajp/corelibs-composer-all/.phive/phars.xml @@ -3,7 +3,7 @@ - - + + diff --git a/www/vendor/egrajp/corelibs-composer-all/phpcs.xml b/www/vendor/egrajp/corelibs-composer-all/phpcs.xml new file mode 100644 index 00000000..cfc7a3dc --- /dev/null +++ b/www/vendor/egrajp/corelibs-composer-all/phpcs.xml @@ -0,0 +1,18 @@ + + + PSR12 override rules (strict, standard). Switch spaces indent to tab. + + + + + + + + + + + + + + + diff --git a/www/vendor/egrajp/corelibs-composer-all/publish/last.published b/www/vendor/egrajp/corelibs-composer-all/publish/last.published index 5d24a8f4..ee1b03fb 100644 --- a/www/vendor/egrajp/corelibs-composer-all/publish/last.published +++ b/www/vendor/egrajp/corelibs-composer-all/publish/last.published @@ -1 +1 @@ -9.8.2 +9.11.1 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 fa8033ef..f740b53a 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/Admin/EditBase.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/Admin/EditBase.php @@ -44,7 +44,7 @@ class EditBase * construct form generator * * 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 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_convert_placeholder?:bool,db_convert_placeholder_target?:string,db_debug_replace_placeholder?:bool} $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/Check/File.php b/www/vendor/egrajp/corelibs-composer-all/src/Check/File.php index 36b4bfc8..c8bdc531 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/Check/File.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/Check/File.php @@ -51,6 +51,23 @@ class File // return lines in file return $lines; } + + /** + * get the mime type of a file via finfo + * if file not found, throws exception + * else returns '' for any other finfo read problem + * + * @param string $read_file File to read, relative or absolute path + * @return string + */ + public static function getMimeType(string $read_file): string + { + $finfo = new \finfo(FILEINFO_MIME_TYPE); + if (!is_file($read_file)) { + throw new \UnexpectedValueException('[getMimeType] File not found: ' . $read_file); + } + return $finfo->file($read_file) ?: ''; + } } // __END__ diff --git a/www/vendor/egrajp/corelibs-composer-all/src/Combined/ArrayHandler.php b/www/vendor/egrajp/corelibs-composer-all/src/Combined/ArrayHandler.php index d664310c..bbe3943f 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/Combined/ArrayHandler.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/Combined/ArrayHandler.php @@ -236,6 +236,54 @@ class ArrayHandler return $hit_list; } + /** + * main wrapper function for next/prev key + * + * @param array $array array to search in + * @param int|string $key key for next/prev + * @param bool $next [=true] if to search next or prev + * @return int|string|null Next/prev key or null for end/first + */ + private static function arrayGetKey(array $array, int|string $key, bool $next = true): int|string|null + { + $keys = array_keys($array); + if (($position = array_search($key, $keys, true)) === false) { + return null; + } + $next_position = $next ? $position + 1 : $position - 1; + + if (!isset($keys[$next_position])) { + return null; + } + return $keys[$next_position]; + } + + /** + * Get previous array key from an array + * null on not found + * + * @param array $array + * @param int|string $key + * @return int|string|null Next key, or null for not found + */ + public static function arrayGetPrevKey(array $array, int|string $key): int|string|null + { + return self::arrayGetKey($array, $key, false); + } + + /** + * Get next array key from an array + * null on not found + * + * @param array $array + * @param int|string $key + * @return int|string|null Next key, or null for not found + */ + public static function arrayGetNextKey(array $array, int|string $key): int|string|null + { + return self::arrayGetKey($array, $key, true); + } + /** * correctly recursive merges as an array as array_merge_recursive * just glues things together diff --git a/www/vendor/egrajp/corelibs-composer-all/src/Combined/DateTime.php b/www/vendor/egrajp/corelibs-composer-all/src/Combined/DateTime.php index a807a39d..d46619c5 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/Combined/DateTime.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/Combined/DateTime.php @@ -108,7 +108,12 @@ class DateTime if (preg_match("/(h|m|s|ms)/", (string)$timestamp)) { return (string)$timestamp; } - list($timestamp, $ms) = array_pad(explode('.', (string)round((float)$timestamp, 4)), 2, null); + // split to 6 (nano seconds) + list($timestamp, $ms) = array_pad(explode('.', (string)round((float)$timestamp, 6)), 2, null); + // if micro seconds is on and we have none, set to 0 + if ($show_micro && $ms === null) { + $ms = 0; + } // if negative remember $negative = false; if ((int)$timestamp < 0) { @@ -120,6 +125,10 @@ class DateTime $time_string = ''; // if timestamp is zero, return zero string if ($timestamp == 0) { + // if no seconds and we have no microseconds either, show no micro seconds + if ($ms == 0) { + $ms = null; + } $time_string = '0s'; } else { for ($i = 0, $iMax = count($timegroups); $i < $iMax; $i++) { @@ -133,11 +142,8 @@ class DateTime } // only add ms if we have an ms value if ($ms !== null) { - // if we have ms and it has leading zeros, remove them, but only if it is nut just 0 - $ms = preg_replace("/^0+(\d+)$/", '${1}', $ms); - if (!is_string($ms) || empty($ms)) { - $ms = '0'; - } + // prefix the milliseoncds with 0. and round it max 3 digits and then convert to int + $ms = round((float)('0.' . $ms), 3) * 1000; // add ms if there if ($show_micro) { $time_string .= ' ' . $ms . 'ms'; @@ -151,6 +157,240 @@ class DateTime return (string)$time_string; } + /** + * update timeStringFormat with year and month support + * + * The following flags have to be set to be timeStringFormat compatible. + * Not that on seconds overflow this method will throw an exception, timeStringFormat returned -1s + * show_only_days: true, + * skip_zero: false, + * skip_last_zero: false, + * truncate_nanoseconds: true, + * truncate_zero_seconds_if_microseconds: false + * + * @param int|float $seconds Seconds to convert, maxium 6 decimals, + * else \UnexpectedValueException will be thrown + * if days too large or years too large \LengthException is thrown + * @param string $truncate_after [=''] Truncate after which time name, will not round, hard end + * values are parts names or interval short names (y, d, f, ...) + * if illegal value \UnexpectedValueException is thrown + * @param bool $natural_seperator [=false] use ',' and 'and', if off use space + * @param bool $name_space_seperator [=false] add a space between the number and the time name + * @param bool $show_microseconds [=true] show microseconds + * @param bool $short_time_name [=true] use the short time names (eg s instead of seconds) + * @param bool $skip_last_zero [=true] skip all trailing zero values, eg 5m 0s => 5m + * @param bool $skip_zero [=true] do not show zero values anywhere, eg 1h 0m 20s => 1h 20s + * @param bool $show_only_days [=false] do not show years or months, show only days + * if truncate after is set to year or month + * throws \UnexpectedValueException + * @param bool $auto_fix_microseconds [=false] if the micro seconds decimals are more than 6, round them + * on defaul throw \UnexpectedValueException + * @param bool $truncate_nanoseconds [=false] if microseconds decimals >3 then normal we show 123.4ms + * cut the .4 is set to true + * @param bool $truncate_zero_seconds_if_microseconds [=true] if we have 0.123 seconds then if true no seconds + * will be shown + * @return string + * @throws \UnexpectedValueException if seconds has more than 6 decimals + * if truncate has an illegal value + * if truncate is set to year or month and show_only_days is turned on + * @throws \LengthException if seconds is too large and show_days_only is selected and days is negetive + * or if years is negativ + */ + public static function intervalStringFormat( + int|float $seconds, + string $truncate_after = '', + bool $natural_seperator = false, + bool $name_space_seperator = false, + bool $show_microseconds = true, + bool $short_time_name = true, + bool $skip_last_zero = true, + bool $skip_zero = true, + bool $show_only_days = false, + bool $auto_fix_microseconds = false, + bool $truncate_nanoseconds = false, + bool $truncate_zero_seconds_if_microseconds = true, + ): string { + // auto fix long seconds, else \UnexpectedValueException will be thrown on error + // check if we have float and -> round to 6 + if ($auto_fix_microseconds === true && is_float($seconds)) { + $seconds = round($seconds, 6); + } + // flag negative + set abs + $negative = $seconds < 0 ? '-' : ''; + $seconds = abs($seconds); + // create base time + $date_now = new \DateTime("@0"); + try { + $date_seconds = new \DateTime("@$seconds"); + } catch (\Exception $e) { + throw new \UnexpectedValueException( + 'Seconds value is invalid, too large or more than six decimals: ' . $seconds, + 1, + $e + ); + } + $interval = date_diff($date_now, $date_seconds); + // if show_only_days and negative but input postive alert that this has to be done in y/m/d ... + if ($interval->y < 0) { + throw new \LengthException('Input seconds value is too large for years output: ' . $seconds, 2); + } elseif ($interval->days < 0 && $show_only_days === true) { + throw new \LengthException('Input seconds value is too large for days output: ' . $seconds, 3); + } + $parts = [ + 'years' => 'y', + 'months' => 'm', + 'days' => 'd', + 'hours' => 'h', + 'minutes' => 'i', + 'seconds' => 's', + 'microseconds' => 'f', + ]; + $short_name = [ + 'years' => 'y', 'months' => 'm', 'days' => 'd', + 'hours' => 'h', 'minutes' => 'm', 'seconds' => 's', + 'microseconds' => 'ms' + ]; + // $skip = false; + if (!empty($truncate_after)) { + // if truncate after not in key or value in parts + if (!in_array($truncate_after, array_keys($parts)) && !in_array($truncate_after, array_values($parts))) { + throw new \UnexpectedValueException( + 'truncate_after has an invalid value: ' . $truncate_after, + 4 + ); + } + // if truncate after is y or m and we have show_only_days, throw exception + if ($show_only_days === true && in_array($truncate_after, ['y', 'years', 'm', 'months'])) { + throw new \UnexpectedValueException( + 'If show_only_days is turned on, the truncate_after cannot be years or months: ' + . $truncate_after, + 5 + ); + } + // $skip = true; + } + $formatted = []; + $zero_formatted = []; + $value_set = false; + $add_zero_seconds = false; + foreach ($parts as $time_name => $part) { + if ( + // skip for micro seconds + ($show_microseconds === false && $part == 'f') || + // skip for if days only and we have year or month + ($show_only_days === true && in_array($part, ['y', 'm'])) + ) { + continue; + } + $add_value = 0; + if ($show_only_days === true && $part == 'd') { + $value = $interval->days; + } else { + $value = $interval->$part; + } + // print "-> V: $value | $part, $time_name" + // . " | Set: " . ($value_set ? 'Y' : 'N') . ", SkipZ: " . ($skip_zero ? 'Y' : 'N') + // . " | SkipLZ: " . ($skip_last_zero ? 'Y' : 'N') + // . " | " . ($value != 0 ? 'Not zero' : 'ZERO') . "
"; + if ($value != 0) { + if ($part == 'f') { + if ($truncate_nanoseconds === true) { + $value = round($value, 3); + } + $value *= 1000; + // anything above that is nano seconds? + } + if ($value) { + $value_set = true; + } + $add_value = 1; + } elseif ( + $value == 0 && + $value_set === true && ( + $skip_last_zero === false || + $skip_zero === false + ) + ) { + $add_value = 2; + } + // echo "ADD VALUE: $add_value
"; + if ($add_value) { + // build format + $format = "$value"; + if ($name_space_seperator) { + $format .= " "; + } + if ($short_time_name) { + $format .= $short_name[$time_name]; + } elseif ($value == 1) { + $format .= substr($time_name, 0, -1); + } else { + $format .= $time_name; + } + if ($add_value == 1) { + if (count($zero_formatted) && $skip_zero === false) { + $formatted = array_merge($formatted, $zero_formatted); + } + $zero_formatted = []; + $formatted[] = $format; + } elseif ($add_value == 2) { + $zero_formatted[] = $format; + } + } + // if seconds is zero + if ( + $part == 's' && $value == 0 && + $show_microseconds === true && + $truncate_zero_seconds_if_microseconds === false + ) { + $add_zero_seconds = true; + } + // stop after a truncate is matching + if ($part == $truncate_after || $truncate_after == $time_name) { + break; + } + } + // add all zero entries if we have skip last off + if (count($zero_formatted) && $skip_last_zero === false) { + $formatted = array_merge($formatted, $zero_formatted); + } + // print "=> F: " . print_r($formatted, true) + // . " | Z: " . print_r($zero_list, true) + // . " | ZL: " . print_r($zero_last_list, true) + // . "
"; + if (count($formatted) == 0) { + // if we have truncate on, then we assume nothing was found + if (!empty($truncate_after)) { + if (in_array($truncate_after, array_values($parts))) { + $truncate_after = array_flip($parts)[$truncate_after]; + } + $time_name = $truncate_after; + } else { + $time_name = 'seconds'; + } + return '0' . ($name_space_seperator ? ' ' : '') + . ($short_time_name ? $short_name[$time_name] : $time_name); + } elseif (count($formatted) == 1) { + return $negative . + ($add_zero_seconds ? + '0' + . ($name_space_seperator ? ' ' : '') + . ($short_time_name ? $short_name['seconds'] : 'seconds') + . ' ' + : '' + ) + . $formatted[0]; + } elseif ($natural_seperator === false) { + return $negative . implode(' ', $formatted); + } else { + $str = implode(', ', array_slice($formatted, 0, -1)); + if (!empty($formatted[count($formatted) - 1])) { + $str .= ' and ' . (string)array_pop($formatted); + } + return $negative . $str; + } + } + /** * does a reverse of the timeStringFormat and converts the string from * xd xh xm xs xms to a timestamp.microtime format @@ -435,9 +675,9 @@ class DateTime foreach ($period as $dt) { $curr = $dt->format('D'); if ($curr == 'Sat' || $curr == 'Sun') { - $days[2] ++; + $days[2]++; } else { - $days[1] ++; + $days[1]++; } } if ($return_named === true) { diff --git a/www/vendor/egrajp/corelibs-composer-all/src/Convert/Byte.php b/www/vendor/egrajp/corelibs-composer-all/src/Convert/Byte.php index ebec727a..b0d05ece 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/Convert/Byte.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/Convert/Byte.php @@ -37,7 +37,7 @@ class Byte * BYTE_FORMAT_ADJUST: sprintf adjusted two 2 decimals * BYTE_FORMAT_SI: use 1000 instead of 1024 * @return string converted byte number (float) with suffix - * @throws \Exception 1: no valid flag set + * @throws \InvalidArgumentException 1: no valid flag set */ public static function humanReadableByteFormat(string|int|float $bytes, int $flags = 0): string { @@ -63,7 +63,7 @@ class Byte $si = false; } if ($flags > 7) { - throw new \Exception("Invalid flags parameter: $flags", 1); + throw new \InvalidArgumentException("Invalid flags parameter: $flags", 1); } // si or normal @@ -119,7 +119,7 @@ class Byte * @param int $flags bitwise flag with use space turned on * BYTE_FORMAT_SI: use 1000 instead of 1024 * @return string|int|float converted value or original value - * @throws \Exception 1: no valid flag set + * @throws \InvalidArgumentException 1: no valid flag set */ public static function stringByteFormat(string|int|float $number, int $flags = 0): string|int|float { @@ -130,7 +130,7 @@ class Byte $si = false; } if ($flags != 0 && $flags != 4) { - throw new \Exception("Invalid flags parameter: $flags", 1); + throw new \InvalidArgumentException("Invalid flags parameter: $flags", 1); } // matches in regex $matches = []; diff --git a/www/vendor/egrajp/corelibs-composer-all/src/Convert/Strings.php b/www/vendor/egrajp/corelibs-composer-all/src/Convert/Strings.php index bcbaf058..2935e26d 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/Convert/Strings.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/Convert/Strings.php @@ -118,6 +118,22 @@ class Strings return $value; } } + + /** + * Strip any duplicated slahes from a path + * eg: //foo///bar/foo.inc -> /foo/bar/foo.inc + * + * @param string $path Path to strip slashes from + * @return string Clean path, on error returns original path + */ + public static function stripMultiplePathSlashes(string $path): string + { + return preg_replace( + '#/+#', + '/', + $path + ) ?? $path; + } } // __END__ 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 b0e6f659..2f19e9df 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 @@ -55,7 +55,7 @@ class ArrayIO extends \CoreLibs\DB\IO * primary key name automatically (from array) * * 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{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_convert_placeholder?:bool,db_convert_placeholder_target?:string,db_debug_replace_placeholder?:bool} $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 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 d5c90c97..efe3901c 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/DB/IO.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/DB/IO.php @@ -261,6 +261,7 @@ use CoreLibs\Debug\Support; use CoreLibs\Create\Uids; use CoreLibs\Convert\Json; use CoreLibs\DB\Options\Convert; +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 @@ -283,6 +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) */ + public const DB_CONVERT_PLACEHOLDER_TARGET = ['pg']; // REGEX_SELECT // REGEX_UPDATE // REGEX INSERT @@ -299,6 +302,9 @@ class IO private string $query = ''; /** @var array current params for query */ private array $params = []; + // if we do have a convert call, store the convert data in here, else it will be empty + /** @var array{}|array{original:array{query:string,params:array},type:''|'named'|'numbered'|'question_mark',found:int,matches:array,params_lookup:array,query:string,params:array} */ + private array $placeholder_converted = []; // only inside // basic vars // the dbh handler, if disconnected by command is null, bool:false on error, @@ -326,10 +332,16 @@ class IO private string $db_ssl; /** @var array flag for converting types from settings */ private array $db_convert_type = []; + /** @var bool convert placeholders from pdo to Pg or the other way around */ + private bool $db_convert_placeholder = false; + /** @var string convert placeholders target, default is 'pg', other allowed is 'pdo' */ + private string $db_convert_placeholder_target = 'pg'; + /** @var bool Replace the placeholders in a query for debug output, defaults to false */ + private bool $db_debug_replace_placeholder = false; // convert type settings // 0: OFF (CONVERT_OFF) // >0: ON - // 1: convert intN/bool (CONVERT_ON) + // 1: convert int/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) @@ -367,6 +379,7 @@ class IO private string $warning_id; /** @var string */ private string $error_history_id; + // timestamp:string,level:string,id:string,error:string,source:string,pg_error:string,message:string,context:array /** @var array Stores warning and errors combinded with detail info */ private array $error_history_long = []; /** @var bool error thrown on class init if we cannot connect to db */ @@ -406,7 +419,7 @@ class IO * 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 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_convert_placeholder?:bool,db_convert_placeholder_target?:string,db_debug_replace_placeholder?:bool} $db_config DB configuration array * @param \CoreLibs\Logging\Logging $log Logging class * @throws \RuntimeException If no DB connection can be established on launch */ @@ -435,16 +448,15 @@ class IO '11' => 'No Querystring given', '12' => 'No Cursor given, no correct query perhaps?', '13' => 'Query could not be executed without errors', - '14' => 'Can\'t connect to DB server', - '15' => 'Can\'t select DB or no db name given', - '16' => 'No DB Handler found / connect or reconnect failed', + '14' => 'No DB Handler found / connect or reconnect failed', + '15' => 'Cannot select DB or no db name given', + // '16' => 'No DB Handler found / connect or reconnect failed', // 16 merged into 14 '17' => 'All dbReturn* methods work only with SELECT statements, ' . 'please use dbExec for everything else', '18' => 'Query not found in cache. Nothing has been reset', - '19' => 'Wrong PK name given or no PK name given at all, can\'t ' - . 'get Insert ID', - '20' => 'Found given Prepare Statement Name in array, ' - . 'Query not prepared, will use existing one', + '19' => 'Wrong PK name given or no PK name given at all, can\'t get Insert ID', + '20' => 'Query has already been prepared', + '26' => 'Same prepare statement name has been used for a different query', '21' => 'Query Prepare failed', '22' => 'Query Execute failed', '23' => 'Query Execute failed, data array does not match placeholders', @@ -472,7 +484,13 @@ class IO '101' => 'Statement name empty for get prepare cursor', '102' => 'Key empty for get prepare cursir', '103' => 'No prepared cursor with this name', - '104' => 'No Key with this name in the prepared cursor array' + '104' => 'No Key with this name in the prepared cursor array', + // abort on Placeholder convert + '200' => 'Cannot have named, question mark or numbered placeholders in the same query', + '210' => 'Cannot lookup param named in param list', + '211' => 'Cannot lookup param named in param lookup list', + '220' => 'Cannot lookup param number in param list', + '221' => 'Cannot lookup param number in param lookup list', ]; // load the core DB functions wrapper class @@ -485,7 +503,6 @@ class IO // connect to DB if (!$this->__connectToDB()) { - $this->__dbError(16); $this->db_connection_closed = true; throw new \RuntimeException('INIT: No DB Handler found / connect or reconnect failed', 16); } @@ -507,7 +524,7 @@ class IO * 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 + * @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_convert_placeholder?:bool,db_convert_placeholder_target?:string,db_debug_replace_placeholder?:bool} $db_config * @return bool */ private function __setConfigOptions(array $db_config): bool @@ -549,6 +566,26 @@ class IO $this->db_convert_type[] = $db_convert_type; $this->__setConvertType($db_convert_type); } + // set placeholder convert flag and target + if ( + isset($db_config['db_convert_placeholder']) && + is_bool($db_config['db_convert_placeholder']) + ) { + $this->db_convert_placeholder = $db_config['db_convert_placeholder']; + } + if ( + isset($db_config['db_convert_placeholder_target']) && + in_array($db_config['db_convert_placeholder_target'], self::DB_CONVERT_PLACEHOLDER_TARGET) + ) { + $this->db_convert_placeholder_target = $db_config['db_convert_placeholder_target']; + } + + if ( + isset($db_config['db_debug_replace_placeholder']) && + is_bool($db_config['db_debug_replace_placeholder']) + ) { + $this->db_debug_replace_placeholder = $db_config['db_debug_replace_placeholder']; + } // return status true: ok, false: options error return true; @@ -601,7 +638,7 @@ class IO // if non set or none matching abort default: // abort error - $this->__dbError(10); + $this->__dbError(10, context: ['db_type' => $this->db_type]); $this->db_connection_closed = true; break; } @@ -619,7 +656,14 @@ class IO { // no DB name set, abort if (empty($this->db_name)) { - $this->__dbError(15); + $this->__dbError(15, context: [ + 'host' => $this->db_host, + 'user' => $this->db_user, + 'password' => 'sha256:' . hash('sha256', $this->db_pwd), + 'database' => $this->db_name, + 'port' => $this->db_port, + 'ssl' => $this->db_ssl + ]); return false; } // generate connect string @@ -633,7 +677,14 @@ class IO ); // if no dbh here, we couldn't connect to the DB itself if (!$this->dbh) { - $this->__dbError(14); + $this->__dbError(14, context: [ + 'host' => $this->db_host, + 'user' => $this->db_user, + 'password' => 'sha256:' . hash('sha256', $this->db_pwd), + 'database' => $this->db_name, + 'port' => $this->db_port, + 'ssl' => $this->db_ssl + ]); return false; } // 15 error (cant select to DB is not valid in postgres, as connect is different) @@ -668,59 +719,6 @@ 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 - */ - private function __checkQueryForSelect(string $query): bool - { - // change to string starts with? - if (preg_match("/^\s*(?:SELECT|SHOW|WITH)\s/i", $query)) { - return true; - } - return false; - } - - /** - * check for DELETE, INSERT, UPDATE - * 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 - */ - private function __checkQueryForInsert(string $query, bool $pure = false): bool - { - if ($pure && preg_match("/^\s*INSERT\s+?INTO\s/i", $query)) { - return true; - } - if (!$pure && preg_match("/^\s*(?:INSERT\s+?INTO|DELETE\s+?FROM|UPDATE)\s/i", $query)) { - return true; - } - return false; - } - - /** - * 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 - */ - private function __checkQueryForUpdate(string $query): bool - { - if (preg_match("/^\s*UPDATE\s?(.+)/i", $query)) { - return true; - } - return false; - } - /** * internal funktion that creates the array * NOTE: @@ -881,12 +879,15 @@ class IO * @param \PgSql\Result|false $cursor current cursor for pg_result_error, * pg_last_error too, but pg_result_error * is more accurate (PgSql\Result) + * @param bool $force_log [=false] if we want to log this error to log, on default logging is handled in the + * dbError/dbWarning calls * @return array Pos 0: if we could get the method where it was called * if not found [Uknown Method] * Pos 1: if we have the pg_error_string from last error * if nothing then empty string + * Pos 2: context array for detailed logging */ - private function __dbErrorPreprocessor(\PgSql\Result|false $cursor = false): array + private function __dbErrorPreprocessor(\PgSql\Result|false $cursor = false, bool $force_log = false): array { $db_prefix = ''; $db_error_string = ''; @@ -923,12 +924,20 @@ class IO } elseif (!empty($db_error_string)) { $db_error_string = $db_prefix . ' ' . $db_error_string; } - if ($db_error_string) { + if ($db_error_string && $force_log) { $this->__dbDebugMessage('db', $db_error_string, 'DB_ERROR', $where_called); } + $context = []; + if ($db_error_string) { + $context = [ + 'pg_error_string' => $db_error_string, + 'where_called' => $where_called, + ]; + } return [ $where_called, - $db_error_string + $db_error_string, + $context ]; } @@ -939,11 +948,12 @@ class IO * 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 - * @param string $pg_error_string - * @param string $msg + * @param string $level warning or error + * @param string $error_id error id + * @param string $where_called context wher eerror was colled + * @param string $pg_error_string if set, postgresql error string + * @param string $message additional message + * @param array $context array with more context information (eg query, params, etc) * @return void */ private function __dbErrorHistory( @@ -951,7 +961,8 @@ class IO string $error_id, string $where_called, string $pg_error_string, - string $msg + string $message, + array $context ): void { if (empty($this->error_history_id)) { $this->error_history_id = Uids::uniqId(self::ERROR_HASH_TYPE); @@ -963,7 +974,8 @@ class IO 'error' => $this->error_string[$error_id] ?? '[UNKNOWN ERROR]', 'source' => $where_called, 'pg_error' => $pg_error_string, - 'msg' => $msg, + 'message' => $message, + 'context' => $context, ]; } @@ -972,54 +984,60 @@ class IO * * @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 + * @param string $message Optional message added to debug + * @param array $context Optional Context array, passed on as error_data to the main error * @return void */ protected function __dbError( int $error_id, \PgSql\Result|false $cursor = false, - string $msg = '' + string $message = '', + array $context = [] ): void { $error_id = (string)$error_id; - [$where_called, $pg_error_string] = $this->__dbErrorPreprocessor($cursor); + [$where_called, $pg_error_string, $_context] = $this->__dbErrorPreprocessor($cursor); // write error msg ... $this->__dbDebugMessage( 'db', $error_id . ': ' . ($this->error_string[$error_id] ?? '[UNKNOWN ERROR]') - . ($msg ? ', ' . $msg : ''), + . ($message ? ', ' . $message : ''), 'DB_ERROR', - $where_called + $where_called, + array_merge($context, $_context) ); $this->error_id = $error_id; - // keep warning history - $this->__dbErrorHistory('error', $error_id, $where_called, $pg_error_string, $msg); + // keep error history + $this->__dbErrorHistory('error', $error_id, $where_called, $pg_error_string, $message, $context); } /** * write a warning * - * @param integer $warning_id Integer warning id added to debug + * @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 + * @param string $message Optional message added to debug + * @param array $context Optional Context array, passed on as error_data to the main error * @return void */ protected function __dbWarning( int $warning_id, \PgSql\Result|false $cursor = false, - string $msg = '' + string $message = '', + array $context = [] ): void { $warning_id = (string)$warning_id; - [$where_called, $pg_error_string] = $this->__dbErrorPreprocessor($cursor); + [$where_called, $pg_error_string, $_context] = $this->__dbErrorPreprocessor($cursor); $this->__dbDebugMessage( 'db', $warning_id . ': ' . ($this->error_string[$warning_id] ?? '[UNKNOWN WARNING') - . ($msg ? ', ' . $msg : ''), + . ($message ? ', ' . $message : ''), 'DB_WARNING', - $where_called + $where_called, + array_merge($context, $_context) ); $this->warning_id = $warning_id; // keep warning history - $this->__dbErrorHistory('warning', $warning_id, $where_called, $pg_error_string, $msg); + $this->__dbErrorHistory('warning', $warning_id, $where_called, $pg_error_string, $message, $context); } /** @@ -1120,39 +1138,69 @@ class IO /** * for debug purpose replaces $1, $2, etc with actual data + * TODO: :name and ? params + * Also works with :name parameters + * ? parameters, will be ignored * * @param string $query Query to replace values in - * @param array $data The data array + * @param array $params The data param array * @return string string of query with data inside */ - private function __dbDebugPrepare(string $query, array $data = []): string + private function __dbDebugPrepare(string $query, array $params = []): string { // skip anything if there is no data - if ($data === []) { + if ($params === []) { return $query; } // get the keys from data array - $keys = array_keys($data); + $keys = array_keys($params); + // check if there is ? or :name i the keys list // because the placeholders start with $ and at 1, // we need to increase each key and prefix it with a $ char for ($i = 0, $iMax = count($keys); $i < $iMax; $i++) { // note: if I use $ here, the str_replace will // replace it again. eg $11 '$1'1would be replaced with $1 again // prefix data set with parameter pos - $data[$i] = '#' . ($keys[$i] + 1) . ':' . ($data[$i] === null ? - '"NULL"' : (string)$data[$i] + $params[$i] = '#' . ($keys[$i] + 1) . ':' . ($params[$i] === null ? + '"NULL"' : (string)$params[$i] ); // search part $keys[$i] = '$' . ($keys[$i] + 1); } // simply replace the $1, $2, ... with the actual data and return it + // note that we do this in return to go from highest number to lowest return str_replace( array_reverse($keys), - array_reverse($data), + array_reverse($params), $query ); } + /** + * Created the context/error_data error for debug messages + * + * @param string $query Query called + * @param array $params Params + * @return array{}|array Empty array if no params, or params + * with optional prepared statement + */ + private function __dbDebugPrepareContext(string $query, array $params = []): array + { + if ($params === []) { + return []; + } + $error_data = [ + 'params' => $params + ]; + if ($this->dbGetDebugReplacePlaceholder()) { + $error_data['prepared'] = $this->__dbDebugPrepare( + $query, + $params + ); + } + return $error_data; + } + /** * extracts schema and table from the query, * if no schema returns just empty string @@ -1164,7 +1212,7 @@ class IO { $matches = []; $schema_table = []; - if ($this->__checkQueryForSelect($query)) { + if ($this->dbCheckQueryForSelect($query)) { // only selects the first one, this is more a fallback // MATCHES 1 (call), 3 (schema), 4 (table) preg_match("/\s+?(FROM)\s+?([\"'])?(?:([\w_]+)\.)?([\w_]+)(?:\2)?\s?/i", $query, $matches); @@ -1254,7 +1302,7 @@ class IO } } } - $this->cursor_ext[$query_hash]['pos'] ++; + $this->cursor_ext[$query_hash]['pos']++; return $return; } @@ -1270,14 +1318,14 @@ class IO // 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 = , ( [equal, comma, opening round bracket] + // 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 preg_match_all( - '/(?:\'.*?\')?\s*(?:\?\?|[(=,])\s*(?:\d+|(?:\'.*?\')|(\$[1-9]{1}(?:[0-9]{1,})?))/s', + '/(?:\'.*?\')?\s*(?:\?\?|<>|[(=,])\s*(?:\d+|(?:\'.*?\')|(\$[1-9]{1}(?:[0-9]{1,})?))/s', $query, $match ); @@ -1288,20 +1336,25 @@ class IO * Checks if the placeholder count in the query matches the params given * on call * - * @param string $query Query to check - * @param int $params_count The parms count expected + * @param string $query Query to check + * @param array $params The parms to count count expected * @return bool True for params count ok, else false */ - private function __dbCheckQueryParams(string $query, int $params_count): bool + private function __dbCheckQueryParams(string $query, array $params): bool { $placeholder_count = $this->__dbCountQueryParams($query); + $params_count = count($params); if ($params_count != $placeholder_count) { $this->__dbError( 23, false, - 'Array data count does not match prepared fields. Need: ' - . $placeholder_count . ', has: ' - . $params_count + 'Need: ' . $placeholder_count . ', has: ' . $params_count, + [ + 'query' => $query, + 'params' => $params, + 'placeholder_needed' => $placeholder_count, + 'placeholder_provided' => $params_count, + ] ); return false; } @@ -1348,7 +1401,6 @@ class IO if (!$this->dbh) { // if reconnect fails drop out if (!$this->__connectToDB()) { - $this->__dbError(16); return false; } } @@ -1358,7 +1410,7 @@ class IO } // if we do have an insert, check if there is no RETURNING pk_id, // add it if I can get the PK id - if ($this->__checkQueryForInsert($this->query, true)) { + if ($this->dbCheckQueryForInsert($this->query, true)) { $this->pk_name = $pk_name; if ($this->pk_name != 'NULL') { if (!$this->pk_name) { @@ -1396,24 +1448,55 @@ class IO } // if we have an UPDATE and RETURNING, flag for true, but do not add anything if ( - $this->__checkQueryForUpdate($this->query) && + $this->dbCheckQueryForUpdate($this->query) && preg_match(self::REGEX_RETURNING, $this->query, $matches) ) { $this->returning_id = true; } // import protection, hash needed $query_hash = $this->dbGetQueryHash($this->query, $this->params); + // QUERY PARAMS: run query params check and rewrite + if ($this->dbGetConvertPlaceholder() === true) { + try { + $this->placeholder_converted = ConvertPlaceholder::convertPlaceholderInQuery( + $this->query, + $this->params, + $this->dbGetConvertPlaceholderTarget() + ); + // write the new queries over the old + if (!empty($this->placeholder_converted['query'])) { + $this->query = $this->placeholder_converted['query']; + $this->params = $this->placeholder_converted['params']; + } + } catch (\OutOfRangeException $e) { + $this->__dbError($e->getCode(), context:[ + 'query' => $this->query, + 'params' => $this->params, + 'location' => '__dbPrepareExec', + 'error' => 'OutOfRangeException', + 'exception' => $e + ]); + return false; + } catch (\RuntimeException $e) { + $this->__dbError($e->getCode(), context:[ + 'query' => $this->query, + 'params' => $this->params, + 'location' => '__dbPrepareExec', + 'error' => 'RuntimeException', + 'exception' => $e + ]); + return false; + } + } // $this->debug('DB IO', 'Q: ' . $this->query . ', RETURN: ' . $this->returning_id); // for DEBUG, only on first time ;) $this->__dbDebug( 'db', - $this->__dbDebugPrepare( - $this->query, - $this->params - ), + $this->query, '__dbPrepareExec', - ($this->params === [] ? 'Q' : 'Qp') + ($this->params === [] ? 'Q' : 'Qp'), + error_data: $this->__dbDebugPrepareContext($this->query, $this->params) ); // if the array index does not exists set it 0 if (!array_key_exists($query_hash, $this->query_called)) { @@ -1429,19 +1512,14 @@ class IO $this->MAX_QUERY_CALL != -1 && $this->query_called[$query_hash] > $this->MAX_QUERY_CALL ) { - $this->__dbError(30, false, $this->query); - $this->__dbDebugMessage( - 'db', - $this->__dbDebugPrepare( - $this->query, - $this->params - ), - 'dbExec', - ($this->params === [] ? 'Q[nc]' : 'Qp[nc]') - ); + $this->__dbError(30, false, context: [ + 'query' => $this->query, + 'params' => $this->params, + 'location' => '__dbPrepareExec' + ]); return false; } - $this->query_called[$query_hash] ++; + $this->query_called[$query_hash]++; // return hash return $query_hash; } @@ -1458,14 +1536,17 @@ class IO // if FALSE returned, set error stuff // if either the cursor is false if ($this->cursor === false || $this->db_functions->__dbLastErrorQuery()) { - // printout Query if debug is turned on - $this->__dbDebug('db', $this->query, 'dbExec', 'Q[nc]'); // internal error handling - $this->__dbError(13, $this->cursor); + $this->__dbError(13, $this->cursor, context: [ + 'query' => $this->query, + 'params' => $this->params, + 'location' => 'dbExec', + 'query_id' => 'Q[nc]', + ]); return false; } else { // if SELECT do here ... - if ($this->__checkQueryForSelect($this->query)) { + if ($this->dbCheckQueryForSelect($this->query)) { // count the rows returned (if select) $this->num_rows = $this->db_functions->__dbNumRows($this->cursor); // count the fields @@ -1486,15 +1567,15 @@ class IO $this->field_names, $this->field_types ); - } elseif ($this->__checkQueryForInsert($this->query)) { + } elseif ($this->dbCheckQueryForInsert($this->query)) { // if not select do here // count affected rows $this->num_rows = $this->db_functions->__dbAffectedRows($this->cursor); if ( // ONLY insert with set pk name - ($this->__checkQueryForInsert($this->query, true) && $this->pk_name != 'NULL') || + ($this->dbCheckQueryForInsert($this->query, true) && $this->pk_name != 'NULL') || // insert or update with returning add - ($this->__checkQueryForInsert($this->query) && $this->returning_id) + ($this->dbCheckQueryForInsert($this->query) && $this->returning_id) ) { $this->__dbSetInsertId( $this->returning_id, @@ -1516,15 +1597,13 @@ class IO * - if many this will also hold all non pk names too * then try to fill insert_id_arr, this is always multi level * - fill key: value as single array or multi array - * 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 + * @param bool $returning_id False if no RETURNING, try to get different via insert id + * @param string $query Query with RETURNING + * @param string|null $pk_name Primary key name * @param \PgSql\Result|false $cursor (PgSql\Result) - * @param string|null $stm_name If not null, is dbExecutre run + * @param string|null $stm_name [null] If not null, is dbExecute run and not a prepared call * @return void */ private function __dbSetInsertId( @@ -1546,9 +1625,16 @@ class IO if ($cursor === false) { // failed to get insert id if ($stm_name === null) { - $this->__dbWarning(34, $cursor, '[dbExec]'); + $this->__dbWarning(34, $cursor, '[dbExec]', context: [ + 'query' => $query, + 'pk_name' => $pk_name, + ]); } else { - $this->__dbWarning(34, false, $stm_name . ': CURSOR is null'); + $this->__dbWarning(34, false, 'CURSOR is null', [ + 'statement_name' => $stm_name, + 'query' => $query, + 'pk_name' => $pk_name, + ]); } return; } @@ -1560,7 +1646,11 @@ class IO $this->insert_id_arr[] = $insert_id; // throw warning that no pk was found if ($insert_id === false) { - $this->__dbWarning(31, $cursor, '[dbExec]'); + $this->__dbWarning(31, $cursor, '[dbExec]', context: [ + 'query' => $query, + 'pk_name' => $pk_name, + 'returning_id' => $returning_id, + ]); } } else { // was stm_name null or not null and cursor // we have returning, now we need to check if we get one or many returned @@ -1577,23 +1667,43 @@ class IO if (count($this->insert_id_arr) == 0) { // failed to get insert id if ($stm_name === null) { - $this->__dbWarning(33, $cursor, '[dbExec]'); + $this->__dbWarning(33, $cursor, '[dbExec]', context: [ + 'query' => $query, + 'pk_name' => $pk_name, + 'returning_id' => $returning_id, + ]); } else { $this->__dbWarning( 33, false, - $stm_name . ': RETURNING returned no data' + 'RETURNING returned no data', + context: [ + 'statement_name' => $stm_name, + 'query' => $query, + 'pk_name' => $pk_name, + 'returning_id' => $returning_id, + ] ); } } elseif (count($this->insert_id_arr) > 1) { // this error handling is only for INSERT (), (), ... sets if ($stm_name === null) { - $this->__dbWarning(32, $cursor, '[dbExec]'); + $this->__dbWarning(32, $cursor, '[dbExec]', context: [ + 'query' => $query, + 'pk_name' => $pk_name, + 'returning_id' => $returning_id, + ]); } else { $this->__dbWarning( 32, false, - $stm_name . ': RETURNING returned an array (possible multiple insert)' + 'RETURNING returned an array (possible multiple insert)', + context: [ + 'statement_name' => $stm_name, + 'query' => $query, + 'pk_name' => $pk_name, + 'returning_id' => $returning_id, + ] ); } } @@ -1864,6 +1974,63 @@ class IO return $string; } + // *************************** + // CHECK QUERY TYPE + // *************************** + + /** + * 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 + */ + public function dbCheckQueryForSelect(string $query): bool + { + // change to string starts with? + if (preg_match("/^\s*(?:SELECT|SHOW|WITH)\s/i", $query)) { + return true; + } + return false; + } + + /** + * check for DELETE, INSERT, UPDATE + * 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 + */ + public function dbCheckQueryForInsert(string $query, bool $pure = false): bool + { + if ($pure && preg_match("/^\s*INSERT\s+?INTO\s/i", $query)) { + return true; + } + if (!$pure && preg_match("/^\s*(?:INSERT\s+?INTO|DELETE\s+?FROM|UPDATE)\s/i", $query)) { + return true; + } + return false; + } + + /** + * 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 + */ + public function dbCheckQueryForUpdate(string $query): bool + { + if (preg_match("/^\s*UPDATE\s?(.+)/i", $query)) { + return true; + } + return false; + } + // *************************** // DATA WRITE CONVERSION // *************************** @@ -2110,7 +2277,10 @@ class IO $table = (!empty($schema) ? $schema . '.' : '') . $table; $array = $this->db_functions->__dbMetaData($table); if (!is_array($array)) { - $this->__dbError(60); + $this->__dbError(60, context: [ + 'table' => $table, + 'schema' => $schema + ]); $array = false; } return $array; @@ -2219,6 +2389,8 @@ class IO 'query' => '', // parameter 'params' => [], + // if we convert placeholders, conversion data is stored here + 'placeholder_converted' => [], // cache flag from method call 'cache_flag' => $cache, // flag if we only have assoc data @@ -2236,35 +2408,71 @@ class IO 'log_pos' => 1, // how many times called overall 'log' => [], // current run log ]; + + // set the query + $this->cursor_ext[$query_hash]['query'] = $query; + // set the query parameters + $this->cursor_ext[$query_hash]['params'] = $params; + // before doing ANYTHING check if query is "SELECT ..." everything else does not work + if (!$this->dbCheckQueryForSelect($this->cursor_ext[$query_hash]['query'])) { + $this->__dbError(17, false, context: [ + 'query' => $this->cursor_ext[$query_hash]['query'], + 'params' => $this->cursor_ext[$query_hash]['params'], + 'location' => 'dbReturn', + ]); + return false; + } + // QUERY PARAMS: run query params check and rewrite + if ($this->dbGetConvertPlaceholder() === true) { + try { + $this->cursor_ext[$query_hash]['placeholder_converted'] = + ConvertPlaceholder::convertPlaceholderInQuery( + $this->cursor_ext[$query_hash]['query'], + $this->cursor_ext[$query_hash]['params'], + $this->dbGetConvertPlaceholderTarget() + ); + if (!empty($this->cursor_ext[$query_hash]['placeholder_converted']['query'])) { + $this->cursor_ext[$query_hash]['query'] = + $this->cursor_ext[$query_hash]['placeholder_converted']['query']; + $this->cursor_ext[$query_hash]['params'] = + $this->cursor_ext[$query_hash]['placeholder_converted']['params']; + } + } catch (\OutOfRangeException $e) { + $this->__dbError($e->getCode(), context:[ + 'query' => $this->cursor_ext[$query_hash]['query'], + 'params' => $this->cursor_ext[$query_hash]['params'], + 'location' => 'dbReturn', + 'error' => 'OutOfRangeException', + 'exception' => $e + ]); + return false; + } catch (\RuntimeException $e) { + $this->__dbError($e->getCode()); + $this->__dbError($e->getCode(), context:[ + 'query' => $this->cursor_ext[$query_hash]['query'], + 'params' => $this->cursor_ext[$query_hash]['params'], + 'location' => 'dbReturn', + 'error' => 'RuntimeException', + 'exception' => $e + ]); + return false; + } + } + // check if params count matches + // checks if the params count given matches the expected count + if ( + $this->__dbCheckQueryParams( + $this->cursor_ext[$query_hash]['query'], + $this->cursor_ext[$query_hash]['params'] + ) === false + ) { + return false; + } } else { - $this->cursor_ext[$query_hash]['log_pos'] ++; + $this->cursor_ext[$query_hash]['log_pos']++; } // reset log for each read $this->cursor_ext[$query_hash]['log'] = []; - // set the query - $this->cursor_ext[$query_hash]['query'] = $query; - // before doing ANYTHING check if query is "SELECT ..." everything else does not work - if (!$this->__checkQueryForSelect($this->cursor_ext[$query_hash]['query'])) { - $this->__dbError(17, false, $this->cursor_ext[$query_hash]['query']); - return false; - } - // set the query parameters - $this->cursor_ext[$query_hash]['params'] = $params; - // check if params count matches - // checks if the params count given matches the expected count - if ($this->__dbCheckQueryParams($query, count($params)) === false) { - // in case we got an error print out query - $this->__dbDebug( - 'db', - $this->__dbDebugPrepare( - $this->query, - $this->params - ), - 'dbReturn', - ($this->params === [] ? 'Q[e]' : 'Qp[e]') - ); - return false; - } // set first call to false $first_call = false; // init return als false @@ -2287,18 +2495,18 @@ class IO // for DEBUG, print out each query executed $this->__dbDebug( 'db', - $this->__dbDebugPrepare( - $this->cursor_ext[$query_hash]['query'], - $this->cursor_ext[$query_hash]['params'] - ), + $this->cursor_ext[$query_hash]['query'], 'dbReturn', ($this->cursor_ext[$query_hash]['params'] === [] ? 'Q' : 'Qp'), + error_data: $this->__dbDebugPrepareContext( + $this->cursor_ext[$query_hash]['query'], + $this->cursor_ext[$query_hash]['params'] + ) ); // if no DB Handler try to reconnect if (!$this->dbh) { // if reconnect fails drop out if (!$this->__connectToDB()) { - $this->__dbError(16); return false; } } @@ -2320,17 +2528,12 @@ class IO } // if still no cursor ... if (!$this->cursor_ext[$query_hash]['cursor']) { - $this->__dbDebug( - 'db', - $this->__dbDebugPrepare( - $this->cursor_ext[$query_hash]['query'], - $this->cursor_ext[$query_hash]['params'] - ), - 'dbReturn', - ($this->cursor_ext[$query_hash]['params'] === [] ? 'Q[e]' : 'Qp[e]'), - ); // internal error handling - $this->__dbError(13, $this->cursor_ext[$query_hash]['cursor']); + $this->__dbError(13, $this->cursor_ext[$query_hash]['cursor'], context: [ + 'query' => $this->cursor_ext[$query_hash]['query'], + 'params' => $this->cursor_ext[$query_hash]['params'], + 'location' => 'dbReturn' + ]); return false; } else { $first_call = true; @@ -2465,8 +2668,8 @@ class IO if ($return) { $this->cursor_ext[$query_hash]['log'][] = 'Return Data'; // internal position counter - $this->cursor_ext[$query_hash]['pos'] ++; - $this->cursor_ext[$query_hash]['read_rows'] ++; + $this->cursor_ext[$query_hash]['pos']++; + $this->cursor_ext[$query_hash]['read_rows']++; // read is finished if ( $this->cursor_ext[$query_hash]['read_rows'] == @@ -2540,24 +2743,28 @@ class IO ): \PgSql\Result|false { $this->__dbErrorReset(); // prepare and check if we can actually run it - if ($this->__dbPrepareExec($query, $params, $pk_name) === false) { + if (($query_hash = $this->__dbPrepareExec($query, $params, $pk_name)) === false) { // bail if no query hash set return false; } // checks if the params count given matches the expected count - if ($this->__dbCheckQueryParams($query, count($params)) === false) { + if ($this->__dbCheckQueryParams($this->query, $this->params) === false) { return false; } // ** actual db exec call - if ($params === []) { + if ($this->params === []) { $cursor = $this->db_functions->__dbQuery($this->query); } else { - $cursor = $this->db_functions->__dbQueryParams($this->query, $params); + $cursor = $this->db_functions->__dbQueryParams($this->query, $this->params); } // if we faield, just set the master cursors to false too $this->cursor = $cursor; if ($cursor === false) { - $this->__dbError(13); + $this->__dbError(13, context: [ + 'query' => $this->query, + 'params' => $this->params, + 'location' => 'dbExecParams', + ]); return false; } // if FALSE returned, set error stuff @@ -2637,8 +2844,13 @@ class IO } // before doing ANYTHING check if query is // "SELECT ..." everything else does not work - if (!$this->__checkQueryForSelect($query)) { - $this->__dbError(17, false, $query); + if (!$this->dbCheckQueryForSelect($query)) { + $this->__dbError(17, false, context: [ + 'query' => $query, + 'params' => $params, + 'assoc_only' => $assoc_only, + 'location' => 'dbReturnRowParams' + ]); return false; } $cursor = $this->dbExecParams($query, $params); @@ -2682,8 +2894,13 @@ class IO return false; } // before doing ANYTHING check if query is "SELECT ..." everything else does not work - if (!$this->__checkQueryForSelect($query)) { - $this->__dbError(17, false, $query); + if (!$this->dbCheckQueryForSelect($query)) { + $this->__dbError(17, false, context: [ + 'query' => $query, + 'params' => $params, + 'assoc_only' => $assoc_only, + 'location' => 'dbReturnArrayParams' + ]); return false; } $cursor = $this->dbExecParams($query, $params); @@ -2728,7 +2945,11 @@ class IO $query_hash = $this->dbGetQueryHash($query, $params); // clears cache for this query if (empty($this->cursor_ext[$query_hash]['query'])) { - $this->__dbError(18); + $this->__dbError(18, context: [ + 'query' => $query, + 'params' => $params, + 'hash' => $query_hash, + ]); return false; } unset($this->cursor_ext[$query_hash]); @@ -2897,7 +3118,6 @@ class IO if (!$this->dbh) { // if reconnect fails drop out if (!$this->__connectToDB()) { - $this->__dbError(16); return false; } } @@ -2924,7 +3144,7 @@ class IO 'returning_id' => false ]; // if this is an insert query, check if we can add a return - if ($this->__checkQueryForInsert($query, true)) { + if ($this->dbCheckQueryForInsert($query, true)) { if ($pk_name != 'NULL') { // set primary key name // current: only via parameter @@ -2972,14 +3192,34 @@ class IO $this->__dbError( 21, false, - $stm_name . ': Prepare field with: ' . $stm_name . ' | ' . $query + context: [ + 'statement_name' => $stm_name, + 'query' => $query, + 'pk_name' => $pk_name, + ] ); return $result; } } else { - // thrown warning - $this->__dbWarning(20, false, $stm_name); - return true; + // if we try to use the same statement name for a differnt query, error abort + if ($this->prepare_cursor[$stm_name]['query'] != $query) { + // thrown error + $this->__dbError(26, false, context: [ + 'statement_name' => $stm_name, + 'prepared_query' => $this->prepare_cursor[$stm_name]['query'], + 'query' => $query, + 'pk_name' => $pk_name, + ]); + return false; + } else { + // thrown warning + $this->__dbWarning(20, false, context: [ + 'statement_name' => $stm_name, + 'query' => $query, + 'pk_name' => $pk_name, + ]); + return true; + } } } @@ -2997,7 +3237,6 @@ class IO if (!$this->dbh) { // if reconnect fails drop out if (!$this->__connectToDB()) { - $this->__dbError(16); return false; } } @@ -3019,28 +3258,37 @@ class IO $this->__dbError( 24, false, - $stm_name . ': We do not have a prepared query entry for this statement name.' + $stm_name . ': We do not have a prepared query entry for this statement name.', + context: ['statement_name' => $stm_name] ); return false; } $this->__dbDebug( 'db', - $this->__dbDebugPrepare( + $this->prepare_cursor[$stm_name]['query'], + 'dbExecute', + 'Qpe', + error_data: array_merge([ + 'statement_name' => $stm_name, + ], $this->__dbDebugPrepareContext( $this->prepare_cursor[$stm_name]['query'], $data - ), - 'dbExecPrep', - 'Qpe' + )) ); // if the count does not match if ($this->prepare_cursor[$stm_name]['count'] != count($data)) { $this->__dbError( 23, false, - $stm_name - . ': Array data count does not match prepared fields. Need: ' - . $this->prepare_cursor[$stm_name]['count'] . ', has: ' - . count($data) + '(' . $stm_name . ') ' + . 'Need: ' . $this->prepare_cursor[$stm_name]['count'] . ', has: ' . count($data), + context: [ + 'statement_name' => $stm_name, + 'query' => $this->prepare_cursor[$stm_name]['query'], + 'params' => $data, + 'placeholder_needed' => $this->prepare_cursor[$stm_name]['count'], + 'placeholder_provided' => count($data) + ] ); return false; } @@ -3052,16 +3300,20 @@ class IO $this->__dbError( 22, $this->prepare_cursor[$stm_name]['result'], - $stm_name . ': Execution failed' + context: [ + 'statement_name' => $stm_name, + 'query' => $this->prepare_cursor[$stm_name]['query'], + 'params' => $data + ] ); return false; } if ( // pure insert wth pk name - ($this->__checkQueryForInsert($this->prepare_cursor[$stm_name]['query'], true) && + ($this->dbCheckQueryForInsert($this->prepare_cursor[$stm_name]['query'], true) && $this->prepare_cursor[$stm_name]['pk_name'] != 'NULL') || // insert or update with returning set - ($this->__checkQueryForInsert($this->prepare_cursor[$stm_name]['query']) && + ($this->dbCheckQueryForInsert($this->prepare_cursor[$stm_name]['query']) && $this->prepare_cursor[$stm_name]['returning_id'] === true ) ) { @@ -3122,20 +3374,24 @@ class IO return false; } // checks if the params count given matches the expected count - if ($this->__dbCheckQueryParams($query, count($params)) === false) { + if ($this->__dbCheckQueryParams($this->query, $this->params) === false) { return false; } // ** actual db exec call if ($params === []) { $status = $this->db_functions->__dbSendQuery($this->query); } else { - $status = $this->db_functions->__dbSendQueryParams($this->query, $params); + $status = $this->db_functions->__dbSendQueryParams($this->query, $this->params); } // run the async query, this just returns true or false // the actually result is in dbCheckAsync if (!$status) { // if failed, process here - $this->__dbError(40); + $this->__dbError(40, context: [ + 'query' => $this->query, + 'params' => $this->params, + 'pk_name' => $pk_name, + ]); return false; } else { $this->async_running = (string)$query_hash; @@ -3216,8 +3472,7 @@ class IO // if no async running print error $this->__dbError( 42, - false, - 'No async query has been started yet.' + false ); return false; } @@ -3461,7 +3716,7 @@ class IO } /** - * Undocumented function + * convert db values (set) * * @param Convert $convert * @return void @@ -3472,7 +3727,7 @@ class IO } /** - * Undocumented function + * unsert convert db values flag * * @param Convert $convert * @return void @@ -3495,7 +3750,7 @@ class IO } /** - * Undocumented function + * check if a conert flag is set * * @param Convert $convert * @return bool @@ -3508,6 +3763,73 @@ class IO return false; } + /** + * Set if we want to auto convert PDO/\Pg placeholders + * + * @param bool $flag + * @return void + */ + public function dbSetConvertPlaceholder(bool $flag): void + { + $this->db_convert_placeholder = $flag; + } + + /** + * get the flag status if we want to auto convert placeholders in the query + * + * @return bool + */ + public function dbGetConvertPlaceholder(): bool + { + return $this->db_convert_placeholder; + } + + /** + * Set convert target for placeholders, returns false on error, true on ok + * + * @param string $target 'pg' or 'pdo', defined in DB_CONVERT_PLACEHOLDER_TARGET + * @return bool + */ + public function dbSetConvertPlaceholderTarget(string $target): bool + { + if (in_array($target, self::DB_CONVERT_PLACEHOLDER_TARGET)) { + $this->db_convert_placeholder_target = $target; + return true; + } + return false; + } + + /** + * Get the current placeholder convert target + * + * @return string + */ + public function dbGetConvertPlaceholderTarget(): string + { + return $this->db_convert_placeholder_target; + } + + /** + * Set flag if we print the query with replaced placeholders or not + * + * @param bool $flag + * @return void + */ + public function dbSetDebugReplacePlaceholder(bool $flag): void + { + $this->db_debug_replace_placeholder = $flag; + } + + /** + * get the current setting for the debug replace placeholder + * + * @return bool True for replace query, False for not + */ + public function dbGetDebugReplacePlaceholder(): bool + { + return $this->db_debug_replace_placeholder; + } + /** * set max query calls, set to -1 to disable loop * protection. this will generate a warning @@ -3525,11 +3847,11 @@ class IO // if -1 then disable loop check // DANGEROUS, WARN USER if ($max_calls == -1) { - $this->__dbWarning(50); + $this->__dbWarning(50, context: ['max_calls' => $max_calls]); } // negative or 0 if ($max_calls < -1 || $max_calls == 0) { - $this->__dbError(51); + $this->__dbError(51, context: ['max_calls' => $max_calls]); // early abort return false; } @@ -3573,7 +3895,7 @@ class IO case 2: // setting schema failed (3) case 3: - $this->__dbError(71); + $this->__dbError(71, context: ['schema' => $db_schema]); $status = false; break; } @@ -3623,7 +3945,7 @@ class IO case 2: // 3 is set failed case 3: - $this->__dbError(81); + $this->__dbError(81, context: ['encoding' => $db_encoding]); $status = false; break; } @@ -3748,6 +4070,16 @@ class IO $this->params = []; } + /** + * Returns the placeholder convert set or empty + * + * @return array{}|array{original:array{query:string,params:array},type:''|'named'|'numbered'|'question_mark',found:int,matches:array,params_lookup:array,query:string,params:array} + */ + public function dbGetPlaceholderConverted(): array + { + return $this->placeholder_converted; + } + // *************************** // INTERNAL VARIABLES READ POST QUERY RUN // *************************** @@ -3966,7 +4298,8 @@ class IO $this->__dbError( 102, false, - 'Invalid key name' + 'Invalid key name', + context: ['key' => $key] ); return false; } @@ -3975,7 +4308,8 @@ class IO $this->__dbError( 103, false, - 'Statement name does not exist in prepare cursor array' + 'Statement name does not exist in prepare cursor array', + context: ['statement_name' => $stm_name] ); return false; } @@ -3984,7 +4318,11 @@ class IO $this->__dbError( 104, false, - 'Key does not exist in prepare cursor array' + 'Key does not exist in prepare cursor array', + context: [ + 'statement_name' => $stm_name, + 'key' => $key + ] ); return false; } diff --git a/www/vendor/egrajp/corelibs-composer-all/src/DB/Support/ConvertPlaceholder.php b/www/vendor/egrajp/corelibs-composer-all/src/DB/Support/ConvertPlaceholder.php new file mode 100644 index 00000000..bcfd5613 --- /dev/null +++ b/www/vendor/egrajp/corelibs-composer-all/src/DB/Support/ConvertPlaceholder.php @@ -0,0 +1,220 @@ + $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 + */ + public static function convertPlaceholderInQuery( + string $query, + array $params, + string $convert_to = 'pg' + ): array { + $convert_to = strtolower($convert_to); + $matches = []; + $pattern = '/' + // prefix string part, must match towards + . '(?:\'.*?\')?\s*(?:\?\?|[(=,])\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); + // if false or null set to -1 + // || $found === null + if ($found === false) { + $found = -1; + } + /** @var array 1: named */ + $named_matches = array_filter($matches[1]); + /** @var array 2: open ? */ + $qmark_matches = array_filter($matches[2]); + /** @var array 3: $n matches */ + $numbered_matches = array_filter($matches[3]); + // count matches + $count_named = count($named_matches); + $count_qmark = count($qmark_matches); + $count_numbered = count($numbered_matches); + // throw if mixed + if ( + ($count_named && $count_qmark) || + ($count_named && $count_numbered) || + ($count_qmark && $count_numbered) + ) { + throw new \OutOfRangeException('Cannot have named, question mark and numbered in the same query', 200); + } + // rebuild + $matches_return = []; + $type = ''; + $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*(?:\?\?|[(=,])\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]] ?? + throw new \RuntimeException( + 'Cannot lookup ' . $matches[3] . ' in params lookup list', + 211 + ) + ); + }, + $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*(?:\?\?|[(=,])\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*(?:\?\?|[(=,])\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 + ); + } + // 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, + ]; + } +} + +// __END__ diff --git a/www/vendor/egrajp/corelibs-composer-all/src/Debug/Support.php b/www/vendor/egrajp/corelibs-composer-all/src/Debug/Support.php index 456be11c..affec0f7 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/Debug/Support.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/Debug/Support.php @@ -295,8 +295,7 @@ class Support * Will start with start_level to skip unwanted from stack * Defaults to skip level 0 wich is this methid * - * @param integer $start_level From what level on, as defaul starts with 1 - * to exclude self + * @param integer $start_level [=1] From what level on, starts with 1 to exclude self * @return array All method names in list where max is last called */ public static function getCallerMethodList(int $start_level = 1): array @@ -304,15 +303,46 @@ class Support $traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); $methods = []; foreach ($traces as $level => $data) { - if ($level >= $start_level) { - if (!empty($data['function'])) { - array_unshift($methods, $data['function']); - } + if ($level < $start_level) { + continue; + } + if (!empty($data['function'])) { + array_unshift($methods, $data['function']); } } return $methods; } + /** + * Get the full call stack from a certain starting level + * The return string is + * file:line:class->method + * + * Note that '::' is used for static calls + * + * @param int $start_level [=1] starts with 1 to exclude itself + * @return array string with file, line, class and method + */ + public static function getCallStack(int $start_level = 1): array + { + $call_stack = []; + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + foreach ($backtrace as $level => $call_trace) { + if ($level < $start_level) { + continue; + } + $call_stack[] = + ($call_trace['file'] ?? 'n/f') . ':' + . ($call_trace['line'] ?? '-') . ':' + . (!empty($call_trace['class']) ? + $call_trace['class'] . ($call_trace['type'] ?? '') : + '' + ) + . $call_trace['function']; + } + return $call_stack; + } + /** * Get the current class where this function is called * Is mostly used in debug log statements to get the class where the 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 a09eab66..3c406d4d 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 @@ -310,7 +310,7 @@ class Generate * construct form generator * * 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 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_convert_placeholder?:bool,db_convert_placeholder_target?:string,db_debug_replace_placeholder?:bool} $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, @@ -826,27 +826,28 @@ class Generate $pk_selected = $res[$this->int_pk_name]; } $t_string = ''; - foreach ($this->field_array as $i => $field_array) { + foreach ($this->field_array as $field_array) { if ($t_string) { $t_string .= ', '; } - if (isset($field_array['before_value'])) { - $t_string .= $field_array['before_value']; + if (!empty($field_array['before_value'])) { + $t_string .= $this->l->__($field_array['before_value']); } // must have res element set if ( - isset($field_array['name']) && + !empty($field_array['name']) && isset($res[$field_array['name']]) ) { - if (isset($field_array['binary'])) { - if (isset($field_array['binary'][0])) { - $t_string .= $field_array['binary'][0]; - } elseif (isset($field_array['binary'][1])) { - $t_string .= $field_array['binary'][1]; - } + $_t_value = ''; + // if we have a binary set, where 0 = YES and 1 = NO + if (!empty($field_array['binary'])) { + $_t_value = !empty($res[$field_array['name']]) ? + ($field_array['binary'][0] ?? 'Yes') : + ($field_array['binary'][1] ?? 'No'); } else { - $t_string .= $res[$field_array['name']]; + $_t_value = $res[$field_array['name']]; } + $t_string .= $this->l->__($_t_value); } } $pk_names[] = $t_string; 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 897e6495..9fd82619 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/Output/Image.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/Output/Image.php @@ -78,7 +78,7 @@ class Image if (!empty($dummy) && file_exists($filename) && is_file($filename)) { $return_data = $filename; } else { - throw new \Exception('Could not set dummy return file: ' . $dummy . ' in ' . $filename); + throw new \RuntimeException('Could not set dummy return file: ' . $dummy . ' in ' . $filename); } } else { $return_data = $dummy; @@ -204,11 +204,11 @@ class Image E_USER_DEPRECATED ); // NOTE: we need to depracte this - $cache_folder = BASE . LAYOUT . CONTENT_PATH . CACHE . IMAGES; + $cache_folder = BASE . CONTENT_PATH . LAYOUT . CACHE . IMAGES; $web_folder = LAYOUT . CACHE . IMAGES; if (!is_dir($cache_folder)) { if (false === mkdir($cache_folder)) { - $cache_folder = BASE . LAYOUT . CONTENT_PATH . CACHE; + $cache_folder = BASE . CONTENT_PATH . LAYOUT . CACHE; $web_folder = LAYOUT . CACHE; } } diff --git a/www/vendor/egrajp/corelibs-composer-all/src/Output/ProgressBar.php b/www/vendor/egrajp/corelibs-composer-all/src/Output/ProgressBar.php index 6affd992..5c4f9c76 100644 --- a/www/vendor/egrajp/corelibs-composer-all/src/Output/ProgressBar.php +++ b/www/vendor/egrajp/corelibs-composer-all/src/Output/ProgressBar.php @@ -156,7 +156,7 @@ class ProgressBar { // avoid divison through 0 if ($this->max - $this->min == 0) { - $this->max ++; + $this->max++; } $percent = round(($step - $this->min) / ($this->max - $this->min) * 100); if ($percent > 100) { @@ -186,7 +186,7 @@ class ProgressBar } // avoid divison through 0 if ($this->max - $this->min == 0) { - $this->max ++; + $this->max++; } $pixel = round(($step - $this->min) * ($bar - ($this->pedding * 2)) / ($this->max - $this->min)); if ($step <= $this->min) { diff --git a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Check/CoreLibsCheckFileTest.php b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Check/CoreLibsCheckFileTest.php index c94ad943..56141aa2 100644 --- a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Check/CoreLibsCheckFileTest.php +++ b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Check/CoreLibsCheckFileTest.php @@ -28,10 +28,10 @@ final class CoreLibsCheckFileTest extends TestCase public function filesList(): array { return [ - ['filename.txt', 'txt', 5], - ['filename.csv', 'csv', 15], - ['filename.tsv', 'tsv', 0], - ['file_does_not_exits', '', -1], + ['filename.txt', 'txt', 5, 'text/plain'], + ['filename.csv', 'csv', 15, 'text/csv'], + ['filename.tsv', 'tsv', 0, 'text/plain'], + ['file_does_not_exits', '', -1, ''], ]; } @@ -63,6 +63,15 @@ final class CoreLibsCheckFileTest extends TestCase return $list; } + public function mimeTypeProvider(): array + { + $list = []; + foreach ($this->filesList() as $row) { + $list[$row[0] . ' must be mime type ' . $row[3]] = [$row[0], $row[3]]; + } + return $list; + } + /** * Tests if file extension matches * @@ -115,6 +124,51 @@ final class CoreLibsCheckFileTest extends TestCase unlink($this->base_folder . $input); } } + + /** + * Undocumented function + * + * @covers ::getMimeType + * @dataProvider mimeTypeProvider + * @testdox getMimeType $input must be mime type $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testGetMimeType(string $input, string $expected): void + { + if (!empty($expected)) { + $file = $this->base_folder . $input; + $fp = fopen($file, 'w'); + switch ($expected) { + case 'text/csv': + for ($i = 1; $i <= 10; $i++) { + fwrite($fp, '"This is row","' . $expected . '",' . $i . PHP_EOL); + } + break; + case 'text/tsv': + for ($i = 1; $i <= 10; $i++) { + fwrite($fp, "\"This is row\"\t\"" . $expected . "\"\t\"" . $i . PHP_EOL); + } + break; + case 'text/plain': + fwrite($fp, 'This is mime type: ' . $expected . PHP_EOL); + break; + } + fclose($fp); + } else { + $this->expectException(\UnexpectedValueException::class); + } + $this->assertEquals( + $expected, + \CoreLibs\Check\File::getMimeType($this->base_folder . $input) + ); + // unlink file + if (is_file($this->base_folder . $input)) { + unlink($this->base_folder . $input); + } + } } // __END__ diff --git a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Combined/CoreLibsCombinedArrayHandlerTest.php b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Combined/CoreLibsCombinedArrayHandlerTest.php index 25ade3a5..8dc5729a 100644 --- a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Combined/CoreLibsCombinedArrayHandlerTest.php +++ b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Combined/CoreLibsCombinedArrayHandlerTest.php @@ -1098,16 +1098,109 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase * @testdox arrayFlatForKey array $input will be $expected [$_dataName] * * @param array $input + * @param string $search * @param array $expected * @return void */ - public function testArrayFlatForKey(array $input, $search, array $expected): void + public function testArrayFlatForKey(array $input, string $search, array $expected): void { $this->assertEquals( $expected, \CoreLibs\Combined\ArrayHandler::arrayFlatForKey($input, $search) ); } + + /** + * Undocumented function + * + * @return array + */ + public function providerArrayGetNextPrevKey(): array + { + return [ + 'find, ok' => [ + 'input' => [ + 'a' => 'First', + 'b' => 'Second', + 'c' => 'Third', + ], + 'b', + 'a', + 'c' + ], + 'find, first' => [ + 'input' => [ + 'a' => 'First', + 'b' => 'Second', + 'c' => 'Third', + ], + 'a', + null, + 'b' + ], + 'find, last' => [ + 'input' => [ + 'a' => 'First', + 'b' => 'Second', + 'c' => 'Third', + ], + 'c', + 'b', + null + ], + 'find, not found' => [ + 'input' => [ + 'a' => 'First', + 'b' => 'Second', + 'c' => 'Third', + ], + 'z', + null, + null + ], + 'int, index' => [ + 'input' => [ + 'a', + 'b', + 'c' + ], + 1, + 0, + 2 + ] + ]; + } + + /** + * Undocumented function + * + * @covers ::arrayGetPrevKey, ::arrayGetNextKey + * @dataProvider providerArrayGetNextPrevKey + * @testdox arrayGetNextPrevKey get next/prev key for $search wtih $expected_prev/$expected_next [$_dataName] + * + * @param array $input + * @param int|string $search + * @param int|string|null $expected_prev + * @param int|string|null $expected_next + * @return void + */ + public function testArrayGetNextPrevKey( + array $input, + int|string $search, + int|string|null $expected_prev, + int|string|null $expected_next + ): void { + $this->assertEquals( + $expected_prev, + \CoreLibs\Combined\ArrayHandler::arrayGetPrevKey($input, $search), + 'Find prev key in array' + ); + $this->assertEquals( + $expected_next, + \CoreLibs\Combined\ArrayHandler::arrayGetNextKey($input, $search), + 'Find next key in array' + ); + } } // __END__ diff --git a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Combined/CoreLibsCombinedDateTimeTest.php b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Combined/CoreLibsCombinedDateTimeTest.php index 7bd68d6a..d7efa9b7 100644 --- a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Combined/CoreLibsCombinedDateTimeTest.php +++ b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Combined/CoreLibsCombinedDateTimeTest.php @@ -66,6 +66,34 @@ final class CoreLibsCombinedDateTimeTest extends TestCase ]; } + /** + * date string convert test + * + * @covers ::dateStringFormat + * @dataProvider timestampProvider + * @testdox dateStringFormat $input (microtime $flag) will be $expected [$_dataName] + * + * @param int|float $input + * @param bool $flag + * @param string $expected + * @return void + */ + public function testDateStringFormat( + $input, + bool $flag_show_micro, + bool $flag_micro_as_float, + string $expected + ): void { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::dateStringFormat( + $input, + $flag_show_micro, + $flag_micro_as_float + ) + ); + } + /** * interval for both directions * @@ -74,6 +102,11 @@ final class CoreLibsCombinedDateTimeTest extends TestCase public function intervalProvider(): array { return [ + 'on hour' => [ + 3600, + false, + '1h 0m 0s' + ], 'interval no microtime' => [ 1641515890, false, @@ -82,7 +115,7 @@ final class CoreLibsCombinedDateTimeTest extends TestCase 'interval with microtime' => [ 1641515890, true, - '18999d 0h 38m 10s', + '18999d 0h 38m 10s 0ms', ], 'micro interval no microtime' => [ 1641515890.123456, @@ -92,7 +125,7 @@ final class CoreLibsCombinedDateTimeTest extends TestCase 'micro interval with microtime' => [ 1641515890.123456, true, - '18999d 0h 38m 10s 1235ms', + '18999d 0h 38m 10s 124ms', ], 'negative interval no microtime' => [ -1641515890, @@ -103,27 +136,27 @@ final class CoreLibsCombinedDateTimeTest extends TestCase 'microtime only' => [ 0.123456, true, - '0s 1235ms', + '0s 123ms', ], 'seconds only' => [ 30.123456, true, - '30s 1235ms', + '30s 123ms', ], 'minutes only' => [ 90.123456, true, - '1m 30s 1235ms', + '1m 30s 123ms', ], 'hours only' => [ 3690.123456, true, - '1h 1m 30s 1235ms', + '1h 1m 30s 123ms', ], 'days only' => [ 90090.123456, true, - '1d 1h 1m 30s 1235ms', + '1d 1h 1m 30s 123ms', ], 'already set' => [ '1d 1h 1m 30s 1235ms', @@ -143,6 +176,306 @@ final class CoreLibsCombinedDateTimeTest extends TestCase ]; } + /** + * time seconds convert test + * + * @covers ::timeStringFormat + * @dataProvider intervalProvider + * @testdox timeStringFormat $input (microtime $flag) will be $expected [$_dataName] + * + * @param string|int|float $input + * @param bool $flag + * @param string $expected + * @return void + */ + public function testTimeStringFormat(string|int|float $input, bool $flag, string $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::timeStringFormat($input, $flag) + ); + } + + /** + * interval seconds convert + * + * @covers ::intervalStringFormat + * @dataProvider intervalProvider + * @testdox intervalStringFormat $input (microtime $show_micro) will be $expected [$_dataName] + * + * @param string|int|float $input + * @param bool $show_micro + * @param string $expected + * @return void + */ + public function testIntervalStringFormat(string|int|float $input, bool $show_micro, string $expected): void + { + // we skip string input, that is not allowed + if (is_string($input)) { + $this->assertTrue(true, 'Skip strings'); + return; + } + // invalid values throw exception in default + if ($input == 999999999999999) { + $this->expectException(\LengthException::class); + } + // below is equal to timeStringFormat + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::intervalStringFormat( + $input, + show_microseconds: $show_micro, + show_only_days: true, + skip_zero: false, + skip_last_zero: false, + truncate_nanoseconds: true, + truncate_zero_seconds_if_microseconds: false + ) + ); + } + + /** + * Undocumented function + * + * @return array + */ + public function intervalExtendedProvider(): array + { + return [ + // A + '(60) default value' => [ + [ + 'seconds' => 60, + ], + 'expected' => '1m', + 'exception' => null + ], + '(60) default value, skip_last_zero:false' => [ + [ + 'seconds' => 60, + 'skip_last_zero' => false, + ], + 'expected' => '1m 0s 0ms', + 'exception' => null + ], + // B + '(120.1) default value' => [ + [ + 'seconds' => 120.1, + ], + 'expected' => '2m 100ms', + 'exception' => null + ], + '(120.1) default value, skip_zero:false' => [ + [ + 'seconds' => 120.1, + 'skip_zero' => false, + ], + 'expected' => '2m 0s 100ms', + 'exception' => null + ], + '(120.1) default value, skip_last_zero:false' => [ + [ + 'seconds' => 120.1, + 'skip_last_zero' => false, + ], + 'expected' => '2m 100ms', + 'exception' => null + ], + // C + '(3601) default value' => [ + [ + 'seconds' => 3601, + ], + 'expected' => '1h 1s', + 'exception' => null + ], + '(3601) default value, skip_zero:false' => [ + [ + 'seconds' => 3601, + 'skip_zero' => false, + ], + 'expected' => '1h 0m 1s', + 'exception' => null + ], + '(3601) default value, skip_last_zero:false' => [ + [ + 'seconds' => 3601, + 'skip_last_zero' => false, + ], + 'expected' => '1h 1s 0ms', + 'exception' => null + ], + // TODO create unit tests for ALL edge cases + // CREATE abort tests, simple, all others are handled in exception tests + 'exception: \UnexpectedValueException:1' => [ + [ + 'seconds' => 99999999999999999999999 + ], + 'expected' => null, + 'exception' => [ + 'class' => \UnexpectedValueException::class, + 'code' => 1, + ], + ] + ]; + } + + /** + * test all options for interval conversion + * + * @covers ::intervalStringFormat + * @dataProvider intervalExtendedProvider + * @testdox intervalStringFormat $input will be $expected / $exception [$_dataName] + * + * @param array $parameter_list + * @param string $expected + * @param array $exception + * @return void + */ + public function testExtendedIntervalStringFormat( + array $parameter_list, + ?string $expected, + ?array $exception + ): void { + if ($expected === null && $exception === null) { + $this->assertFalse(true, 'Cannot have expected and exception null in test data'); + } + $parameters = []; + foreach ( + [ + 'seconds' => null, + 'truncate_after' => '', + 'natural_seperator' => false, + 'name_space_seperator' => false, + 'show_microseconds' => true, + 'short_time_name' => true, + 'skip_last_zero' => true, + 'skip_zero' => true, + 'show_only_days' => false, + 'auto_fix_microseconds' => false, + 'truncate_nanoseconds' => false, + 'truncate_zero_seconds_if_microseconds' => true, + ] as $param => $default + ) { + if (empty($parameter_list[$param]) && $default === null) { + $this->assertFalse(true, 'Parameter ' . $param . ' is mandatory '); + } elseif (!isset($parameter_list[$param]) || $parameter_list[$param] === null) { + $parameters[] = $default; + } else { + $parameters[] = $parameter_list[$param]; + } + } + if ($expected !== null) { + $this->assertEquals( + $expected, + call_user_func_array('CoreLibs\Combined\DateTime::intervalStringFormat', $parameters) + ); + } else { + if (empty($exception['class']) || empty($exception['code'])) { + $this->assertFalse(true, 'Exception tests need Exception name and Code'); + } + $this->expectException($exception['class']); + $this->expectExceptionCode($exception['code']); + // if we have a message, must be regex + if (!empty($exception['message'])) { + $this->expectExceptionMessageMatches($exception['message']); + } + call_user_func_array('CoreLibs\Combined\DateTime::intervalStringFormat', $parameters); + } + } + + /** + * Undocumented function + * + * @return array + */ + public function exceptionsIntervalProvider(): array + { + return [ + 'UnexpectedValueException: 1 A' => [ + 'seconds' => 99999999999999999999999, + 'params' => [], + 'exception' => \UnexpectedValueException::class, + 'exception_message' => "/^Seconds value is invalid, too large or more than six decimals: /", + 'excpetion_code' => 1, + ], + 'UnexpectedValueException: 1 B' => [ + 'seconds' => 123.1234567, + 'params' => [], + 'exception' => \UnexpectedValueException::class, + 'exception_message' => "/^Seconds value is invalid, too large or more than six decimals: /", + 'excpetion_code' => 1, + ], + // exception 2 is very likely covered by exception 1 + 'LengthException: 3' => [ + 'seconds' => 999999999999999999, + 'params' => [ + 'show_only_days', + ], + 'exception' => \LengthException::class, + 'exception_message' => "/^Input seconds value is too large for days output: /", + 'excpetion_code' => 3, + ], + 'UnexpectedValueException: 4' => [ + 'seconds' => 1234567, + 'params' => [ + 'truncate_after' + ], + 'exception' => \UnexpectedValueException::class, + 'exception_message' => "/^truncate_after has an invalid value: /", + 'excpetion_code' => 4, + ], + 'UnexpectedValueException: 5' => [ + 'seconds' => 1234567, + 'params' => [ + 'show_only_days:truncate_after' + ], + 'exception' => \UnexpectedValueException::class, + 'exception_message' => + "/^If show_only_days is turned on, the truncate_after cannot be years or months: /", + 'excpetion_code' => 5, + ] + ]; + } + + /** + * Test all exceptions + * + * @covers ::intervalStringFormat + * @dataProvider exceptionsIntervalProvider + * @testdox intervalStringFormat: test Exceptions + * + * @param int|float $seconds + * @param array $params + * @param string $exception + * @param string $exception_message + * @param int $excpetion_code + * @return void + */ + public function testExceptionsIntervalStringFormat( + int|float $seconds, + array $params, + string $exception, + string $exception_message, + int $excpetion_code, + ): void { + $this->expectException($exception); + $this->expectExceptionMessageMatches($exception_message); + $this->expectExceptionCode($excpetion_code); + if (empty($params)) { + \CoreLibs\Combined\DateTime::intervalStringFormat($seconds); + } else { + if (in_array('show_only_days', $params)) { + \CoreLibs\Combined\DateTime::intervalStringFormat($seconds, show_only_days:true); + } elseif (in_array('truncate_after', $params)) { + \CoreLibs\Combined\DateTime::intervalStringFormat($seconds, truncate_after: 'v'); + } elseif (in_array('show_only_days:truncate_after', $params)) { + \CoreLibs\Combined\DateTime::intervalStringFormat($seconds, show_only_days:true, truncate_after: 'y'); + } + } + } + /** * Undocumented function * @@ -203,6 +536,25 @@ final class CoreLibsCombinedDateTimeTest extends TestCase ]; } + /** + * Undocumented function + * + * @covers ::stringToTime + * @dataProvider reverseIntervalProvider + * @testdox stringToTime $input will be $expected [$_dataName] + * + * @param string|int|float $input + * @param string|int|float $expected + * @return void + */ + public function testStringToTime($input, $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::stringToTime($input) + ); + } + /** * Undocumented function * @@ -238,6 +590,25 @@ final class CoreLibsCombinedDateTimeTest extends TestCase ]; } + /** + * Undocumented function + * + * @covers ::checkDate + * @dataProvider dateProvider + * @testdox checkDate $input will be $expected [$_dataName] + * + * @param string $input + * @param bool $expected + * @return void + */ + public function testCheckDate(string $input, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::checkDate($input) + ); + } + /** * Undocumented function * @@ -297,6 +668,25 @@ final class CoreLibsCombinedDateTimeTest extends TestCase ]; } + /** + * Undocumented function + * + * @covers ::checkDateTime + * @dataProvider dateTimeProvider + * @testdox checkDateTime $input will be $expected [$_dataName] + * + * @param string $input + * @param bool $expected + * @return void + */ + public function testCheckDateTime(string $input, bool $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::checkDateTime($input) + ); + } + /** * Undocumented function * @@ -371,6 +761,37 @@ final class CoreLibsCombinedDateTimeTest extends TestCase ]; } + /** + * Undocumented function + * + * @covers ::compareDate + * @dataProvider dateCompareProvider + * @testdox compareDate $input_a compared to $input_b will be $expected [$_dataName] + * + * @param string $input_a + * @param string $input_b + * @param int|bool $expected + * @param string|null $exception + * @param int|null $exception_code + * @return void + */ + public function testCompareDate( + string $input_a, + string $input_b, + int|bool $expected, + ?string $exception, + ?int $exception_code + ): void { + if ($expected === false) { + $this->expectException($exception); + $this->expectExceptionCode($exception_code); + } + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::compareDate($input_a, $input_b) + ); + } + /** * Undocumented function * @@ -466,6 +887,37 @@ final class CoreLibsCombinedDateTimeTest extends TestCase ]; } + /** + * Undocumented function + * + * @covers ::compareDateTime + * @dataProvider dateTimeCompareProvider + * @testdox compareDateTime $input_a compared to $input_b will be $expected [$_dataName] + * + * @param string $input_a + * @param string $input_b + * @param int|bool $expected + * @param string|null $exception + * @param int|null $exception_code + * @return void + */ + public function testCompareDateTime( + string $input_a, + string $input_b, + int|bool $expected, + ?string $exception, + ?int $exception_code + ): void { + if ($expected === false) { + $this->expectException($exception); + $this->expectExceptionCode($exception_code); + } + $this->assertEquals( + $expected, + \CoreLibs\Combined\DateTime::compareDateTime($input_a, $input_b) + ); + } + /** * Undocumented function * @@ -520,214 +972,6 @@ final class CoreLibsCombinedDateTimeTest extends TestCase ]; } - /** - * Undocumented function - * - * @return array - */ - public function dateRangeHasWeekendProvider(): array - { - return [ - 'no weekend' => [ - '2023-07-03', - '2023-07-04', - false - ], - 'start weekend sat' => [ - '2023-07-01', - '2023-07-04', - true - ], - 'start weekend sun' => [ - '2023-07-02', - '2023-07-04', - true - ], - 'end weekend sat' => [ - '2023-07-03', - '2023-07-08', - true - ], - 'end weekend sun' => [ - '2023-07-03', - '2023-07-09', - true - ], - 'long period > 6 days' => [ - '2023-07-03', - '2023-07-27', - true - ] - ]; - } - - /** - * date string convert test - * - * @covers ::dateStringFormat - * @dataProvider timestampProvider - * @testdox dateStringFormat $input (microtime $flag) will be $expected [$_dataName] - * - * @param int|float $input - * @param bool $flag - * @param string $expected - * @return void - */ - public function testDateStringFormat( - $input, - bool $flag_show_micro, - bool $flag_micro_as_float, - string $expected - ): void { - $this->assertEquals( - $expected, - \CoreLibs\Combined\DateTime::dateStringFormat( - $input, - $flag_show_micro, - $flag_micro_as_float - ) - ); - } - - /** - * interval convert test - * - * @covers ::timeStringFormat - * @dataProvider intervalProvider - * @testdox timeStringFormat $input (microtime $flag) will be $expected [$_dataName] - * - * @param int|float $input - * @param bool $flag - * @param string $expected - * @return void - */ - public function testTimeStringFormat($input, bool $flag, string $expected): void - { - $this->assertEquals( - $expected, - \CoreLibs\Combined\DateTime::timeStringFormat($input, $flag) - ); - } - - /** - * Undocumented function - * - * @covers ::stringToTime - * @dataProvider reverseIntervalProvider - * @testdox stringToTime $input will be $expected [$_dataName] - * - * @param string|int|float $input - * @param string|int|float $expected - * @return void - */ - public function testStringToTime($input, $expected): void - { - $this->assertEquals( - $expected, - \CoreLibs\Combined\DateTime::stringToTime($input) - ); - } - - /** - * Undocumented function - * - * @covers ::checkDate - * @dataProvider dateProvider - * @testdox checkDate $input will be $expected [$_dataName] - * - * @param string $input - * @param bool $expected - * @return void - */ - public function testCheckDate(string $input, bool $expected): void - { - $this->assertEquals( - $expected, - \CoreLibs\Combined\DateTime::checkDate($input) - ); - } - - /** - * Undocumented function - * - * @covers ::checkDateTime - * @dataProvider dateTimeProvider - * @testdox checkDateTime $input will be $expected [$_dataName] - * - * @param string $input - * @param bool $expected - * @return void - */ - public function testCheckDateTime(string $input, bool $expected): void - { - $this->assertEquals( - $expected, - \CoreLibs\Combined\DateTime::checkDateTime($input) - ); - } - - /** - * Undocumented function - * - * @covers ::compareDate - * @dataProvider dateCompareProvider - * @testdox compareDate $input_a compared to $input_b will be $expected [$_dataName] - * - * @param string $input_a - * @param string $input_b - * @param int|bool $expected - * @param string|null $exception - * @param int|null $exception_code - * @return void - */ - public function testCompareDate( - string $input_a, - string $input_b, - int|bool $expected, - ?string $exception, - ?int $exception_code - ): void { - if ($expected === false) { - $this->expectException($exception); - $this->expectExceptionCode($exception_code); - } - $this->assertEquals( - $expected, - \CoreLibs\Combined\DateTime::compareDate($input_a, $input_b) - ); - } - - /** - * Undocumented function - * - * @covers ::compareDateTime - * @dataProvider dateTimeCompareProvider - * @testdox compareDateTime $input_a compared to $input_b will be $expected [$_dataName] - * - * @param string $input_a - * @param string $input_b - * @param int|bool $expected - * @param string|null $exception - * @param int|null $exception_code - * @return void - */ - public function testCompareDateTime( - string $input_a, - string $input_b, - int|bool $expected, - ?string $exception, - ?int $exception_code - ): void { - if ($expected === false) { - $this->expectException($exception); - $this->expectExceptionCode($exception_code); - } - $this->assertEquals( - $expected, - \CoreLibs\Combined\DateTime::compareDateTime($input_a, $input_b) - ); - } - /** * Undocumented function * @@ -906,6 +1150,47 @@ final class CoreLibsCombinedDateTimeTest extends TestCase ); } + /** + * Undocumented function + * + * @return array + */ + public function dateRangeHasWeekendProvider(): array + { + return [ + 'no weekend' => [ + '2023-07-03', + '2023-07-04', + false + ], + 'start weekend sat' => [ + '2023-07-01', + '2023-07-04', + true + ], + 'start weekend sun' => [ + '2023-07-02', + '2023-07-04', + true + ], + 'end weekend sat' => [ + '2023-07-03', + '2023-07-08', + true + ], + 'end weekend sun' => [ + '2023-07-03', + '2023-07-09', + true + ], + 'long period > 6 days' => [ + '2023-07-03', + '2023-07-27', + true + ] + ]; + } + /** * Undocumented function * diff --git a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Convert/CoreLibsConvertByteTest.php b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Convert/CoreLibsConvertByteTest.php index bf49af3b..95838889 100644 --- a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Convert/CoreLibsConvertByteTest.php +++ b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Convert/CoreLibsConvertByteTest.php @@ -253,7 +253,8 @@ final class CoreLibsConvertByteTest extends TestCase */ public function testHumanReadableByteFormatException(int $flag): void { - $this->expectException(\Exception::class); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(1); \CoreLibs\Convert\Byte::humanReadableByteFormat(12, $flag); } @@ -272,7 +273,8 @@ final class CoreLibsConvertByteTest extends TestCase */ public function testStringByteFormatException(int $flag): void { - $this->expectException(\Exception::class); + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionCode(1); \CoreLibs\Convert\Byte::stringByteFormat(12, $flag); } } diff --git a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Convert/CoreLibsConvertStringsTest.php b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Convert/CoreLibsConvertStringsTest.php index 90415d20..0b2f79a5 100644 --- a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Convert/CoreLibsConvertStringsTest.php +++ b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Convert/CoreLibsConvertStringsTest.php @@ -256,6 +256,80 @@ final class CoreLibsConvertStringsTest extends TestCase $output ); } + + /** + * provider for testStripMultiplePathSlashes + * + * @return array + */ + public function stripMultiplePathSlashesProvider(): array + { + return [ + 'no slahses' => [ + 'input' => 'string_abc', + 'expected' => 'string_abc', + ], + 'one slash' => [ + 'input' => 'some/foo', + 'expected' => 'some/foo', + ], + 'two slashes' => [ + 'input' => 'some//foo', + 'expected' => 'some/foo', + ], + 'three slashes' => [ + 'input' => 'some///foo', + 'expected' => 'some/foo', + ], + 'slashes in front' => [ + 'input' => '/foo', + 'expected' => '/foo', + ], + 'two slashes in front' => [ + 'input' => '//foo', + 'expected' => '/foo', + ], + 'thee slashes in front' => [ + 'input' => '///foo', + 'expected' => '/foo', + ], + 'slashes in back' => [ + 'input' => 'foo/', + 'expected' => 'foo/', + ], + 'two slashes in back' => [ + 'input' => 'foo//', + 'expected' => 'foo/', + ], + 'thee slashes in back' => [ + 'input' => 'foo///', + 'expected' => 'foo/', + ], + 'multiple slashes' => [ + 'input' => '/foo//bar///string/end_times', + 'expected' => '/foo/bar/string/end_times', + ] + ]; + } + + /** + * test multiple slashes clean up + * + * @covers ::stripMultiplePathSlashes + * @dataProvider stripMultiplePathSlashesProvider + * @testdox stripMultiplePathSlashes $input will be $expected [$_dataName] + * + * @param string $input + * @param string $expected + * @return void + */ + public function testStripMultiplePathSlashes(string $input, string $expected): void + { + $this->assertEquals( + $expected, + \CoreLibs\Convert\Strings::stripMultiplePathSlashes($input) + ); + } } // __END__ 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 d1d598f3..93703db8 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 @@ -232,7 +232,7 @@ final class CoreLibsDBIOTest extends TestCase $this->assertEquals( $error, $last_error, - 'Assert query warning' + 'Assert query error' ); return [$last_warning, $last_error]; } @@ -251,8 +251,6 @@ final class CoreLibsDBIOTest extends TestCase */ public function testDbVersion(): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log @@ -276,8 +274,6 @@ final class CoreLibsDBIOTest extends TestCase */ public function testDbVersionNumeric(): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log @@ -306,8 +302,6 @@ final class CoreLibsDBIOTest extends TestCase */ public function testDbVersionInfoParameters(): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log @@ -365,8 +359,6 @@ final class CoreLibsDBIOTest extends TestCase */ public function testDbVersionInfo(string $parameter, string $expected): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log @@ -1592,8 +1584,6 @@ final class CoreLibsDBIOTest extends TestCase string $error, bool $run_many_times = false ): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log @@ -1832,8 +1822,6 @@ final class CoreLibsDBIOTest extends TestCase string $error, string $insert_data ): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log @@ -2002,8 +1990,6 @@ final class CoreLibsDBIOTest extends TestCase string $error, string $insert_data ): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log @@ -3069,8 +3055,6 @@ final class CoreLibsDBIOTest extends TestCase string $error, string $insert_data ): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log @@ -3465,7 +3449,7 @@ final class CoreLibsDBIOTest extends TestCase $read_query, null, null, - // + // warning: 20 true, '20', '', // 'result', '', '', @@ -3482,6 +3466,31 @@ final class CoreLibsDBIOTest extends TestCase 'returning_id' => false, ], ], + // prepare with different statement name + 'prepare query with same statement name, different query' => [ + 'double_error', + $read_query, + // primary key + null, + // arguments (none) + null, + // expected return false, warning: no, error: 26 + false, '', '26', + // return expected, warning, error + '', '', '', + // dummy query for second prepare with wrong query + $read_query . ' WHERE uid = $3', + [], + // + $insert_query, + // + [ + 'pk_name' => '', + 'count' => 0, + 'query' => 'SELECT row_int, uid FROM table_with_primary_key', + 'returning_id' => false, + ], + ], // insert wrong data count compared to needed (execute 23) 'wrong parmeter count' => [ 'wrong_param_count', @@ -3554,8 +3563,6 @@ final class CoreLibsDBIOTest extends TestCase string $insert_data, array $prepare_cursor, ): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log @@ -3575,6 +3582,9 @@ final class CoreLibsDBIOTest extends TestCase $db->dbPrepare($stm_name, $query) : $db->dbPrepare($stm_name, $query, $pk_name); } + if ($error_prepare == '26') { + $prepare_result = $db->dbPrepare($stm_name, $expected_data_query); + } // if result type, or if forced bool if (is_string($expected_prepare) && $expected_prepare == 'result') { // if PHP or newer, must be Object PgSql\Result @@ -3597,66 +3607,68 @@ final class CoreLibsDBIOTest extends TestCase // for non fail prepare test exec // check test result - $execute_result = $query_data === null ? - $db->dbExecute($stm_name) : - $db->dbExecute($stm_name, $query_data); - if ($expected_execute == 'result') { - // if PHP or newer, must be Object PgSql\Result - $this->assertIsObject( - $execute_result - ); - // also check that this is correct instance type - $this->assertInstanceOf( - 'PgSql\Result', - $execute_result - ); - // if this is an select use dbFetchArray to get data and test - } else { - $this->assertEquals( - $expected_execute, - $execute_result - ); - } - // error/warning check - $this->subAssertErrorTest($db, $warning_execute, $error_execute); - // now check test result if expected return is result - if ( - $expected_execute == 'result' && - !empty($expected_data_query) - ) { - // $expected_data_query - // $expected_data - $rows = $db->dbReturnArray($expected_data_query); - $this->assertEquals( - $expected_data, - $rows - ); - } - if ( - $expected_execute == 'result' && - $execute_result !== false && - empty($expected_data_query) && - count($expected_data) - ) { - // compare previously read data to compare data - $compare_data = []; - // read in the query data - while (is_array($row = $db->dbFetchArray($execute_result, true))) { - $compare_data[] = $row; + if (!$error_prepare) { + $execute_result = $query_data === null ? + $db->dbExecute($stm_name) : + $db->dbExecute($stm_name, $query_data); + if ($expected_execute == 'result') { + // if PHP or newer, must be Object PgSql\Result + $this->assertIsObject( + $execute_result + ); + // also check that this is correct instance type + $this->assertInstanceOf( + 'PgSql\Result', + $execute_result + ); + // if this is an select use dbFetchArray to get data and test + } else { + $this->assertEquals( + $expected_execute, + $execute_result + ); + } + // error/warning check + $this->subAssertErrorTest($db, $warning_execute, $error_execute); + // now check test result if expected return is result + if ( + $expected_execute == 'result' && + !empty($expected_data_query) + ) { + // $expected_data_query + // $expected_data + $rows = $db->dbReturnArray($expected_data_query); + $this->assertEquals( + $expected_data, + $rows + ); + } + if ( + $expected_execute == 'result' && + $execute_result !== false && + empty($expected_data_query) && + count($expected_data) + ) { + // compare previously read data to compare data + $compare_data = []; + // read in the query data + while (is_array($row = $db->dbFetchArray($execute_result, true))) { + $compare_data[] = $row; + } + $this->assertEquals( + $expected_data, + $compare_data + ); } - $this->assertEquals( - $expected_data, - $compare_data - ); - } - // check dbGetPrepareCursorValue - foreach (['pk_name', 'count', 'query', 'returning_id'] as $key) { - $this->assertEquals( - $prepare_cursor[$key], - $db->dbGetPrepareCursorValue($stm_name, $key), - 'Prepared cursor: ' . $key . ': failed assertion' - ); + // check dbGetPrepareCursorValue + foreach (['pk_name', 'count', 'query', 'returning_id'] as $key) { + $this->assertEquals( + $prepare_cursor[$key], + $db->dbGetPrepareCursorValue($stm_name, $key), + 'Prepared cursor: ' . $key . ': failed assertion' + ); + } } // reset all data @@ -3844,8 +3856,6 @@ final class CoreLibsDBIOTest extends TestCase string $expected_get_var, string $expected_get_db ): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config[$connection], self::$log @@ -3910,7 +3920,10 @@ final class CoreLibsDBIOTest extends TestCase // 'main::run::run::run::run::run::run::run::runBare::runTest::testDbErrorHandling::dbSetMaxQueryCall 'source' => "/^(include::)?main::(run::)+runBare::runTest::testDbErrorHandling::dbSetMaxQueryCall$/", 'pg_error' => '', - 'msg' => '', + 'message' => '', + 'context' => [ + 'max_calls' => 0 + ] ] ], 'trigger warning' => [ @@ -3943,8 +3956,6 @@ final class CoreLibsDBIOTest extends TestCase string $error_id, array $expected_history ): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log @@ -3970,7 +3981,7 @@ final class CoreLibsDBIOTest extends TestCase foreach ($expected_history as $key => $value) { // check if starts with / because this is regex (timestamp) // if (substr($expected_2, 0, 1) == '/) { - if (strpos($value, '/') === 0) { + if (!is_array($value) && strpos($value, '/') === 0) { // this is regex $this->assertMatchesRegularExpression( $value, @@ -4058,8 +4069,6 @@ final class CoreLibsDBIOTest extends TestCase bool $expected_set_flag, string $expected_get_encoding ): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config[$connection], self::$log @@ -4141,8 +4150,6 @@ final class CoreLibsDBIOTest extends TestCase ?string $encoding_php, string $text ): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config[$connection], self::$log @@ -4272,8 +4279,6 @@ final class CoreLibsDBIOTest extends TestCase string $table, string $primary_key ): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log @@ -4330,7 +4335,7 @@ final class CoreLibsDBIOTest extends TestCase // NOTE if there are different INSERTS before the primary keys // will not match anymore. Must be updated by hand // IMPORTANT: if this is stand alone the primary key will not match and fail - $table_with_primary_key_id = 68; + $table_with_primary_key_id = 70; // 0: query + returning // 1: params // 1: pk name for db exec @@ -4530,8 +4535,6 @@ final class CoreLibsDBIOTest extends TestCase array|string|int|null $expected_ret_ext, array $expected_ret_arr ): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log @@ -4875,8 +4878,6 @@ final class CoreLibsDBIOTest extends TestCase array $expected_col_names, array $expected_col_types ): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log @@ -5030,6 +5031,147 @@ final class CoreLibsDBIOTest extends TestCase $db->dbClose(); } + // query placeholder convert + + public function queryPlaceholderReplaceProvider(): array + { + // WHERE row_varchar = $1 + return [ + 'select, no change' => [ + 'query' => << [], + 'found' => 0, + 'expected_query' => '', + 'expected_params' => [], + ], + 'select, params ?' => [ + 'query' => << ['string a'], + 'found' => 1, + 'expected_query' => << ['string a'], + ], + 'select, params :' => [ + 'query' => << [':row_varchar' => 'string a'], + 'found' => 1, + 'expected_query' => << ['string a'], + ] + ]; + } + + /** + * test query string with placeholders convert + * + * @dataProvider queryPlaceholderReplaceProvider + * @testdox Query replacement test [$_dataName] + * + * @param string $query + * @param array $params + * @param string $expected_query + * @param array $expected_params + * @return void + */ + public function testQueryPlaceholderReplace( + string $query, + array $params, + int $expected_found, + string $expected_query, + array $expected_params + ): void { + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + $db->dbSetConvertPlaceholder(true); + // + if ($db->dbCheckQueryForSelect($query)) { + $res = $db->dbReturnRowParams($query, $params); + $converted = $db->dbGetPlaceholderConverted(); + } else { + $db->dbExecParams($query, $params); + $converted = $db->dbGetPlaceholderConverted(); + } + $this->assertEquals( + $expected_found, + $converted['found'], + 'Found not equal' + ); + $this->assertEquals( + $expected_query, + $converted['query'], + 'Query not equal' + ); + $this->assertEquals( + $expected_params, + $converted['params'], + 'Params not equal' + ); + } + + /** + * test exception for placeholder convert + * -> internally converted to error + * + * @testdox Query Replace error tests + * + * @return void + */ + public function testQueryPlaceholderReplaceException(): void + { + $db = new \CoreLibs\DB\IO( + self::$db_config['valid'], + self::$log + ); + $db->dbSetConvertPlaceholder(true); + $db->dbExecParams( + <<assertEquals( + 200, + $db->dbGetLastError() + ); + + // catch unset, for :names + $db->dbExecParams( + << 'a', ':bname' => 'b'] + ); + $this->assertEquals( + 210, + $db->dbGetLastError() + ); + + // TODO: other way around for to pdo + } + // TODO implement below checks // - complex write sets // dbWriteData, dbWriteDataExt @@ -5158,8 +5300,6 @@ final class CoreLibsDBIOTest extends TestCase string $warning_final, string $error_final ): void { - // self::$log->setLogLevelAll('debug', true); - // self::$log->setLogLevelAll('print', true); $db = new \CoreLibs\DB\IO( self::$db_config['valid'], self::$log diff --git a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Debug/CoreLibsDebugSupportTest.php b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Debug/CoreLibsDebugSupportTest.php index ab74df7d..74ed67ed 100644 --- a/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Debug/CoreLibsDebugSupportTest.php +++ b/www/vendor/egrajp/corelibs-composer-all/test/phpunit/Debug/CoreLibsDebugSupportTest.php @@ -513,7 +513,7 @@ final class CoreLibsDebugSupportTest extends TestCase public function testGetCallerMethodList(array $expected): void { $compare = Support::getCallerMethodList(); - // 10: legact + // 10: legacy // 11: direct // 12: full call switch (count($compare)) { @@ -571,6 +571,31 @@ final class CoreLibsDebugSupportTest extends TestCase } } + /** + * Undocumented function + * + * @cover ::getCallStack + * @testdox getCallStack check if it returns data [$_dataName] + * + * @return void + */ + public function testGetCallStack(): void + { + $call_stack = Support::getCallStack(); + // print "Get CALL: " . print_r(Support::getCallStack(), true) . "\n"; + if ($call_stack < 8) { + $this->assertFalse(true, 'getCallStack too low: 8'); + } else { + $this->assertTrue(true, 'getCallSteck ok'); + } + // just test top entry + $first = array_shift($call_stack); + $this->assertStringEndsWith( + ':tests\CoreLibsDebugSupportTest->testGetCallStack', + $first, + ); + } + /** * test the lowest one (one above base) * diff --git a/www/vendor/egrajp/smarty-extended/.gitignore b/www/vendor/egrajp/smarty-extended/.gitignore index 22d0d82f..7579f743 100644 --- a/www/vendor/egrajp/smarty-extended/.gitignore +++ b/www/vendor/egrajp/smarty-extended/.gitignore @@ -1 +1,2 @@ vendor +composer.lock diff --git a/www/vendor/egrajp/smarty-extended/ReadMe.md b/www/vendor/egrajp/smarty-extended/ReadMe.md new file mode 100644 index 00000000..ded4fcec --- /dev/null +++ b/www/vendor/egrajp/smarty-extended/ReadMe.md @@ -0,0 +1,46 @@ +# Composer package from Smarty Extended + +This is an updated package for smarty\smarty + +Adds: + +- translation block +- label and pos for checkboxes and radio buttons + +For local install only + +## Setup from central composer + +Setup from gitea internal servers + +```sh +composer config repositories.git.egplusww.jp.Composer composer https://git.egplusww.jp/api/packages/Composer/composer +``` + +Alternative setup composer local zip file repot: +`composer config repositories.composer.egplusww.jp composer http://composer.egplusww.jp` + +## Install package + +`composer require egrajp/smarty-extended:^4.3` + +## How to update + +1) update the original composer for ^4.3 +2) copy over the src/sysplugins and all base files in src/ +3) check either function.html_checkboxes.php and function.html_options.php have changed +4) copy src/plugins except the above two files, be sure to keep the block.t.php and function_popup*.php +5) Create new release version as official relase number + +## Updated files (different from master) + +### New + +`src/plugins/block.t.php` +`src/plugins/function_popup.php` +`src/plugins/function_popup.init.php` + +### Changed + +`src/plugins/function.html_checkboxes.php` +`src/plugins/function.html_options.php` diff --git a/www/vendor/egrajp/smarty-extended/composer.json b/www/vendor/egrajp/smarty-extended/composer.json index 13fc8762..dfc944f8 100644 --- a/www/vendor/egrajp/smarty-extended/composer.json +++ b/www/vendor/egrajp/smarty-extended/composer.json @@ -8,9 +8,9 @@ "homepage": "https://github.com/smarty-php/smarty/", "license": "LGPL-3.0", "autoload": { - "psr-4": { - "Smarty\\": "src/" - } + "classmap": [ + "src/" + ] }, "authors": [ { diff --git a/www/vendor/egrajp/smarty-extended/publish/.gitignore b/www/vendor/egrajp/smarty-extended/publish/.gitignore new file mode 100644 index 00000000..81a38be6 --- /dev/null +++ b/www/vendor/egrajp/smarty-extended/publish/.gitignore @@ -0,0 +1 @@ +.env* diff --git a/www/vendor/egrajp/smarty-extended/publish/last.published b/www/vendor/egrajp/smarty-extended/publish/last.published new file mode 100644 index 00000000..eda862a9 --- /dev/null +++ b/www/vendor/egrajp/smarty-extended/publish/last.published @@ -0,0 +1 @@ +4.3.4 diff --git a/www/vendor/egrajp/smarty-extended/publish/package-download/.gitignore b/www/vendor/egrajp/smarty-extended/publish/package-download/.gitignore new file mode 100644 index 00000000..c4c4ffc6 --- /dev/null +++ b/www/vendor/egrajp/smarty-extended/publish/package-download/.gitignore @@ -0,0 +1 @@ +*.zip diff --git a/www/vendor/egrajp/smarty-extended/publish/publish.sh b/www/vendor/egrajp/smarty-extended/publish/publish.sh new file mode 100755 index 00000000..7095275c --- /dev/null +++ b/www/vendor/egrajp/smarty-extended/publish/publish.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash + +BASE_FOLDER=$(dirname $(readlink -f $0))"/"; +PACKAGE_DOWNLOAD="${BASE_FOLDER}package-download/"; +if [ ! -d "${PACKAGE_DOWNLOAD}" ]; then + mkdir "${PACKAGE_DOWNLOAD}"; +fi; +VERSION=$(git tag --list | sort -V | tail -n1 | sed -e "s/^v//"); +file_last_published="${BASE_FOLDER}last.published"; +go_flag="$1"; + +if [ -z "${VERSION}" ]; then + echo "Version must be set in the form x.y.z without any leading characters"; + exit; +fi; +# compare version, if different or newer, deploy +if [ -f "${file_last_published}" ]; then + LAST_PUBLISHED_VERSION=$(cat ${file_last_published}); + if $(dpkg --compare-versions "${VERSION}" le "${LAST_PUBLISHED_VERSION}"); then + echo "git tag version ${VERSION} is not newer than previous published version ${LAST_PUBLISHED_VERSION}"; + exit; + fi; +fi; + +# read in the .env.deploy file and we must have +# GITEA_UPLOAD_FILENAME +# GITLAB_USER +# GITLAB_TOKEN +# GITLAB_URL +# GITEA_USER +# GITEA_DEPLOY_TOKEN +# GITEA_URL_DL +# GITEA_URL_PUSH +if [ ! -f "${BASE_FOLDER}.env.deploy" ]; then + echo "Deploy enviroment file .env.deploy is missing"; + exit; +fi; +set -o allexport; +cd ${BASE_FOLDER}; +source .env.deploy; +cd -; +set +o allexport; + +if [ "${go_flag}" != "go" ]; then + echo "No go flag given"; + echo "Would publish ${VERSION}"; + echo "[END]"; + exit; +fi; + +echo "[START]"; +# gitea +if [ ! -z "${GITEA_UPLOAD_FILENAME}" ] && + [ ! -z "${GITEA_URL_DL}" ] && [ ! -z "${GITEA_URL_PUSH}" ] && + [ ! -z "${GITEA_USER}" ] && [ ! -z "${GITEA_TOKEN}" ]; then + curl -LJO \ + --output-dir "${PACKAGE_DOWNLOAD}" \ + ${GITEA_URL_DL}/v${VERSION}.zip; + # echo "curl -LJO \ + # --output-dir "${PACKAGE_DOWNLOAD}" \ + # ${GITEA_URL_DL}/v${VERSION}.zip;" + curl --user ${GITEA_USER}:${GITEA_TOKEN} \ + --upload-file "${PACKAGE_DOWNLOAD}${GITEA_UPLOAD_FILENAME}-v${VERSION}.zip" \ + ${GITEA_URL_PUSH}?version=${VERSION}; + # echo "curl --user ${GITEA_USER}:${GITEA_TOKEN} \ + # --upload-file "${PACKAGE_DOWNLOAD}${GITEA_UPLOAD_FILENAME}-v${VERSION}.zip" \ + # ${GITEA_URL_PUSH}?version=${VERSION};" + echo "${VERSION}" > "${file_last_published}"; +else + echo "Missing either GITEA_UPLOAD_FILENAME, GITEA_URL_DL, GITEA_URL_PUSH, GITEA_USER or GITEA_TOKEN environment variable"; +fi; + +# gitlab +if [ ! -z "${GITLAB_URL}" ] && [ ! -z "${GITLAB_DEPLOY_TOKEN}" ]; then + curl --data tag=v${VERSION} \ + --header "Deploy-Token: ${GITLAB_DEPLOY_TOKEN}" \ + "${GITLAB_URL}"; + curl --data branch=master \ + --header "Deploy-Token: ${GITLAB_DEPLOY_TOKEN}" \ + "${GITLAB_URL}"; + echo "${VERSION}" > "${file_last_published}"; +else + echo "Missing GITLAB_DEPLOY_TOKEN environment variable"; +fi; +echo ""; +echo "[DONE]"; + +# __END__ diff --git a/www/vendor/egrajp/smarty-extended/src/Smarty.class.php b/www/vendor/egrajp/smarty-extended/src/Smarty.class.php index 5351b579..0a47c835 100644 --- a/www/vendor/egrajp/smarty-extended/src/Smarty.class.php +++ b/www/vendor/egrajp/smarty-extended/src/Smarty.class.php @@ -107,7 +107,7 @@ class Smarty extends Smarty_Internal_TemplateBase /** * smarty version */ - const SMARTY_VERSION = '4.3.0'; + const SMARTY_VERSION = '4.4.1'; /** * define variable scopes */ diff --git a/www/vendor/egrajp/smarty-extended/src/debug.tpl b/www/vendor/egrajp/smarty-extended/src/debug.tpl index 4f82a582..cd932566 100644 --- a/www/vendor/egrajp/smarty-extended/src/debug.tpl +++ b/www/vendor/egrajp/smarty-extended/src/debug.tpl @@ -167,9 +167,7 @@ {/capture} diff --git a/www/vendor/egrajp/smarty-extended/src/plugins/function.math.php b/www/vendor/egrajp/smarty-extended/src/plugins/function.math.php index f9cf67fe..34912d23 100644 --- a/www/vendor/egrajp/smarty-extended/src/plugins/function.math.php +++ b/www/vendor/egrajp/smarty-extended/src/plugins/function.math.php @@ -67,7 +67,7 @@ function smarty_function_math($params, $template) $equation = preg_replace('/\s+/', '', $equation); // Adapted from https://www.php.net/manual/en/function.eval.php#107377 - $number = '(?:\d+(?:[,.]\d+)?|pi|π)'; // What is a number + $number = '-?(?:\d+(?:[,.]\d+)?|pi|π)'; // What is a number $functionsOrVars = '((?:0x[a-fA-F0-9]+)|([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*))'; $operators = '[,+\/*\^%-]'; // Allowed math operators $regexp = '/^(('.$number.'|'.$functionsOrVars.'|('.$functionsOrVars.'\s*\((?1)*\)|\((?1)*\)))(?:'.$operators.'(?1))?)+$/'; diff --git a/www/vendor/egrajp/smarty-extended/src/plugins/modifier.escape.php b/www/vendor/egrajp/smarty-extended/src/plugins/modifier.escape.php index 11e44682..e168679c 100644 --- a/www/vendor/egrajp/smarty-extended/src/plugins/modifier.escape.php +++ b/www/vendor/egrajp/smarty-extended/src/plugins/modifier.escape.php @@ -115,7 +115,9 @@ function smarty_modifier_escape($string, $esc_type = 'html', $char_set = null, $ // see https://html.spec.whatwg.org/multipage/scripting.html#restrictions-for-contents-of-script-elements '