Compare commits

...

47 Commits

Author SHA1 Message Date
Clemens Schwaighofer
dbbe6c263b RandomKey class update, add methods to strings, json, array class, fix phpstan errors in other classes, add tests 2025-06-05 18:13:59 +09:00
Clemens Schwaighofer
ec1fb72ba9 Release: v9.33.0 2025-04-22 11:14:19 +09:00
Clemens Schwaighofer
e0003beae2 phpstan fixes for DB IO, add array key modify method that keeps value 2025-04-22 11:11:55 +09:00
Clemens Schwaighofer
bacd31d0e3 Release: v9.32.1 2025-04-16 17:46:03 +09:00
Clemens Schwaighofer
ae125ea45e DB IO cache reset flag add to ignore warning, and ignore cache reset warning in Login class 2025-04-16 17:44:42 +09:00
Clemens Schwaighofer
94eb1c7697 Release: v9.32.0 2025-04-15 18:43:49 +09:00
Clemens Schwaighofer
aff4944ffd ACL Login update with pages in acl array and file in allowed access list lookup 2025-04-15 18:42:55 +09:00
Clemens Schwaighofer
1a4c8e188f Release: v9.31.1 2025-04-15 17:49:25 +09:00
Clemens Schwaighofer
c603922fca Bug fix in edit user control array for custom error example data missing 2025-04-15 17:48:35 +09:00
Clemens Schwaighofer
7ac13c2ba6 Release: v9.31.0 2025-04-09 11:43:26 +09:00
Clemens Schwaighofer
1c66ee34a1 DB IO rename dbGetQueryHash to dbBuildQueryHash, store last query hash 2025-04-09 11:40:07 +09:00
Clemens Schwaighofer
2e101d55d2 Release: v9.30.0 2025-04-07 19:54:30 +09:00
Clemens Schwaighofer
4b699d753d DB placeholder comment fix, add hash hmac to Hashlib 2025-04-07 19:52:01 +09:00
Clemens Schwaighofer
254a0e4802 Release: v9.29.0 2025-04-04 16:03:34 +09:00
Clemens Schwaighofer
82f35535ae Class Hash update 2025-04-04 15:59:59 +09:00
Clemens Schwaighofer
c41796a478 Release: v9.28.1 2025-04-01 11:29:56 +09:00
Clemens Schwaighofer
a310fab3ee Release: v9.28.0 2025-04-01 11:28:58 +09:00
Clemens Schwaighofer
9914815285 htmlent add encoding, date combined add wrapper for calc date interval for numeric and named index return 2025-04-01 11:27:57 +09:00
Clemens Schwaighofer
969467fa15 Release: v9.27.0 2025-02-17 11:27:12 +09:00
Clemens Schwaighofer
f4dd78fff2 Merge branch 'development' 2025-02-17 11:26:16 +09:00
Clemens Schwaighofer
ba5e78e839 Config errors throw exception, bug fixes for date interval, eslint update, Login ACL number to unit detail 2025-02-17 11:25:36 +09:00
Clemens Schwaighofer
1a5ee2e16d Release: v9.26.8 2025-01-20 10:51:05 +09:00
Clemens Schwaighofer
e1d9985ec8 DB IO cache reset query not found is warning and not error 2025-01-20 10:50:07 +09:00
Clemens Schwaighofer
2316c151ac Release: v9.26.7.1 2025-01-17 18:12:33 +09:00
Clemens Schwaighofer
8ff8aa195b Merge branch 'master' into development 2025-01-17 18:10:07 +09:00
Clemens Schwaighofer
f176d12a1e Release: v9.26.7 2025-01-17 18:03:29 +09:00
Clemens Schwaighofer
f974b15f78 Smarty Extended update 2025-01-17 18:01:21 +09:00
Clemens Schwaighofer
91fad09367 DB IO prepare query fix for INSERT types 2025-01-17 17:56:43 +09:00
Clemens Schwaighofer
e8fe1feda5 Release: v9.26.6.1 2025-01-17 14:46:42 +09:00
Clemens Schwaighofer
23fd78e5c8 ACL Login depricate edit access id check 2025-01-17 14:45:54 +09:00
Clemens Schwaighofer
6cdede2997 Release: v9.26.6 2025-01-17 14:40:21 +09:00
Clemens Schwaighofer
ace02b14d8 Merge branch 'development' 2025-01-17 14:39:05 +09:00
Clemens Schwaighofer
58e916d314 Fix ACL Login edit access cuid <-> id lookup 2025-01-17 14:38:41 +09:00
Clemens Schwaighofer
4f6d85f4da Release: v9.26.5 2025-01-17 12:52:30 +09:00
Clemens Schwaighofer
cd45590a72 ACL Login add lookup edit access id to cuid 2025-01-17 12:51:25 +09:00
Clemens Schwaighofer
4d42da201c Release: v9.26.4 2025-01-17 10:06:57 +09:00
Clemens Schwaighofer
e310cb626a Logging file block separator character change, deprecated php 8.4 helpers 2025-01-17 10:05:54 +09:00
Clemens Schwaighofer
c04c71d755 Release: v9.26.3 2025-01-16 14:52:16 +09:00
Clemens Schwaighofer
9fc40a6629 ACL Login add edit access id to acl array 2025-01-16 14:51:29 +09:00
Clemens Schwaighofer
6362e7f2f0 Release: v9.26.2 2025-01-16 14:40:08 +09:00
Clemens Schwaighofer
50dfc10d31 Merge branch 'development' 2025-01-16 14:38:59 +09:00
Clemens Schwaighofer
24077e483f ACL Login add edit access id to cuid lookup 2025-01-16 14:38:49 +09:00
Clemens Schwaighofer
6585c6bfef Release: v9.26.1 2025-01-16 14:11:41 +09:00
Clemens Schwaighofer
f180046283 ACL Login unit detail info update, deprecated message fix 2025-01-16 14:10:46 +09:00
Clemens Schwaighofer
b64d0ce5f0 Release: v9.26.0 2025-01-16 10:27:00 +09:00
Clemens Schwaighofer
bab8460f80 PHP 8.4 compatible release 2025-01-16 10:25:58 +09:00
Clemens Schwaighofer
a092217201 Release: v9.25.3 2024-12-24 12:52:33 +09:00
48 changed files with 5484 additions and 625 deletions

View File

@@ -25,7 +25,7 @@
"phpstan/phpdoc-parser": "^2.0", "phpstan/phpdoc-parser": "^2.0",
"phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-deprecation-rules": "^2.0",
"phan/phan": "^5.4", "phan/phan": "^5.4",
"egrajp/smarty-extended": "^4.3", "egrajp/smarty-extended": "^5.4",
"gullevek/dotenv": "dev-master", "gullevek/dotenv": "dev-master",
"phpunit/phpunit": "^9" "phpunit/phpunit": "^9"
}, },

View File

@@ -22,6 +22,9 @@ parameters:
# - vendor # - vendor
# ignore errores with # ignore errores with
ignoreErrors: ignoreErrors:
-
message: '#Expression in empty\(\) is not falsy.#'
path: %currentWorkingDirectory%/src/Language/GetLocale.php
#- # this error is ignore because of the PHP 8.0 to 8.1 change for pg_*, only for 8.0 or lower #- # this error is ignore because of the PHP 8.0 to 8.1 change for pg_*, only for 8.0 or lower
# message: "#^Parameter \\#1 \\$(result|connection) of function pg_\\w+ expects resource(\\|null)?, object\\|resource(\\|bool)? given\\.$#" # message: "#^Parameter \\#1 \\$(result|connection) of function pg_\\w+ expects resource(\\|null)?, object\\|resource(\\|bool)? given\\.$#"
# path: %currentWorkingDirectory%/www/lib/CoreLibs/DB/SQL/PgSQL.php # path: %currentWorkingDirectory%/www/lib/CoreLibs/DB/SQL/PgSQL.php

View File

@@ -1 +1 @@
9.25.2 9.33.0

View File

@@ -423,14 +423,9 @@ class Login
// LOGOUT TARGET // LOGOUT TARGET
if (!isset($options['logout_target'])) { if (!isset($options['logout_target'])) {
if (defined('LOGOUT_TARGET')) { // defaults to ''
trigger_error( $options['logout_target'] = '';
'loginMainCall: LOGOUT_TARGET should not be used', $this->logout_target = $options['logout_target'];
E_USER_DEPRECATED
);
$options['logout_target'] = LOGOUT_TARGET;
$this->logout_target = $options['logout_target'];
}
} }
// *** PASSWORD SETTINGS // *** PASSWORD SETTINGS
@@ -929,7 +924,9 @@ class Login
$mandatory_session_vars = [ $mandatory_session_vars = [
'LOGIN_USER_NAME', 'LOGIN_GROUP_NAME', 'LOGIN_EUCUID', 'LOGIN_EUCUUID', 'LOGIN_USER_NAME', 'LOGIN_GROUP_NAME', 'LOGIN_EUCUID', 'LOGIN_EUCUUID',
'LOGIN_USER_ADDITIONAL_ACL', 'LOGIN_GROUP_ADDITIONAL_ACL', 'LOGIN_USER_ADDITIONAL_ACL', 'LOGIN_GROUP_ADDITIONAL_ACL',
'LOGIN_ADMIN', 'LOGIN_GROUP_ACL_LEVEL', 'LOGIN_PAGES_ACL_LEVEL', 'LOGIN_USER_ACL_LEVEL', 'LOGIN_ADMIN', 'LOGIN_GROUP_ACL_LEVEL',
'LOGIN_PAGES', 'LOGIN_PAGES_LOOKUP', 'LOGIN_PAGES_ACL_LEVEL',
'LOGIN_USER_ACL_LEVEL',
'LOGIN_UNIT', 'LOGIN_UNIT_DEFAULT_EACUID' 'LOGIN_UNIT', 'LOGIN_UNIT_DEFAULT_EACUID'
]; ];
$force_reauth = false; $force_reauth = false;
@@ -1157,7 +1154,7 @@ class Login
$q $q
); );
// reset any query data that might exist // reset any query data that might exist
$this->db->dbCacheReset($q, $params); $this->db->dbCacheReset($q, $params, show_warning:false);
// never cache return data // never cache return data
$res = $this->db->dbReturnParams($q, $params, $this->db::NO_CACHE); $res = $this->db->dbReturnParams($q, $params, $this->db::NO_CACHE);
// query was not run successful // query was not run successful
@@ -1269,6 +1266,7 @@ class Login
} }
$edit_page_ids = []; $edit_page_ids = [];
$pages = []; $pages = [];
$pages_lookup = [];
$pages_acl = []; $pages_acl = [];
// set pages access // set pages access
$q = <<<SQL $q = <<<SQL
@@ -1312,6 +1310,7 @@ class Login
'query' => [], 'query' => [],
'visible' => [] 'visible' => []
]; ];
$pages_lookup[$res['filename']] = $res['cuid'];
// make reference filename -> level // make reference filename -> level
$pages_acl[$res['filename']] = $res['level']; $pages_acl[$res['filename']] = $res['level'];
} // for each page } // for each page
@@ -1372,6 +1371,7 @@ class Login
// write back the pages data to the output array // write back the pages data to the output array
$this->session->setMany([ $this->session->setMany([
'LOGIN_PAGES' => $pages, 'LOGIN_PAGES' => $pages,
'LOGIN_PAGES_LOOKUP' => $pages_lookup,
'LOGIN_PAGES_ACL_LEVEL' => $pages_acl, 'LOGIN_PAGES_ACL_LEVEL' => $pages_acl,
]); ]);
// load the edit_access user rights // load the edit_access user rights
@@ -1418,6 +1418,7 @@ class Login
'additional_acl' => Json::jsonConvertToArray($res['additional_acl']), 'additional_acl' => Json::jsonConvertToArray($res['additional_acl']),
'data' => $ea_data 'data' => $ea_data
]; ];
// LEGACY LOOKUP
$unit_access_eaid[$res['edit_access_id']] = [ $unit_access_eaid[$res['edit_access_id']] = [
'cuid' => $res['cuid'], 'cuid' => $res['cuid'],
]; ];
@@ -1477,6 +1478,8 @@ class Login
// username (login), group name // username (login), group name
$this->acl['user_name'] = $_SESSION['LOGIN_USER_NAME']; $this->acl['user_name'] = $_SESSION['LOGIN_USER_NAME'];
$this->acl['group_name'] = $_SESSION['LOGIN_GROUP_NAME']; $this->acl['group_name'] = $_SESSION['LOGIN_GROUP_NAME'];
// DEPRECATED
$this->acl['euid'] = $_SESSION['LOGIN_EUID'];
// edit user cuid // edit user cuid
$this->acl['eucuid'] = $_SESSION['LOGIN_EUCUID']; $this->acl['eucuid'] = $_SESSION['LOGIN_EUCUID'];
$this->acl['eucuuid'] = $_SESSION['LOGIN_EUCUUID']; $this->acl['eucuuid'] = $_SESSION['LOGIN_EUCUUID'];
@@ -1528,8 +1531,10 @@ class Login
) { ) {
$this->acl['page'] = $_SESSION['LOGIN_PAGES_ACL_LEVEL'][$this->page_name]; $this->acl['page'] = $_SESSION['LOGIN_PAGES_ACL_LEVEL'][$this->page_name];
} }
$this->acl['pages_detail'] = $_SESSION['LOGIN_PAGES'];
$this->acl['pages_lookup_cuid'] = $_SESSION['LOGIN_PAGES_LOOKUP'];
$this->acl['unit_id'] = null; $this->acl['unit_cuid'] = null;
$this->acl['unit_name'] = null; $this->acl['unit_name'] = null;
$this->acl['unit_uid'] = null; $this->acl['unit_uid'] = null;
$this->acl['unit'] = []; $this->acl['unit'] = [];
@@ -1552,9 +1557,12 @@ class Login
$this->acl['unit_legacy'][$unit['id']] = $this->acl['unit'][$ea_cuid]; $this->acl['unit_legacy'][$unit['id']] = $this->acl['unit'][$ea_cuid];
// detail name/level set // detail name/level set
$this->acl['unit_detail'][$ea_cuid] = [ $this->acl['unit_detail'][$ea_cuid] = [
'id' => $unit['id'],
'name' => $unit['name'], 'name' => $unit['name'],
'uid' => $unit['uid'], 'uid' => $unit['uid'],
'cuuid' => $unit['cuuid'],
'level' => $this->default_acl_list[$this->acl['unit'][$ea_cuid]]['name'] ?? -1, 'level' => $this->default_acl_list[$this->acl['unit'][$ea_cuid]]['name'] ?? -1,
'level_number' => $this->acl['unit'][$ea_cuid],
'default' => $unit['default'], 'default' => $unit['default'],
'data' => $unit['data'], 'data' => $unit['data'],
'additional_acl' => $unit['additional_acl'] 'additional_acl' => $unit['additional_acl']
@@ -2533,7 +2541,7 @@ HTML;
$this->login_user_id, $this->login_user_id,
-1, -1,
$login_user_id_changed $login_user_id_changed
); ) ?? '';
// flag unclean input data // flag unclean input data
if ($login_user_id_changed > 0) { if ($login_user_id_changed > 0) {
$this->login_user_id_unclear = true; $this->login_user_id_unclear = true;
@@ -2727,6 +2735,31 @@ HTML;
return $this->session->get('LOGIN_PAGES'); return $this->session->get('LOGIN_PAGES');
} }
/**
* Return the current loaded list of pages the user can access
*
* @return array<mixed>
*/
public function loginGetPageLookupList(): array
{
return $this->session->get('LOGIN_PAGES_LOOKUP');
}
/**
* Check access to a file in the pages list
*
* @param string $filename File name to check
* @return bool True if page in list and anything other than None access, False if failed
*/
public function loginPageAccessAllowed(string $filename): bool
{
return (
$this->session->get('LOGIN_PAGES')[
$this->session->get('LOGIN_PAGES_LOOKUP')[$filename] ?? ''
] ?? 0
) != 0 ? true : false;
}
// MARK: logged in uid(pk)/eucuid/eucuuid // MARK: logged in uid(pk)/eucuid/eucuuid
/** /**
@@ -3212,7 +3245,7 @@ HTML;
* @return int|null same edit access id if ok * @return int|null same edit access id if ok
* or the default edit access id * or the default edit access id
* if given one is not valid * if given one is not valid
* @deprecated Please switch to using edit access cuid check with ->loginCheckEditAccessValidCuid() * @#deprecated Please switch to using edit access cuid check with ->loginCheckEditAccessValidCuid()
*/ */
public function loginCheckEditAccessId(?int $edit_access_id): ?int public function loginCheckEditAccessId(?int $edit_access_id): ?int
{ {
@@ -3277,6 +3310,34 @@ HTML;
return (int)$_SESSION['LOGIN_UNIT_CUID'][$uid]; return (int)$_SESSION['LOGIN_UNIT_CUID'][$uid];
} }
/**
* Legacy lookup for edit access id to cuid
*
* @param int $id edit access id PK
* @return string|false edit access cuid or false if not found
*/
public function loginGetEditAccessCuidFromId(int $id): string|false
{
if (!isset($_SESSION['LOGIN_UNIT_LEGACY'][$id])) {
return false;
}
return (string)$_SESSION['LOGIN_UNIT_LEGACY'][$id]['cuid'];
}
/**
* This is a Legacy lookup from the edit access id to cuid for further lookups in the normal list
*
* @param string $cuid edit access cuid
* @return int|false false on not found or edit access id PK
*/
public function loginGetEditAccessIdFromCuid(string $cuid): int|false
{
if (!isset($_SESSION['LOGIN_UNIT'][$cuid])) {
return false;
}
return $_SESSION['LOGIN_UNIT'][$cuid]['id'];
}
/** /**
* Check if admin flag is set * Check if admin flag is set
* *

View File

@@ -289,7 +289,7 @@ class Backend
* JSON, STRING/SERIEAL, BINARY/BZIP or ZLIB * JSON, STRING/SERIEAL, BINARY/BZIP or ZLIB
* @param string|null $db_schema [default=null] override target schema * @param string|null $db_schema [default=null] override target schema
* @return void * @return void
* @deprecated Use $login->writeLog() and set action_set from ->adbGetActionSet() * @deprecated Use $login->writeLog($event, $data, action_set:$cms->adbGetActionSet(), write_type:$write_type)
*/ */
public function adbEditLog( public function adbEditLog(
string $event = '', string $event = '',

View File

@@ -14,9 +14,6 @@ declare(strict_types=1);
namespace CoreLibs\Admin; namespace CoreLibs\Admin;
use Exception;
use SmartyException;
class EditBase class EditBase
{ {
/** @var array<mixed> */ /** @var array<mixed> */
@@ -63,6 +60,7 @@ class EditBase
// smarty template engine (extended Translation version) // smarty template engine (extended Translation version)
$this->smarty = new \CoreLibs\Template\SmartyExtend( $this->smarty = new \CoreLibs\Template\SmartyExtend(
$l10n, $l10n,
$log,
$options['cache_id'] ?? '', $options['cache_id'] ?? '',
$options['compile_id'] ?? '', $options['compile_id'] ?? '',
); );
@@ -78,7 +76,7 @@ class EditBase
); );
if ($this->form->mobile_phone) { if ($this->form->mobile_phone) {
echo "I am sorry, but this page cannot be viewed by a mobile phone"; echo "I am sorry, but this page cannot be viewed by a mobile phone";
exit; exit(1);
} }
// $this->log->debug('POST', $this->log->prAr($_POST)); // $this->log->debug('POST', $this->log->prAr($_POST));
} }
@@ -538,8 +536,7 @@ class EditBase
* builds the smarty content and runs smarty display output * builds the smarty content and runs smarty display output
* *
* @return void * @return void
* @throws Exception * @throws \Smarty\Exception
* @throws SmartyException
*/ */
public function editBaseRun( public function editBaseRun(
?string $template_dir = null, ?string $template_dir = null,

View File

@@ -103,11 +103,7 @@ class Basic
'VIDEOS', 'DOCUMENTS', 'PDFS', 'BINARIES', 'ICONS', 'UPLOADS', 'CSV', 'JS', 'VIDEOS', 'DOCUMENTS', 'PDFS', 'BINARIES', 'ICONS', 'UPLOADS', 'CSV', 'JS',
'CSS', 'TABLE_ARRAYS', 'SMARTY', 'LANG', 'CACHE', 'TMP', 'LOG', 'TEMPLATES', 'CSS', 'TABLE_ARRAYS', 'SMARTY', 'LANG', 'CACHE', 'TMP', 'LOG', 'TEMPLATES',
'TEMPLATES_C', 'DEFAULT_LANG', 'DEFAULT_ENCODING', 'DEFAULT_HASH', 'TEMPLATES_C', 'DEFAULT_LANG', 'DEFAULT_ENCODING', 'DEFAULT_HASH',
'DEFAULT_ACL_LEVEL', 'LOGOUT_TARGET', 'PASSWORD_CHANGE', 'AJAX_REQUEST_TYPE', 'DB_CONFIG_NAME', 'DB_CONFIG', 'TARGET'
'USE_PROTOTYPE', 'USE_SCRIPTACULOUS', 'USE_JQUERY', 'PAGE_WIDTH',
'MASTER_TEMPLATE_NAME', 'PUBLIC_SCHEMA', 'TEST_SCHEMA', 'DEV_SCHEMA',
'LIVE_SCHEMA', 'DB_CONFIG_NAME', 'DB_CONFIG', 'TARGET', 'DEBUG',
'SHOW_ALL_ERRORS'
] as $constant ] as $constant
) { ) {
if (!defined($constant)) { if (!defined($constant)) {
@@ -387,7 +383,8 @@ class Basic
public function initRandomKeyLength(int $key_length): bool public function initRandomKeyLength(int $key_length): bool
{ {
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\RandomKey::setRandomKeyLength()', E_USER_DEPRECATED); trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\RandomKey::setRandomKeyLength()', E_USER_DEPRECATED);
return \CoreLibs\Create\RandomKey::setRandomKeyLength($key_length); // no op, we do no longer pre set the random key length
return true;
} }
/** /**
@@ -992,10 +989,10 @@ class Basic
* @param bool $auto_check default true, if source encoding is set * @param bool $auto_check default true, if source encoding is set
* check that the source is actually matching * check that the source is actually matching
* to what we sav the source is * to what we sav the source is
* @return string encoding converted string * @return string|false encoding converted string
* @deprecated use \CoreLibs\Convert\Encoding::convertEncoding() instead * @deprecated use \CoreLibs\Convert\Encoding::convertEncoding() instead
*/ */
public static function convertEncoding(string $string, string $to_encoding, string $source_encoding = '', bool $auto_check = true): string public static function convertEncoding(string $string, string $to_encoding, string $source_encoding = '', bool $auto_check = true): string|false
{ {
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Encoding::convertEncoding()', E_USER_DEPRECATED); trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Encoding::convertEncoding()', E_USER_DEPRECATED);
return \CoreLibs\Convert\Encoding::convertEncoding($string, $to_encoding, $source_encoding, $auto_check); return \CoreLibs\Convert\Encoding::convertEncoding($string, $to_encoding, $source_encoding, $auto_check);
@@ -1028,8 +1025,12 @@ class Basic
*/ */
public function __sha1Short(string $string, bool $use_sha = false): string public function __sha1Short(string $string, bool $use_sha = false): string
{ {
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\Hash::__sha1Short()', E_USER_DEPRECATED); trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\Hash::sha1Short() or ::__crc32b()', E_USER_DEPRECATED);
return \CoreLibs\Create\Hash::__sha1Short($string, $use_sha); if ($use_sha) {
return \CoreLibs\Create\Hash::sha1Short($string);
} else {
return \CoreLibs\Create\Hash::__crc32b($string);
}
} }
/** /**
@@ -1044,8 +1045,8 @@ class Basic
*/ */
public function __hash(string $string, string $hash_type = 'adler32'): string public function __hash(string $string, string $hash_type = 'adler32'): string
{ {
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\Hash::__hash()', E_USER_DEPRECATED); trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\Hash::hash()', E_USER_DEPRECATED);
return \CoreLibs\Create\Hash::__hash($string, $hash_type); return \CoreLibs\Create\Hash::hash($string, $hash_type);
} }
// *** HASH FUNCTIONS END // *** HASH FUNCTIONS END

View File

@@ -56,7 +56,11 @@ class Encoding
{ {
// return mb_substitute_character(); // return mb_substitute_character();
if ($return_substitute_func === true) { if ($return_substitute_func === true) {
return mb_substitute_character(); // if false abort with error
if (($return = mb_substitute_character()) === false) {
return self::$mb_error_char;
}
return $return;
} else { } else {
return self::$mb_error_char; return self::$mb_error_char;
} }
@@ -88,7 +92,13 @@ class Encoding
): array|false { ): array|false {
// convert to target encoding and convert back // convert to target encoding and convert back
$temp = mb_convert_encoding($string, $to_encoding, $from_encoding); $temp = mb_convert_encoding($string, $to_encoding, $from_encoding);
if ($temp === false) {
return false;
}
$compare = mb_convert_encoding($temp, $from_encoding, $to_encoding); $compare = mb_convert_encoding($temp, $from_encoding, $to_encoding);
if ($compare === false) {
return false;
}
// if string does not match anymore we have a convert problem // if string does not match anymore we have a convert problem
if ($string == $compare) { if ($string == $compare) {
return false; return false;

View File

@@ -10,6 +10,8 @@ namespace CoreLibs\Combined;
class ArrayHandler class ArrayHandler
{ {
public const string DATA_SEPARATOR = ':';
/** /**
* searches key = value in an array / array * searches key = value in an array / array
* only returns the first one found * only returns the first one found
@@ -148,18 +150,22 @@ class ArrayHandler
* array search simple. looks for key, value combination, if found, returns true * array search simple. looks for key, value combination, if found, returns true
* on default does not strict check, so string '4' will match int 4 and vica versa * on default does not strict check, so string '4' will match int 4 and vica versa
* *
* @param array<mixed> $array search in as array * @param array<mixed> $array search in as array
* @param string|int $key key (key to search in) * @param string|int $key key (key to search in)
* @param string|int|bool $value value (what to find) * @param string|int|bool|array<string|int|bool> $value values list (what to find)
* @param bool $strict [false], if set to true, will strict check key/value * @param bool $strict [false], if set to true, will strict check key/value
* @return bool true on found, false on not found * @return bool true on found, false on not found
*/ */
public static function arraySearchSimple( public static function arraySearchSimple(
array $array, array $array,
string|int $key, string|int $key,
string|int|bool $value, string|int|bool|array $value,
bool $strict = false bool $strict = false
): bool { ): bool {
// convert to array
if (!is_array($value)) {
$value = [$value];
}
foreach ($array as $_key => $_value) { foreach ($array as $_key => $_value) {
// if value is an array, we search // if value is an array, we search
if (is_array($_value)) { if (is_array($_value)) {
@@ -167,9 +173,9 @@ class ArrayHandler
if (($result = self::arraySearchSimple($_value, $key, $value, $strict)) !== false) { if (($result = self::arraySearchSimple($_value, $key, $value, $strict)) !== false) {
return $result; return $result;
} }
} elseif ($strict === false && $_key == $key && $_value == $value) { } elseif ($strict === false && $_key == $key && in_array($_value, $value)) {
return true; return true;
} elseif ($strict === true && $_key === $key && $_value === $value) { } elseif ($strict === true && $_key === $key && in_array($_value, $value, true)) {
return true; return true;
} }
} }
@@ -236,6 +242,160 @@ class ArrayHandler
return $hit_list; return $hit_list;
} }
/**
* Search in an array for value with or without key and
* check in the same array block for the required key
* If not found return an array with the array block there the required key is missing,
* the path as string with seperator block set and the missing key entry
*
* @param array<mixed> $array
* @param string|int|float|bool $search_value
* @param string|array<string> $required_key
* @param ?string $search_key [null]
* @param string $path_separator [DATA_SEPARATOR]
* @param string $current_path
* @return array<array{content?:array<mixed>,path?:string,missing_key?:array<string>}>
*/
public static function findArraysMissingKey(
array $array,
string|int|float|bool $search_value,
string|array $required_key,
?string $search_key = null,
string $path_separator = self::DATA_SEPARATOR,
string $current_path = ''
): array {
$results = [];
foreach ($array as $key => $value) {
$path = $current_path ? $current_path . $path_separator . $key : $key;
if (is_array($value)) {
// Check if this array contains the search value
// either any value match or with key
if ($search_key === null) {
$containsValue = in_array($search_value, $value, true);
} else {
$containsValue = array_key_exists($search_key, $value) && $value[$search_key] === $search_value;
}
// If it contains the value but doesn't have the required key
if (
$containsValue &&
(
(
is_string($required_key) &&
!array_key_exists($required_key, $value)
) || (
is_array($required_key) &&
count(array_intersect($required_key, array_keys($value))) !== count($required_key)
)
)
) {
$results[] = [
'content' => $value,
'path' => $path,
'missing_key' => is_array($required_key) ?
array_values(array_diff($required_key, array_keys($value))) :
[$required_key]
];
}
// Recursively search nested arrays
$results = array_merge(
$results,
self::findArraysMissingKey(
$value,
$search_value,
$required_key,
$search_key,
$path_separator,
$path
)
);
}
}
return $results;
}
/**
* Find key => value entry and return set with key for all matching
* Can search recursively through nested arrays if recursive flag is set
*
* @param array<mixed> $array
* @param string $lookup
* @param int|string|float|bool $search
* @param bool $strict [false]
* @param bool $case_insensitive [false]
* @param bool $recursive [false]
* @param bool $flat_result [true] If set to false and recursive is on the result is a nested array
* @param string $flat_separator [DATA_SEPARATOR] if flat result is true, can be any string
* @return array<mixed>
*/
public static function selectArrayFromOption(
array $array,
string $lookup,
int|string|float|bool $search,
bool $strict = false,
bool $case_insensitive = false,
bool $recursive = false,
bool $flat_result = true,
string $flat_separator = self::DATA_SEPARATOR
): array {
// skip on empty
if ($array == []) {
return [];
}
// init return result
$result = [];
// case sensitive convert if string
if ($case_insensitive && is_string($search)) {
$search = strtolower($search);
}
foreach ($array as $key => $value) {
// Handle current level search
if (isset($value[$lookup])) {
$compareValue = $value[$lookup];
if ($case_insensitive && is_string($compareValue)) {
$compareValue = strtolower($compareValue);
}
if (
($strict && $search === $compareValue) ||
(!$strict && $search == $compareValue)
) {
$result[$key] = $value;
}
}
// Handle recursive search if flag is set
if ($recursive && is_array($value)) {
$recursiveResults = self::selectArrayFromOption(
$value,
$lookup,
$search,
$strict,
$case_insensitive,
true,
$flat_result,
$flat_separator
);
// Merge recursive results with current results
// Preserve keys by using array_merge with string keys or + operator
foreach ($recursiveResults as $recKey => $recValue) {
if ($flat_result) {
$result[$key . $flat_separator . $recKey] = $recValue;
} else {
$result[$key][$recKey] = $recValue;
}
}
}
}
return $result;
}
/** /**
* main wrapper function for next/prev key * main wrapper function for next/prev key
* *
@@ -551,6 +711,103 @@ class ArrayHandler
ARRAY_FILTER_USE_KEY ARRAY_FILTER_USE_KEY
); );
} }
/**
* Modifieds the key of an array with a prefix and/or suffix and
* returns it with the original value
* does not change order in array
*
* @param array<string|int,mixed> $in_array
* @param string $key_mod_prefix [''] key prefix string
* @param string $key_mod_suffix [''] key suffix string
* @return array<string|int,mixed>
*/
public static function arrayModifyKey(
array $in_array,
string $key_mod_prefix = '',
string $key_mod_suffix = ''
): array {
// skip if array is empty or neither prefix or suffix are set
if (
$in_array == [] ||
($key_mod_prefix == '' && $key_mod_suffix == '')
) {
return $in_array;
}
return array_combine(
array_map(
fn($key) => $key_mod_prefix . $key . $key_mod_suffix,
array_keys($in_array)
),
array_values($in_array)
);
}
/**
* sort array and return in same call
* sort ascending or descending with or without lower case convert
* value only, will loose key connections unless preserve_keys is set to true
*
* @param array<mixed> $array array to sort by values
* @param int $params sort flags
* @return array<mixed>
*/
public static function sortArray(
array $array,
bool $case_insensitive = false,
bool $reverse = false,
bool $maintain_keys = false,
int $params = SORT_REGULAR
): array {
$fk_sort_lower_case = function (string $a, string $b): int {
return strtolower($a) <=> strtolower($b);
};
$fk_sort_lower_case_reverse = function (string $a, string $b): int {
return strtolower($b) <=> strtolower($a);
};
$case_insensitive ? (
$maintain_keys ?
(uasort($array, $reverse ? $fk_sort_lower_case_reverse : $fk_sort_lower_case)) :
(usort($array, $reverse ? $fk_sort_lower_case_reverse : $fk_sort_lower_case))
) :
(
$maintain_keys ?
($reverse ? arsort($array, $params) : asort($array, $params)) :
($reverse ? rsort($array, $params) : sort($array, $params))
);
return $array;
}
/**
* sort by key ascending or descending and return
*
* @param array<mixed> $array
* @param bool $case_insensitive [false]
* @param bool $reverse [false]
* @return array<mixed>
*/
public static function ksortArray(array $array, bool $case_insensitive = false, bool $reverse = false): array
{
$fk_sort_lower_case = function (string $a, string $b): int {
return strtolower($a) <=> strtolower($b);
};
$fk_sort_lower_case_reverse = function (string $a, string $b): int {
return strtolower($b) <=> strtolower($a);
};
$fk_sort = function (string $a, string $b): int {
return $a <=> $b;
};
$fk_sort_reverse = function (string $a, string $b): int {
return $b <=> $a;
};
uksort(
$array,
$case_insensitive ?
($reverse ? $fk_sort_lower_case_reverse : $fk_sort_lower_case) :
($reverse ? $fk_sort_reverse : $fk_sort)
);
return $array;
}
} }
// __END__ // __END__

View File

@@ -639,16 +639,26 @@ class DateTime
* *
* @param string $start_date valid start date (y/m/d) * @param string $start_date valid start date (y/m/d)
* @param string $end_date valid end date (y/m/d) * @param string $end_date valid end date (y/m/d)
* @param bool $return_named return array type, false (default), true for named * @param bool $return_named [default=false] return array type, false (default), true for named
* @return array<mixed> 0/overall, 1/weekday, 2/weekend * @param bool $include_end_date [default=true] include end date in calc
* @param bool $exclude_start_date [default=false] include end date in calc
* @return array{0:int,1:int,2:int,3:bool}|array{overall:int,weekday:int,weekend:int,reverse:bool}
* 0/overall, 1/weekday, 2/weekend, 3/reverse
*/ */
public static function calcDaysInterval( public static function calcDaysInterval(
string $start_date, string $start_date,
string $end_date, string $end_date,
bool $return_named = false bool $return_named = false,
bool $include_end_date = true,
bool $exclude_start_date = false
): array { ): array {
// pos 0 all, pos 1 weekday, pos 2 weekend // pos 0 all, pos 1 weekday, pos 2 weekend
$days = []; $days = [
0 => 0,
1 => 0,
2 => 0,
3 => false,
];
// if anything invalid, return 0,0,0 // if anything invalid, return 0,0,0
try { try {
$start = new \DateTime($start_date); $start = new \DateTime($start_date);
@@ -659,19 +669,30 @@ class DateTime
'overall' => 0, 'overall' => 0,
'weekday' => 0, 'weekday' => 0,
'weekend' => 0, 'weekend' => 0,
'reverse' => false
]; ];
} else { } else {
return [0, 0, 0]; return $days;
} }
} }
// so we include the last day too, we need to add +1 second in the time // so we include the last day too, we need to add +1 second in the time
$end->setTime(0, 0, 1); // if start is before end, switch dates and flag
// if end date before start date, only this will be filled $days[3] = false;
$days[0] = $end->diff($start)->days; if ($start > $end) {
$days[1] = 0; $new_start = $end;
$days[2] = 0; $end = $start;
$start = $new_start;
$days[3] = true;
}
// get period for weekends/weekdays // get period for weekends/weekdays
$period = new \DatePeriod($start, new \DateInterval('P1D'), $end); $options = 0;
if ($include_end_date) {
$options |= \DatePeriod::INCLUDE_END_DATE;
}
if ($exclude_start_date) {
$options |= \DatePeriod::EXCLUDE_START_DATE;
}
$period = new \DatePeriod($start, new \DateInterval('P1D'), $end, $options);
foreach ($period as $dt) { foreach ($period as $dt) {
$curr = $dt->format('D'); $curr = $dt->format('D');
if ($curr == 'Sat' || $curr == 'Sun') { if ($curr == 'Sat' || $curr == 'Sun') {
@@ -679,18 +700,80 @@ class DateTime
} else { } else {
$days[1]++; $days[1]++;
} }
$days[0]++;
} }
if ($return_named === true) { if ($return_named === true) {
return [ return [
'overall' => $days[0], 'overall' => $days[0],
'weekday' => $days[1], 'weekday' => $days[1],
'weekend' => $days[2], 'weekend' => $days[2],
'reverse' => $days[3],
]; ];
} else { } else {
return $days; return $days;
} }
} }
/**
* wrapper for calcDaysInterval with numeric return only
*
* @param string $start_date valid start date (y/m/d)
* @param string $end_date valid end date (y/m/d)
* @param bool $include_end_date [default=true] include end date in calc
* @param bool $exclude_start_date [default=false] include end date in calc
* @return array{0:int,1:int,2:int,3:bool}
*/
public static function calcDaysIntervalNumIndex(
string $start_date,
string $end_date,
bool $include_end_date = true,
bool $exclude_start_date = false
): array {
$values = self::calcDaysInterval(
$start_date,
$end_date,
false,
$include_end_date,
$exclude_start_date
);
return [
$values[0] ?? 0,
$values[1] ?? 0,
$values[2] ?? 0,
$values[3] ?? false,
];
}
/**
* wrapper for calcDaysInterval with named return only
*
* @param string $start_date valid start date (y/m/d)
* @param string $end_date valid end date (y/m/d)
* @param bool $include_end_date [default=true] include end date in calc
* @param bool $exclude_start_date [default=false] include end date in calc
* @return array{overall:int,weekday:int,weekend:int,reverse:bool}
*/
public static function calcDaysIntervalNamedIndex(
string $start_date,
string $end_date,
bool $include_end_date = true,
bool $exclude_start_date = false
): array {
$values = self::calcDaysInterval(
$start_date,
$end_date,
true,
$include_end_date,
$exclude_start_date
);
return [
'overall' => $values['overall'] ?? 0,
'weekday' => $values['weekday'] ?? 0,
'weekend' => $values['weekend'] ?? 0,
'reverse' => $values['reverse'] ?? false,
];
}
/** /**
* check if a weekend day (sat/sun) is in the given date range * check if a weekend day (sat/sun) is in the given date range
* Can have time too, but is not needed * Can have time too, but is not needed
@@ -705,6 +788,13 @@ class DateTime
): bool { ): bool {
$dd_start = new \DateTime($start_date); $dd_start = new \DateTime($start_date);
$dd_end = new \DateTime($end_date); $dd_end = new \DateTime($end_date);
// flip if start is after end
if ($dd_start > $dd_end) {
$new_start = $dd_end;
$dd_end = $dd_start;
$dd_start = $new_start;
}
// if start > end, flip
if ( if (
// starts with a weekend // starts with a weekend
$dd_start->format('N') >= 6 || $dd_start->format('N') >= 6 ||

View File

@@ -23,14 +23,14 @@ class Encoding
* @param bool $auto_check default true, if source encoding is set * @param bool $auto_check default true, if source encoding is set
* check that the source is actually matching * check that the source is actually matching
* to what we sav the source is * to what we sav the source is
* @return string encoding converted string * @return string|false encoding converted string or false on error
*/ */
public static function convertEncoding( public static function convertEncoding(
string $string, string $string,
string $to_encoding, string $to_encoding,
string $source_encoding = '', string $source_encoding = '',
bool $auto_check = true bool $auto_check = true
): string { ): string|false {
// set if not given // set if not given
if (!$source_encoding) { if (!$source_encoding) {
$source_encoding = mb_detect_encoding($string); $source_encoding = mb_detect_encoding($string);

View File

@@ -10,9 +10,16 @@ namespace CoreLibs\Convert;
class Html class Html
{ {
/** @var int */
public const SELECTED = 0; public const SELECTED = 0;
/** @var int */
public const CHECKED = 1; public const CHECKED = 1;
// TODO: check for not valid htmlentites encoding
// as of PHP 8.4: https://www.php.net/manual/en/function.htmlentities.php
/** @#var array<string> */
// public const VALID_HTMLENT_ENCODINGS = [];
/** /**
* full wrapper for html entities * full wrapper for html entities
* *
@@ -22,14 +29,19 @@ class Html
* encodes in UTF-8 * encodes in UTF-8
* does not double encode * does not double encode
* *
* @param mixed $string string to html encode * @param mixed $string string to html encode
* @param int $flags [default: ENT_QUOTES | ENT_HTML5] * @param int $flags [default=ENT_QUOTES | ENT_HTML5]
* @param string $encoding [default=UTF-8]
* @return mixed if string, encoded, else as is (eg null) * @return mixed if string, encoded, else as is (eg null)
*/ */
public static function htmlent(mixed $string, int $flags = ENT_QUOTES | ENT_HTML5): mixed public static function htmlent(
{ mixed $string,
int $flags = ENT_QUOTES | ENT_HTML5,
string $encoding = 'UTF-8'
): mixed {
if (is_string($string)) { if (is_string($string)) {
return htmlentities($string, $flags, 'UTF-8', false); // if not a valid encoding this will throw a warning and use UTF-8
return htmlentities($string, $flags, $encoding, false);
} }
return $string; return $string;
} }
@@ -37,7 +49,7 @@ class Html
/** /**
* strips out all line breaks or replaced with given string * strips out all line breaks or replaced with given string
* @param string $string string * @param string $string string
* @param string $replace replace character, default ' ' * @param string $replace [default=' '] replace character
* @return string cleaned string without any line breaks * @return string cleaned string without any line breaks
*/ */
public static function removeLB(string $string, string $replace = ' '): string public static function removeLB(string $string, string $replace = ' '): string

View File

@@ -119,6 +119,23 @@ class Json
} }
return $return_string === true ? $json_error_string : self::$json_last_error; return $return_string === true ? $json_error_string : self::$json_last_error;
} }
/**
* wrapper to call convert array to json with pretty print
*
* @param array<mixed> $data
* @return string
*/
public static function jsonPrettyPrint(array $data): string
{
return self::jsonConvertArrayTo(
$data,
JSON_PRETTY_PRINT |
JSON_UNESCAPED_LINE_TERMINATORS |
JSON_UNESCAPED_SLASHES |
JSON_UNESCAPED_UNICODE
);
}
} }
// __END__ // __END__

View File

@@ -8,8 +8,20 @@ declare(strict_types=1);
namespace CoreLibs\Convert; namespace CoreLibs\Convert;
use CoreLibs\Combined\ArrayHandler;
class Strings class Strings
{ {
/** @var array<int,string> all the preg error messages */
public const array PREG_ERROR_MESSAGES = [
PREG_NO_ERROR => 'No error',
PREG_INTERNAL_ERROR => 'Internal PCRE error',
PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit exhausted',
PREG_RECURSION_LIMIT_ERROR => 'Recursion limit exhausted',
PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data',
PREG_BAD_UTF8_OFFSET_ERROR => 'Bad UTF-8 offset',
PREG_JIT_STACKLIMIT_ERROR => 'JIT stack limit exhausted'
];
/** /**
* return the number of elements in the split list * return the number of elements in the split list
* 0 if nothing / invalid split * 0 if nothing / invalid split
@@ -52,29 +64,42 @@ class Strings
* Note a string LONGER then the maxium will be attached with the LAST * Note a string LONGER then the maxium will be attached with the LAST
* split character. In above exmaple * split character. In above exmaple
* ABCD1234EFGHTOOLONG will be ABCD-1234-EFGH-TOOLONG * ABCD1234EFGHTOOLONG will be ABCD-1234-EFGH-TOOLONG
* If the characters are NOT ASCII it will return the string as is
* *
* @param string $value string value to split * @param string $string string value to split
* @param string $split_format split format * @param string $split_format split format
* @param string $split_characters list of charcters with which we split
* if not set uses dash ('-')
* @return string split formatted string or original value if not chnaged * @return string split formatted string or original value if not chnaged
* @throws \InvalidArgumentException for empty split format, invalid values, split characters or split format
*/ */
public static function splitFormatString( public static function splitFormatString(
string $value, string $string,
string $split_format, string $split_format,
string $split_characters = '-'
): string { ): string {
if ( // skip if string or split format is empty is empty
// abort if split format is empty if (empty($string) || empty($split_format)) {
empty($split_format) || return $string;
// if not in the valid ASCII character range for any of the strings }
preg_match('/[^\x20-\x7e]/', $value) || if (preg_match('/[^\x20-\x7e]/', $string)) {
// preg_match('/[^\x20-\x7e]/', $split_format) || throw new \InvalidArgumentException(
preg_match('/[^\x20-\x7e]/', $split_characters) || "The string to split can only be ascii characters: " . $string
// only numbers and split characters in split_format );
!preg_match("/[0-9" . $split_characters . "]/", $split_format) }
) { // get the split characters that are not numerical and check they are ascii
return $value; $split_characters = self::removeDuplicates(preg_replace('/[0-9]/', '', $split_format) ?: '');
if (empty($split_characters)) {
throw new \InvalidArgumentException(
"A split character must exist in the format string: " . $split_format
);
}
if (preg_match('/[^\x20-\x7e]/', $split_characters)) {
throw new \InvalidArgumentException(
"The split character has to be a valid ascii character: " . $split_characters
);
}
if (!preg_match("/^[0-9" . $split_characters . "]+$/", $split_format)) {
throw new \InvalidArgumentException(
"The split format can only be numbers and the split characters: " . $split_format
);
} }
// split format list // split format list
$split_list = preg_split( $split_list = preg_split(
@@ -86,14 +111,14 @@ class Strings
); );
// if this is false, or only one array, abort split // if this is false, or only one array, abort split
if (!is_array($split_list) || count($split_list) == 1) { if (!is_array($split_list) || count($split_list) == 1) {
return $value; return $string;
} }
$out = ''; $out = '';
$pos = 0; $pos = 0;
$last_split = ''; $last_split = '';
foreach ($split_list as $offset) { foreach ($split_list as $offset) {
if (is_numeric($offset)) { if (is_numeric($offset)) {
$_part = substr($value, $pos, (int)$offset); $_part = substr($string, $pos, (int)$offset);
if (empty($_part)) { if (empty($_part)) {
break; break;
} }
@@ -104,8 +129,8 @@ class Strings
$last_split = $offset; $last_split = $offset;
} }
} }
if (!empty($out) && $pos < strlen($value)) { if (!empty($out) && $pos < strlen($string)) {
$out .= $last_split . substr($value, $pos); $out .= $last_split . substr($string, $pos);
} }
// if last is not alphanumeric remove, remove // if last is not alphanumeric remove, remove
if (!strcspn(substr($out, -1, 1), $split_characters)) { if (!strcspn(substr($out, -1, 1), $split_characters)) {
@@ -115,10 +140,49 @@ class Strings
if (!empty($out)) { if (!empty($out)) {
return $out; return $out;
} else { } else {
return $value; return $string;
} }
} }
/**
* Split a string into n-length blocks with a split character inbetween
* This is simplified version from splitFormatString that uses
* fixed split length with a characters, this evenly splits the string out into the
* given length
* This works with non ASCII characters too
*
* @param string $string string to split
* @param int $split_length split length, must be smaller than string and larger than 0
* @param string $split_characters [default=-] the character to split, can be more than one
* @return string
* @throws \InvalidArgumentException Thrown if split length style is invalid
*/
public static function splitFormatStringFixed(
string $string,
int $split_length,
string $split_characters = '-'
): string {
// if empty string or if split lenght is 0 or empty split characters
// then we skip any splitting
if (empty($string) || $split_length == 0 || empty($split_characters)) {
return $string;
}
$return_string = '';
$string_length = mb_strlen($string);
// check that the length is not too short
if ($split_length < 1 || $split_length >= $string_length) {
throw new \InvalidArgumentException(
"The split length must be at least 1 character and less than the string length to split. "
. "Split length: " . $split_length . ", string length: " . $string_length
);
}
for ($i = 0; $i < $string_length; $i += $split_length) {
$return_string .= mb_substr($string, $i, $split_length) . $split_characters;
}
// remove last trailing character which is always the split char length
return mb_substr($return_string, 0, -1 * mb_strlen($split_characters));
}
/** /**
* Strip any duplicated slahes from a path * Strip any duplicated slahes from a path
* eg: //foo///bar/foo.inc -> /foo/bar/foo.inc * eg: //foo///bar/foo.inc -> /foo/bar/foo.inc
@@ -146,6 +210,116 @@ class Strings
{ {
return trim($text, pack('H*', 'EFBBBF')); return trim($text, pack('H*', 'EFBBBF'));
} }
/**
* Make as string of characters unique
*
* @param string $string
* @return string
*/
public static function removeDuplicates(string $string): string
{
// combine again
$result = implode(
'',
// unique list
array_unique(
// split into array
mb_str_split($string)
)
);
return $result;
}
/**
* check if all characters are in set
*
* @param string $needle Needle to search
* @param string $haystack Haystack to search in
* @return bool True on found, False if not in haystack
*/
public static function allCharsInSet(string $needle, string $haystack): bool
{
$input_length = strlen($needle);
for ($i = 0; $i < $input_length; $i++) {
if (strpos($haystack, $needle[$i]) === false) {
return false;
}
}
return true;
}
/**
* converts a list of arrays of strings into a string of unique entries
* input arrays can be nested, only values are used
*
* @param array<mixed> ...$char_lists
* @return string
*/
public static function buildCharStringFromLists(array ...$char_lists): string
{
return implode('', array_unique(
ArrayHandler::flattenArray(
array_merge(...$char_lists)
)
));
}
/**
* Check if a regex is valid. Does not return the detail regex parser error
*
* @param string $pattern Any regex string
* @return bool False on invalid regex
*/
public static function isValidRegex(string $pattern): bool
{
preg_last_error();
try {
$var = '';
@preg_match($pattern, $var);
return preg_last_error() === PREG_NO_ERROR;
} catch (\Error $e) {
return false;
}
}
/**
* Returns the last preg error messages as string
* all messages are defined in PREG_ERROR_MESSAGES
*
* @return string
*/
public static function getLastRegexErrorString(): string
{
return self::PREG_ERROR_MESSAGES[preg_last_error()] ?? 'Unknown error';
}
/**
* check if a regex is invalid, returns array with flag and error string
*
* @param string $pattern
* @return array{valid:bool,preg_error:int,error:null|string,pcre_error:null|string}
*/
public static function validateRegex(string $pattern): array
{
// Clear any previous PCRE errors
preg_last_error();
$var = '';
if (@preg_match($pattern, $var) === false) {
$error = preg_last_error();
return [
'valid' => false,
'preg_error' => $error,
'error' => self::PREG_ERROR_MESSAGES[$error] ?? 'Unknown error',
'pcre_error' => preg_last_error_msg(),
];
}
return ['valid' => true, 'preg_error' => PREG_NO_ERROR, 'error' => null, 'pcre_error' => null];
}
} }
// __END__ // __END__

View File

@@ -38,6 +38,7 @@ class Email
* @param string $encoding Encoding, if not set UTF-8 * @param string $encoding Encoding, if not set UTF-8
* @param bool $kv_folding If set to true and a valid encoding, do KV folding * @param bool $kv_folding If set to true and a valid encoding, do KV folding
* @return string Correctly encoded and build email string * @return string Correctly encoded and build email string
* @throws \IntlException if email name cannot be converted to UTF-8
*/ */
public static function encodeEmailName( public static function encodeEmailName(
string $email, string $email,
@@ -52,6 +53,10 @@ class Email
if ($encoding != 'UTF-8') { if ($encoding != 'UTF-8') {
$email_name = mb_convert_encoding($email_name, $encoding, 'UTF-8'); $email_name = mb_convert_encoding($email_name, $encoding, 'UTF-8');
} }
// if we cannot transcode the name, return just the email
if ($email_name === false) {
throw new \IntlException('Cannot convert email_name to UTF-8');
}
$email_name = $email_name =
mb_encode_mimeheader( mb_encode_mimeheader(
in_array($encoding, self::$encoding_kv_allowed) && $kv_folding ? in_array($encoding, self::$encoding_kv_allowed) && $kv_folding ?
@@ -77,6 +82,8 @@ class Email
* @param bool $kv_folding If set to true and a valid encoding, * @param bool $kv_folding If set to true and a valid encoding,
* do KV folding * do KV folding
* @return array<string> Pos 0: Subject, Pos 1: Body * @return array<string> Pos 0: Subject, Pos 1: Body
* @throws \IntlException if subject cannot be converted to UTF-8
* @throws \IntlException if body cannot be converted to UTF-8
*/ */
private static function replaceContent( private static function replaceContent(
string $subject, string $subject,
@@ -102,6 +109,12 @@ class Email
$subject = mb_convert_encoding($subject, $encoding, 'UTF-8'); $subject = mb_convert_encoding($subject, $encoding, 'UTF-8');
$body = mb_convert_encoding($body, $encoding, 'UTF-8'); $body = mb_convert_encoding($body, $encoding, 'UTF-8');
} }
if ($subject === false) {
throw new \IntlException('Cannot convert subject to UTF-8');
}
if ($body === false) {
throw new \IntlException('Cannot convert body to UTF-8');
}
// we need to encodde the subject // we need to encodde the subject
$subject = mb_encode_mimeheader( $subject = mb_encode_mimeheader(
in_array($encoding, self::$encoding_kv_allowed) && $kv_folding ? in_array($encoding, self::$encoding_kv_allowed) && $kv_folding ?

View File

@@ -10,9 +10,14 @@ namespace CoreLibs\Create;
class Hash class Hash
{ {
/** @var string default short hash -> deprecated use STANDARD_HASH_SHORT */
public const DEFAULT_HASH = 'adler32'; public const DEFAULT_HASH = 'adler32';
/** @var string default long hash (40 chars) */
public const STANDARD_HASH_LONG = 'ripemd160'; public const STANDARD_HASH_LONG = 'ripemd160';
/** @var string default short hash (8 chars) */
public const STANDARD_HASH_SHORT = 'adler32'; public const STANDARD_HASH_SHORT = 'adler32';
/** @var string this is the standard hash to use hashStd and hash (64 chars) */
public const STANDARD_HASH = 'sha256';
/** /**
* checks php version and if >=5.2.7 it will flip the string * checks php version and if >=5.2.7 it will flip the string
@@ -20,6 +25,7 @@ class Hash
* hash returns false * hash returns false
* preg_replace fails for older php version * preg_replace fails for older php version
* Use __hash with crc32b or hash('crc32b', ...) for correct output * Use __hash with crc32b or hash('crc32b', ...) for correct output
* For future short hashes use hashShort() instead
* *
* @param string $string string to crc * @param string $string string to crc
* @return string crc32b hash (old type) * @return string crc32b hash (old type)
@@ -43,19 +49,31 @@ class Hash
* replacement for __crc32b call * replacement for __crc32b call
* *
* @param string $string string to hash * @param string $string string to hash
* @param bool $use_sha use sha instead of crc32b (default false) * @param bool $use_sha [default=false] use sha1 instead of crc32b
* @return string hash of the string * @return string hash of the string
* @deprecated use __crc32b() for drop in replacement with default, or sha1Short() for use sha true
*/ */
public static function __sha1Short(string $string, bool $use_sha = false): string public static function __sha1Short(string $string, bool $use_sha = false): string
{ {
if ($use_sha) { if ($use_sha) {
// return only the first 9 characters return self::sha1Short($string);
return substr(hash('sha1', $string), 0, 9);
} else { } else {
return self::__crc32b($string); return self::__crc32b($string);
} }
} }
/**
* returns a short sha1
*
* @param string $string string to hash
* @return string hash of the string
*/
public static function sha1Short(string $string): string
{
// return only the first 9 characters
return substr(hash('sha1', $string), 0, 9);
}
/** /**
* replacemend for __crc32b call (alternate) * replacemend for __crc32b call (alternate)
* defaults to adler 32 * defaults to adler 32
@@ -63,34 +81,135 @@ class Hash
* all that create 8 char long hashes * all that create 8 char long hashes
* *
* @param string $string string to hash * @param string $string string to hash
* @param string $hash_type hash type (default adler32) * @param string $hash_type [default=STANDARD_HASH_SHORT] hash type (default adler32)
* @return string hash of the string * @return string hash of the string
* @deprecated use hashShort() of short hashes with adler 32 or hash() for other hash types
*/ */
public static function __hash( public static function __hash(
string $string, string $string,
string $hash_type = self::DEFAULT_HASH string $hash_type = self::STANDARD_HASH_SHORT
): string {
return self::hash($string, $hash_type);
}
/**
* check if hash type is valid, returns false if not
*
* @param string $hash_type
* @return bool
*/
public static function isValidHashType(string $hash_type): bool
{
if (!in_array($hash_type, hash_algos())) {
return false;
}
return true;
}
/**
* check if hash hmac type is valid, returns false if not
*
* @param string $hash_hmac_type
* @return bool
*/
public static function isValidHashHmacType(string $hash_hmac_type): bool
{
if (!in_array($hash_hmac_type, hash_hmac_algos())) {
return false;
}
return true;
}
/**
* creates a hash over string if any valid hash given.
* if no hash type set use sha256
*
* @param string $string string to hash
* @param string $hash_type [default=STANDARD_HASH] hash type (default sha256)
* @return string hash of the string
*/
public static function hash(
string $string,
string $hash_type = self::STANDARD_HASH
): string { ): string {
// if not empty, check if in valid list
if ( if (
empty($hash_type) || empty($hash_type) ||
!in_array($hash_type, hash_algos()) !in_array($hash_type, hash_algos())
) { ) {
// fallback to default hash type if none set or invalid // fallback to default hash type if empty or invalid
$hash_type = self::DEFAULT_HASH; $hash_type = self::STANDARD_HASH;
} }
return hash($hash_type, $string); return hash($hash_type, $string);
} }
/** /**
* Wrapper function for standard long hashd * creates a hash mac key
*
* @param string $string string to hash mac
* @param string $key key to use
* @param string $hash_type [default=STANDARD_HASH]
* @return string hash mac string
*/
public static function hashHmac(
string $string,
#[\SensitiveParameter]
string $key,
string $hash_type = self::STANDARD_HASH
): string {
if (
empty($hash_type) ||
!in_array($hash_type, hash_hmac_algos())
) {
// fallback to default hash type if e or invalid
$hash_type = self::STANDARD_HASH;
}
return hash_hmac($hash_type, $string, $key);
}
/**
* short hash with max length of 8, uses adler32
*
* @param string $string string to hash
* @return string hash of the string
*/
public static function hashShort(string $string): string
{
return hash(self::STANDARD_HASH_SHORT, $string);
}
/**
* Wrapper function for standard long hash
*
* @param string $string String to be hashed
* @return string Hashed string
* @deprecated use hashLong()
*/
public static function __hashLong(string $string): string
{
return self::hashLong($string);
}
/**
* Wrapper function for standard long hash, uses ripmd160
* *
* @param string $string String to be hashed * @param string $string String to be hashed
* @return string Hashed string * @return string Hashed string
*/ */
public static function __hashLong(string $string): string public static function hashLong(string $string): string
{ {
return hash(self::STANDARD_HASH_LONG, $string); return hash(self::STANDARD_HASH_LONG, $string);
} }
/**
* create standard hash basd on STANDAR_HASH, currently sha256
*
* @param string $string string in
* @return string hash of the string
*/
public static function hashStd(string $string): string
{
return self::hash($string, self::STANDARD_HASH);
}
} }
// __END__ // __END__

View File

@@ -8,39 +8,97 @@ declare(strict_types=1);
namespace CoreLibs\Create; namespace CoreLibs\Create;
use CoreLibs\Convert\Strings;
class RandomKey class RandomKey
{ {
/** @var int set the default key length it nothing else is set */
public const int KEY_LENGTH_DEFAULT = 4;
/** @var int the maximum key length allowed */
public const int KEY_LENGTH_MAX = 256;
/** @var string the default characters in the key range */
public const string KEY_CHARACTER_RANGE_DEFAULT =
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
. 'abcdefghijklmnopqrstuvwxyz'
. '0123456789';
// key generation // key generation
/** @var string */ /** @var string all the characters that are int he current radnom key range */
private static string $key_range = ''; private static string $key_character_range = '';
/** @var int */ /** @var int character count in they key character range */
private static int $one_key_length; private static int $key_character_range_length = 0;
/** @var int */ /** @var int default key lenghth */
private static int $key_length = 4; // default key length /** @deprecated Will be removed */
/** @var int */ private static int $key_length = 4;
private static int $max_key_length = 256; // max allowed length
/** /**
* if launched as class, init random key data first * if launched as class, init random key data first
*
* @param array<string> ...$key_range
*/ */
public function __construct() public function __construct(array ...$key_range)
{ {
$this->initRandomKeyData(); $this->setRandomKeyData(...$key_range);
} }
/**
* internal key range validation
*
* @param array<string> ...$key_range
* @return string
*/
private static function validateRandomKeyData(array ...$key_range): string
{
$key_character_range = Strings::buildCharStringFromLists(...$key_range);
if (strlen(self::$key_character_range) <= 1) {
return '';
}
return $key_character_range;
}
/** /**
* sets the random key range with the default values * sets the random key range with the default values
* *
* @param array<string> $key_range a list of key ranges as array
* @return void has no return * @return void has no return
* @throws \LengthException If the string length is only 1 abort
*/ */
private static function initRandomKeyData(): void public static function setRandomKeyData(array ...$key_range): void
{ {
// random key generation base string // if key range is not set
self::$key_range = join('', array_merge( if (!count($key_range)) {
range('A', 'Z'), self::$key_character_range = self::KEY_CHARACTER_RANGE_DEFAULT;
range('a', 'z'), } else {
range('0', '9') self::$key_character_range = self::validateRandomKeyData(...$key_range);
)); // random key generation base string
self::$one_key_length = strlen(self::$key_range); }
self::$key_character_range_length = strlen(self::$key_character_range);
if (self::$key_character_range_length <= 1) {
throw new \LengthException(
"The given key character range '" . self::$key_character_range . "' "
. "is too small, must be at lest two characters: "
. self::$key_character_range_length
);
}
}
/**
* get the characters for the current key characters
*
* @return string
*/
public static function getRandomKeyData(): string
{
return self::$key_character_range;
}
/**
* get the length of all random characters
*
* @return int
*/
public static function getRandomKeyDataLength(): int
{
return self::$key_character_range_length;
} }
/** /**
@@ -53,7 +111,7 @@ class RandomKey
{ {
if ( if (
$key_length > 0 && $key_length > 0 &&
$key_length <= self::$max_key_length $key_length <= self::KEY_LENGTH_MAX
) { ) {
return true; return true;
} else { } else {
@@ -67,6 +125,7 @@ class RandomKey
* *
* @param int $key_length key length * @param int $key_length key length
* @return bool true/false for set status * @return bool true/false for set status
* @deprecated This function does no longer set the key length, the randomKeyGen parameter has to b used
*/ */
public static function setRandomKeyLength(int $key_length): bool public static function setRandomKeyLength(int $key_length): bool
{ {
@@ -83,6 +142,7 @@ class RandomKey
* get the current set random key length * get the current set random key length
* *
* @return int Current set key length * @return int Current set key length
* @deprecated Key length is set during randomKeyGen call, this nethid is deprecated
*/ */
public static function getRandomKeyLength(): int public static function getRandomKeyLength(): int
{ {
@@ -94,28 +154,37 @@ class RandomKey
* if override key length is set, it will check on valid key and use this * if override key length is set, it will check on valid key and use this
* this will not set the class key length variable * this will not set the class key length variable
* *
* @param int $key_length key length override, -1 for use default * @param int $key_length [default=-1] key length override,
* @return string random key * if not set use default [LEGACY]
* @param array<string> $key_range a list of key ranges as array,
* if not set use previous set data
* @return string random key
*/ */
public static function randomKeyGen(int $key_length = -1): string public static function randomKeyGen(
{ int $key_length = self::KEY_LENGTH_DEFAULT,
// init random key strings if not set array ...$key_range
if ( ): string {
!isset(self::$one_key_length) $key_character_range = '';
) { if (count($key_range)) {
self::initRandomKeyData(); $key_character_range = self::validateRandomKeyData(...$key_range);
} $key_character_range_length = strlen($key_character_range);
$use_key_length = 0;
// only if valid int key with valid length
if (self::validateRandomKeyLenght($key_length) === true) {
$use_key_length = $key_length;
} else { } else {
$use_key_length = self::$key_length; if (!self::$key_character_range_length) {
self::setRandomKeyData();
}
$key_character_range = self::getRandomKeyData();
$key_character_range_length = self::getRandomKeyDataLength();
}
// if not valid key length, fallback to default
if (!self::validateRandomKeyLenght($key_length)) {
$key_length = self::KEY_LENGTH_DEFAULT;
} }
// create random string // create random string
$random_string = ''; $random_string = '';
for ($i = 1; $i <= $use_key_length; $i++) { for ($i = 1; $i <= $key_length; $i++) {
$random_string .= self::$key_range[random_int(0, self::$one_key_length - 1)]; $random_string .= $key_character_range[
random_int(0, $key_character_range_length - 1)
];
} }
return $random_string; return $random_string;
} }

View File

@@ -39,9 +39,9 @@ class ArrayIO extends \CoreLibs\DB\IO
{ {
// main calss variables // main calss variables
/** @var array<mixed> */ /** @var array<mixed> */
private array $table_array; // the array from the table to work on private array $table_array = []; // the array from the table to work on
/** @var string */ /** @var string */
private string $table_name; // the table_name private string $table_name = ''; // the table_name
/** @var string */ /** @var string */
private string $pk_name = ''; // the primary key from this table private string $pk_name = ''; // the primary key from this table
/** @var int|string|null */ /** @var int|string|null */
@@ -127,9 +127,9 @@ class ArrayIO extends \CoreLibs\DB\IO
public function getTableArray(bool $reset = false): array public function getTableArray(bool $reset = false): array
{ {
if (!$reset) { if (!$reset) {
return $this->table_array ?? []; return $this->table_array;
} }
$table_array = $this->table_array ?? []; $table_array = $this->table_array;
reset($table_array); reset($table_array);
return $table_array; return $table_array;
} }
@@ -194,7 +194,7 @@ class ArrayIO extends \CoreLibs\DB\IO
*/ */
public function getTableName(): string public function getTableName(): string
{ {
return $this->table_name ?? ''; return $this->table_name;
} }
/** /**

View File

@@ -303,6 +303,8 @@ class IO
private string $query = ''; private string $query = '';
/** @var array<mixed> current params for query */ /** @var array<mixed> current params for query */
private array $params = []; private array $params = [];
/** @var string current hash build from query and params */
private string $query_hash = '';
// if we do have a convert call, store the convert data in here, else it will be empty // 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<mixed>},type:''|'named'|'numbered'|'question_mark',found:int,matches:array<string>,params_lookup:array<mixed>,query:string,params:array<mixed>} */ /** @var array{}|array{original:array{query:string,params:array<mixed>},type:''|'named'|'numbered'|'question_mark',found:int,matches:array<string>,params_lookup:array<mixed>,query:string,params:array<mixed>} */
private array $placeholder_converted = []; private array $placeholder_converted = [];
@@ -500,7 +502,7 @@ class IO
die('<!-- Cannot load db functions class for: ' . $this->db_type . ' -->'); die('<!-- Cannot load db functions class for: ' . $this->db_type . ' -->');
} }
// write to internal one, once OK // write to internal one, once OK
$this->db_functions = $db_functions; $this->db_functions = $db_functions; /** @phan-suppress-current-line PhanPossiblyNullTypeMismatchProperty */
// connect to DB // connect to DB
if (!$this->__connectToDB()) { if (!$this->__connectToDB()) {
@@ -1319,7 +1321,7 @@ class IO
*/ */
private function __dbCountQueryParams(string $query): int private function __dbCountQueryParams(string $query): int
{ {
return $this->db_functions->__dbCountQueryParams($query); return count($this->db_functions->__dbGetQueryParams($query));
} }
/** /**
@@ -1382,6 +1384,8 @@ class IO
$this->query = $query; $this->query = $query;
// current params // current params
$this->params = $params; $this->params = $params;
// empty on new
$this->query_hash = '';
// no query set // no query set
if (empty($this->query)) { if (empty($this->query)) {
$this->__dbError(11); $this->__dbError(11);
@@ -1413,10 +1417,7 @@ class IO
$this->pk_name_table[$table] ? $this->pk_name_table[$table] ?
$this->pk_name_table[$table] : 'NULL'; $this->pk_name_table[$table] : 'NULL';
} }
if ( if (!preg_match(self::REGEX_RETURNING, $this->query) && $this->pk_name != 'NULL') {
!preg_match(self::REGEX_RETURNING, $this->query) &&
$this->pk_name && $this->pk_name != 'NULL'
) {
// check if this query has a ; at the end and remove it // check if this query has a ; at the end and remove it
$__query = preg_replace("/(;\s*)$/", '', $this->query); $__query = preg_replace("/(;\s*)$/", '', $this->query);
// must be query, if preg replace failed, use query as before // must be query, if preg replace failed, use query as before
@@ -1426,7 +1427,7 @@ class IO
} elseif ( } elseif (
preg_match(self::REGEX_RETURNING, $this->query, $matches) preg_match(self::REGEX_RETURNING, $this->query, $matches)
) { ) {
if ($this->pk_name && $this->pk_name != 'NULL') { if ($this->pk_name != 'NULL') {
// add the primary key if it is not in the returning set // add the primary key if it is not in the returning set
if (!preg_match("/$this->pk_name/", $matches[1])) { if (!preg_match("/$this->pk_name/", $matches[1])) {
$this->query .= " , " . $this->pk_name; $this->query .= " , " . $this->pk_name;
@@ -1444,7 +1445,7 @@ class IO
$this->returning_id = true; $this->returning_id = true;
} }
// import protection, hash needed // import protection, hash needed
$query_hash = $this->dbGetQueryHash($this->query, $this->params); $query_hash = $this->dbBuildQueryHash($this->query, $this->params);
// QUERY PARAMS: run query params check and rewrite // QUERY PARAMS: run query params check and rewrite
if ($this->dbGetConvertPlaceholder() === true) { if ($this->dbGetConvertPlaceholder() === true) {
try { try {
@@ -1478,7 +1479,8 @@ class IO
return false; return false;
} }
} }
// set query hash
$this->query_hash = $query_hash;
// $this->debug('DB IO', 'Q: ' . $this->query . ', RETURN: ' . $this->returning_id); // $this->debug('DB IO', 'Q: ' . $this->query . ', RETURN: ' . $this->returning_id);
// for DEBUG, only on first time ;) // for DEBUG, only on first time ;)
$this->__dbDebug( $this->__dbDebug(
@@ -1962,7 +1964,7 @@ class IO
{ {
// set start array // set start array
if ($query) { if ($query) {
$array = $this->cursor_ext[$this->dbGetQueryHash($query)] ?? []; $array = $this->cursor_ext[$this->dbBuildQueryHash($query)] ?? [];
} else { } else {
$array = $this->cursor_ext; $array = $this->cursor_ext;
} }
@@ -2364,7 +2366,7 @@ class IO
return false; return false;
} }
// create hash from query ... // create hash from query ...
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
// pre declare array // pre declare array
if (!isset($this->cursor_ext[$query_hash])) { if (!isset($this->cursor_ext[$query_hash])) {
$this->cursor_ext[$query_hash] = [ $this->cursor_ext[$query_hash] = [
@@ -2542,7 +2544,10 @@ class IO
} // only go if NO cursor exists } // only go if NO cursor exists
// if cursor exists ... // if cursor exists ...
if ($this->cursor_ext[$query_hash]['cursor']) { if (
$this->cursor_ext[$query_hash]['cursor'] instanceof \PgSql\Result ||
$this->cursor_ext[$query_hash]['cursor'] == 1
) {
if ($first_call === true) { if ($first_call === true) {
$this->cursor_ext[$query_hash]['log'][] = 'First call'; $this->cursor_ext[$query_hash]['log'][] = 'First call';
// count the rows returned (if select) // count the rows returned (if select)
@@ -2940,13 +2945,15 @@ class IO
* data to create a unique call one, optional * data to create a unique call one, optional
* @return bool False if query not found, true if success * @return bool False if query not found, true if success
*/ */
public function dbCacheReset(string $query, array $params = []): bool public function dbCacheReset(string $query, array $params = [], bool $show_warning = true): bool
{ {
$this->__dbErrorReset(); $query_hash = $this->dbBuildQueryHash($query, $params);
$query_hash = $this->dbGetQueryHash($query, $params);
// clears cache for this query // clears cache for this query
if (empty($this->cursor_ext[$query_hash]['query'])) { if (
$this->__dbError(18, context: [ $show_warning &&
empty($this->cursor_ext[$query_hash]['query'])
) {
$this->__dbWarning(18, context: [
'query' => $query, 'query' => $query,
'params' => $params, 'params' => $params,
'hash' => $query_hash, 'hash' => $query_hash,
@@ -2985,7 +2992,7 @@ class IO
if ($query === null) { if ($query === null) {
return $this->cursor_ext; return $this->cursor_ext;
} }
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
if ( if (
!empty($this->cursor_ext) && !empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash]) isset($this->cursor_ext[$query_hash])
@@ -3015,7 +3022,7 @@ class IO
$this->__dbError(11); $this->__dbError(11);
return false; return false;
} }
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
if ( if (
!empty($this->cursor_ext) && !empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash]) isset($this->cursor_ext[$query_hash])
@@ -3041,7 +3048,7 @@ class IO
$this->__dbError(11); $this->__dbError(11);
return false; return false;
} }
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
if ( if (
!empty($this->cursor_ext) && !empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash]) isset($this->cursor_ext[$query_hash])
@@ -3067,7 +3074,7 @@ class IO
*/ */
public function dbResetQueryCalled(string $query, array $params = []): void public function dbResetQueryCalled(string $query, array $params = []): void
{ {
$this->query_called[$this->dbGetQueryHash($query, $params)] = 0; $this->query_called[$this->dbBuildQueryHash($query, $params)] = 0;
} }
/** /**
@@ -3080,7 +3087,7 @@ class IO
*/ */
public function dbGetQueryCalled(string $query, array $params = []): int public function dbGetQueryCalled(string $query, array $params = []): int
{ {
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
if (!empty($this->query_called[$query_hash])) { if (!empty($this->query_called[$query_hash])) {
return $this->query_called[$query_hash]; return $this->query_called[$query_hash];
} else { } else {
@@ -3141,6 +3148,7 @@ class IO
'pk_name' => '', 'pk_name' => '',
'count' => 0, 'count' => 0,
'query' => '', 'query' => '',
'query_raw' => $query,
'result' => null, 'result' => null,
'returning_id' => false, 'returning_id' => false,
'placeholder_converted' => [], 'placeholder_converted' => [],
@@ -3237,11 +3245,12 @@ class IO
} }
} else { } else {
// if we try to use the same statement name for a differnt query, error abort // if we try to use the same statement name for a differnt query, error abort
if ($this->prepare_cursor[$stm_name]['query'] != $query) { if ($this->prepare_cursor[$stm_name]['query_raw'] != $query) {
// thrown error // thrown error
$this->__dbError(26, false, context: [ $this->__dbError(26, false, context: [
'statement_name' => $stm_name, 'statement_name' => $stm_name,
'prepared_query' => $this->prepare_cursor[$stm_name]['query'], 'prepared_query' => $this->prepare_cursor[$stm_name]['query'],
'prepared_query_raw' => $this->prepare_cursor[$stm_name]['query_raw'],
'query' => $query, 'query' => $query,
'pk_name' => $pk_name, 'pk_name' => $pk_name,
]); ]);
@@ -4047,7 +4056,7 @@ class IO
} }
/** /**
* Returns hash for query * Creates hash for query and parameters
* Hash is used in all internal storage systems for return data * Hash is used in all internal storage systems for return data
* *
* @param string $query The query to create the hash from * @param string $query The query to create the hash from
@@ -4055,9 +4064,9 @@ class IO
* data to create a unique call one, optional * data to create a unique call one, optional
* @return string Hash, as set by hash long * @return string Hash, as set by hash long
*/ */
public function dbGetQueryHash(string $query, array $params = []): string public function dbBuildQueryHash(string $query, array $params = []): string
{ {
return Hash::__hashLong( return Hash::hashLong(
$query . ( $query . (
$params !== [] ? $params !== [] ?
'#' . json_encode($params) : '' '#' . json_encode($params) : ''
@@ -4105,6 +4114,26 @@ class IO
$this->params = []; $this->params = [];
} }
/**
* get the current set query hash
*
* @return string Current Query hash
*/
public function dbGetQueryHash(): string
{
return $this->query_hash;
}
/**
* reset query hash
*
* @return void
*/
public function dbResetQueryHash(): void
{
$this->query_hash = '';
}
/** /**
* Returns the placeholder convert set or empty * Returns the placeholder convert set or empty
* *
@@ -4284,6 +4313,17 @@ class IO
return $this->field_names[$pos] ?? false; return $this->field_names[$pos] ?? false;
} }
/**
* get all the $ placeholders
*
* @param string $query
* @return array<string>
*/
public function dbGetQueryParamPlaceholders(string $query): array
{
return $this->db_functions->__dbGetQueryParams($query);
}
/** /**
* Return a field type for a field name or pos, * Return a field type for a field name or pos,
* will return false if field is not found in list * will return false if field is not found in list
@@ -4364,6 +4404,37 @@ class IO
return $this->prepare_cursor[$stm_name][$key]; return $this->prepare_cursor[$stm_name][$key];
} }
/**
* Checks if a prepared query eixsts
*
* @param string $stm_name Statement to check
* @param string $query [default=''] If set then query must also match
* @return false|int<0,2> False on missing stm_name
* 0: ok, 1: stm_name matchin, 2: stm_name and query matching
*/
public function dbPreparedCursorStatus(string $stm_name, string $query = ''): false|int
{
if (empty($stm_name)) {
$this->__dbError(
101,
false,
'No statement name given'
);
return false;
}
// does not exist
$return_value = 0;
if (!empty($this->prepare_cursor[$stm_name]['query_raw'])) {
// statement name eixts
$return_value = 1;
if ($this->prepare_cursor[$stm_name]['query_raw'] == $query) {
// query also matches
$return_value = 2;
}
}
return $return_value;
}
// *************************** // ***************************
// ERROR AND WARNING DATA // ERROR AND WARNING DATA
// *************************** // ***************************

View File

@@ -379,9 +379,9 @@ interface SqlFunctions
* Undocumented function * Undocumented function
* *
* @param string $query * @param string $query
* @return int * @return array<string>
*/ */
public function __dbCountQueryParams(string $query): int; public function __dbGetQueryParams(string $query): array;
} }
// __END__ // __END__

View File

@@ -978,12 +978,12 @@ class PgSQL implements Interface\SqlFunctions
} }
/** /**
* Count placeholder queries. $ only * Get the all the $ params, as a unique list
* *
* @param string $query * @param string $query
* @return int * @return array<string>
*/ */
public function __dbCountQueryParams(string $query): int public function __dbGetQueryParams(string $query): array
{ {
$matches = []; $matches = [];
// regex for params: only stand alone $number allowed // regex for params: only stand alone $number allowed
@@ -998,11 +998,11 @@ class PgSQL implements Interface\SqlFunctions
// Matches in 1:, must be array_filtered to remove empty, count with array_unique // Matches in 1:, must be array_filtered to remove empty, count with array_unique
// Regex located in the ConvertPlaceholder class // Regex located in the ConvertPlaceholder class
preg_match_all( preg_match_all(
ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS, ConvertPlaceholder::REGEX_LOOKUP_NUMBERED,
$query, $query,
$matches $matches
); );
return count(array_unique(array_filter($matches[3]))); return array_unique(array_filter($matches[ConvertPlaceholder::MATCHING_POS]));
} }
} }

View File

@@ -14,76 +14,57 @@ namespace CoreLibs\DB\Support;
class ConvertPlaceholder class ConvertPlaceholder
{ {
// NOTE for missing: range */+ are not iplemented in the regex below, but - is for now /** @var string text block in SQL, single quited
// NOTE some combinations are allowed, but the query will fail before this * Note that does not include $$..$$ strings or anything with token name or nested ones
/** @var string split regex, entries before $ group */ */
private const PATTERN_QUERY_SPLIT = private const PATTERN_TEXT_BLOCK_SINGLE_QUOTE = '(?:\'(?:[^\'\\\\]|\\\\.)*\')';
'\?\?|' // UNKNOWN: double ??, is this to avoid something? /** @var string text block in SQL, dollar quoted
. '[\(,]|' // for ',' and '(' mostly in INSERT or ANY() * NOTE: if this is added everything shifts by one lookup number
. '[<>=]|' // general set for <, >, = in any query with any combination */
. '\^@|' // text search for start from text with ^@ private const PATTERN_TEXT_BLOCK_DOLLAR = '(?:\$(\w*)\$.*?\$\1\$)';
. '\|\||' // concats two elements
. '&&|' // array overlap
. '\-\|\-|' // range overlap for array
. '[^-]-{1}|' // single -, used in JSON too
. '->|->>|#>|#>>|@>|<@|@@|@\?|\?{1}|\?\||\?&|#-|' // JSON searches, Array searchs, etc
. 'THEN|ELSE' // command parts (CASE)
;
/** @var string the main regex including the pattern query split */
private const PATTERN_ELEMENT = '(?:\'.*?\')?\s*(?:' . self::PATTERN_QUERY_SPLIT . ')\s*';
/** @var string comment regex /** @var string comment regex
* anything that starts with -- and ends with a line break but any character that is not line break inbetween */ * anything that starts with -- and ends with a line break but any character that is not line break inbetween
private const PATTERN_COMMENT = '(?:\-\-[^\r\n]*?\r?\n)*\s*'; * this is the FIRST thing in the line and will skip any further lookups */
/** @var string parts to ignore in the SQL */ private const PATTERN_COMMENT = '(?:\-\-[^\r\n]*?\r?\n)';
private const PATTERN_IGNORE = // below are the params lookups
// digit -> ignore /** @var string named parameters, must start with single : */
'\d+|' private const PATTERN_NAMED = '((?<!:):(?:\w+))';
// other string -> ignore /** @var string question mark parameters, will catch any */
. '(?:\'.*?\')|'; private const PATTERN_QUESTION_MARK = '(\?{1})';
/** @var string named parameters */ /** @var string numbered parameters, can only start 1 to 9, second and further digits can be 0-9
private const PATTERN_NAMED = '(:\w+)'; * This ignores the $$ ... $$ escape syntax. If we find something like this will fail
/** @var string question mark parameters */ * It is recommended to use proper string escape quiting for writing data to the DB
private const PATTERN_QUESTION_MARK = '(?:(?:\?\?)?\s*(\?{1}))'; */
/** @var string numbered parameters */
private const PATTERN_NUMBERED = '(\$[1-9]{1}(?:[0-9]{1,})?)'; private const PATTERN_NUMBERED = '(\$[1-9]{1}(?:[0-9]{1,})?)';
// below here are full regex that will be used // below here are full regex that will be used
/** @var string replace regex for named (:...) entries */ /** @var string replace regex for named (:...) entries */
public const REGEX_REPLACE_NAMED = '/' public const REGEX_REPLACE_NAMED = '/'
. '(' . self::PATTERN_ELEMENT . ')' . self::PATTERN_COMMENT . '|'
. self::PATTERN_COMMENT . self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. '(' . self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_IGNORE
. self::PATTERN_NAMED . self::PATTERN_NAMED
. ')'
. '/s'; . '/s';
/** @var string replace regex for question mark (?) entries */ /** @var string replace regex for question mark (?) entries */
public const REGEX_REPLACE_QUESTION_MARK = '/' public const REGEX_REPLACE_QUESTION_MARK = '/'
. '(' . self::PATTERN_ELEMENT . ')' . self::PATTERN_COMMENT . '|'
. self::PATTERN_COMMENT . self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. '(' . self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_IGNORE
. self::PATTERN_QUESTION_MARK . self::PATTERN_QUESTION_MARK
. ')'
. '/s'; . '/s';
/** @var string replace regex for numbered ($n) entries */ /** @var string replace regex for numbered ($n) entries */
public const REGEX_REPLACE_NUMBERED = '/' public const REGEX_REPLACE_NUMBERED = '/'
. '(' . self::PATTERN_ELEMENT . ')' . self::PATTERN_COMMENT . '|'
. self::PATTERN_COMMENT . self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. '(' . self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_IGNORE
. self::PATTERN_NUMBERED . self::PATTERN_NUMBERED
. ')'
. '/s'; . '/s';
/** @var string the main lookup query for all placeholders */ /** @var string the main lookup query for all placeholders */
public const REGEX_LOOKUP_PLACEHOLDERS = '/' public const REGEX_LOOKUP_PLACEHOLDERS = '/'
// prefix string part, must match towards . self::PATTERN_COMMENT . '|'
// seperator for ( = , ? - [and json/jsonb in pg doc section 9.15] . self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. self::PATTERN_ELEMENT . self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
. self::PATTERN_COMMENT
// match for replace part // match for replace part
. '(?:' . '(?:'
// ignore parts
. self::PATTERN_IGNORE
// :name named part (PDO) [1] // :name named part (PDO) [1]
. self::PATTERN_NAMED . '|' . self::PATTERN_NAMED . '|'
// ? question mark part (PDO) [2] // ? question mark part (PDO) [2]
@@ -94,6 +75,26 @@ class ConvertPlaceholder
. ')' . ')'
// single line -> add line break to matches in "." // single line -> add line break to matches in "."
. '/s'; . '/s';
/** @var string lookup for only numbered placeholders */
public const REGEX_LOOKUP_NUMBERED = '/'
. self::PATTERN_COMMENT . '|'
. self::PATTERN_TEXT_BLOCK_SINGLE_QUOTE . '|'
. self::PATTERN_TEXT_BLOCK_DOLLAR . '|'
// match for replace part
. '(?:'
// $n numbered part (\PG php) [1]
. self::PATTERN_NUMBERED
// end match
. ')'
. '/s';
/** @var int position for regex in full placeholder lookup: named */
public const LOOOKUP_NAMED_POS = 2;
/** @var int position for regex in full placeholder lookup: question mark */
public const LOOOKUP_QUESTION_MARK_POS = 3;
/** @var int position for regex in full placeholder lookup: numbered */
public const LOOOKUP_NUMBERED_POS = 4;
/** @var int matches position for replacement and single lookup */
public const MATCHING_POS = 2;
/** /**
* Convert PDO type query with placeholders to \PG style and vica versa * Convert PDO type query with placeholders to \PG style and vica versa
@@ -132,11 +133,12 @@ class ConvertPlaceholder
$found = -1; $found = -1;
} }
/** @var array<string> 1: named */ /** @var array<string> 1: named */
$named_matches = array_filter($matches[1]); $named_matches = array_filter($matches[self::LOOOKUP_NAMED_POS]);
/** @var array<string> 2: open ? */ /** @var array<string> 2: open ? */
$qmark_matches = array_filter($matches[2]); $qmark_matches = array_filter($matches[self::LOOOKUP_QUESTION_MARK_POS]);
/** @var array<string> 3: $n matches */ /** @var array<string> 3: $n matches */
$numbered_matches = array_filter($matches[3]); $numbered_matches = array_filter($matches[self::LOOOKUP_NUMBERED_POS]);
// print "**MATCHES**: <pre>" . print_r($matches, true) . "</pre>";
// count matches // count matches
$count_named = count(array_unique($named_matches)); $count_named = count(array_unique($named_matches));
$count_qmark = count($qmark_matches); $count_qmark = count($qmark_matches);
@@ -235,38 +237,37 @@ class ConvertPlaceholder
$empty_params = $converted_placeholders['original']['empty_params']; $empty_params = $converted_placeholders['original']['empty_params'];
switch ($converted_placeholders['type']) { switch ($converted_placeholders['type']) {
case 'named': case 'named':
// 0: full // 1: replace part :named
// 0: full
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part :named
$pos = 0; $pos = 0;
$query_new = preg_replace_callback( $query_new = preg_replace_callback(
self::REGEX_REPLACE_NAMED, self::REGEX_REPLACE_NAMED,
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) { function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) {
// only count up if $match[3] is not yet in lookup table if (!isset($matches[self::MATCHING_POS])) {
if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) { throw new \RuntimeException(
'Cannot lookup ' . self::MATCHING_POS . ' in matches list',
209
);
}
$match = $matches[self::MATCHING_POS];
// only count up if $match[1] is not yet in lookup table
if (empty($params_lookup[$match])) {
$pos++; $pos++;
$params_lookup[$matches[3]] = '$' . $pos; $params_lookup[$match] = '$' . $pos;
// skip params setup if param list is empty // skip params setup if param list is empty
if (!$empty_params) { if (!$empty_params) {
$params_new[] = $params[$matches[3]] ?? $params_new[] = $params[$match] ??
throw new \RuntimeException( throw new \RuntimeException(
'Cannot lookup ' . $matches[3] . ' in params list', 'Cannot lookup ' . $match . ' in params list',
210 210
); );
} }
} }
// add the connectors back (1), and the data sets only if no replacement will be done // add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . ( return $params_lookup[$match] ??
empty($matches[3]) ? throw new \RuntimeException(
$matches[2] : 'Cannot lookup ' . $match . ' in params lookup list',
$params_lookup[$matches[3]] ?? 211
throw new \RuntimeException( );
'Cannot lookup ' . $matches[3] . ' in params lookup list',
211
)
);
}, },
$converted_placeholders['original']['query'] $converted_placeholders['original']['query']
); );
@@ -276,61 +277,61 @@ class ConvertPlaceholder
// order and data stays the same // order and data stays the same
$params_new = $params ?? []; $params_new = $params ?? [];
} }
// 0: full // 1: replace part ?
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part ?
$pos = 0; $pos = 0;
$query_new = preg_replace_callback( $query_new = preg_replace_callback(
self::REGEX_REPLACE_QUESTION_MARK, self::REGEX_REPLACE_QUESTION_MARK,
function ($matches) use (&$pos, &$params_lookup) { function ($matches) use (&$pos, &$params_lookup) {
if (!isset($matches[self::MATCHING_POS])) {
throw new \RuntimeException(
'Cannot lookup ' . self::MATCHING_POS . ' in matches list',
229
);
}
$match = $matches[self::MATCHING_POS];
// only count pos up for actual replacements we will do // only count pos up for actual replacements we will do
if (!empty($matches[3])) { if (!empty($match)) {
$pos++; $pos++;
$params_lookup[] = '$' . $pos; $params_lookup[] = '$' . $pos;
} }
// add the connectors back (1), and the data sets only if no replacement will be done // add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . ( return '$' . $pos;
empty($matches[3]) ?
$matches[2] :
'$' . $pos
);
}, },
$converted_placeholders['original']['query'] $converted_placeholders['original']['query']
); );
break; break;
case 'numbered': case 'numbered':
// 0: full // 1: replace part $numbered
// 1: pre part
// 2: keep part UNLESS '3' is set
// 3: replace part $numbered
$pos = 0; $pos = 0;
$query_new = preg_replace_callback( $query_new = preg_replace_callback(
self::REGEX_REPLACE_NUMBERED, self::REGEX_REPLACE_NUMBERED,
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) { function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) {
// only count up if $match[3] is not yet in lookup table if (!isset($matches[self::MATCHING_POS])) {
if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) { throw new \RuntimeException(
'Cannot lookup ' . self::MATCHING_POS . ' in matches list',
239
);
}
$match = $matches[self::MATCHING_POS];
// only count up if $match[1] is not yet in lookup table
if (empty($params_lookup[$match])) {
$pos++; $pos++;
$params_lookup[$matches[3]] = ':' . $pos . '_named'; $params_lookup[$match] = ':' . $pos . '_named';
// skip params setup if param list is empty // skip params setup if param list is empty
if (!$empty_params) { if (!$empty_params) {
$params_new[] = $params[($pos - 1)] ?? $params_new[] = $params[($pos - 1)] ??
throw new \RuntimeException( throw new \RuntimeException(
'Cannot lookup ' . ($pos - 1) . ' in params list', 'Cannot lookup ' . ($pos - 1) . ' in params list',
220 230
); );
} }
} }
// add the connectors back (1), and the data sets only if no replacement will be done // add the connectors back (1), and the data sets only if no replacement will be done
return $matches[1] . ( return $params_lookup[$match] ??
empty($matches[3]) ? throw new \RuntimeException(
$matches[2] : 'Cannot lookup ' . $match . ' in params lookup list',
$params_lookup[$matches[3]] ?? 231
throw new \RuntimeException( );
'Cannot lookup ' . $matches[3] . ' in params lookup list',
221
)
);
}, },
$converted_placeholders['original']['query'] $converted_placeholders['original']['query']
); );

View File

@@ -0,0 +1,95 @@
<?php
/**
* AUTHOR: Clemens Schwaighofer
* CREATED: 2025/1/17
* DESCRIPTION:
* Deprecated helper for fputcsv
*/
declare(strict_types=1);
namespace CoreLibs\DeprecatedHelper;
use InvalidArgumentException;
class Deprecated84
{
/**
* This is a wrapper for fputcsv to fix deprecated warning for $escape parameter
* See: https://www.php.net/manual/en/function.fputcsv.php
* escape parameter deprecation and recommend to set to "" for compatible with PHP 9.0
*
* @param mixed $stream
* @param array<mixed> $fields
* @param string $separator
* @param string $enclosure
* @param string $escape
* @param string $eol
* @return int|false
* @throws InvalidArgumentException
*/
public static function fputcsv(
mixed $stream,
array $fields,
string $separator = ",",
string $enclosure = '"',
string $escape = '', // set to empty for future compatible
string $eol = PHP_EOL
): int | false {
if (!is_resource($stream)) {
throw new \InvalidArgumentException("fputcsv stream parameter must be a resrouce");
}
return fputcsv($stream, $fields, $separator, $enclosure, $escape, $eol);
}
/**
* This is a wrapper for fgetcsv to fix deprecated warning for $escape parameter
* See: https://www.php.net/manual/en/function.fgetcsv.php
* escape parameter deprecation and recommend to set to "" for compatible with PHP 9.0
*
* @param mixed $stream
* @param null|int<0,max> $length
* @param string $separator
* @param string $enclosure
* @param string $escape
* @return array<mixed>|false
* @throws InvalidArgumentException
*/
public static function fgetcsv(
mixed $stream,
?int $length = null,
string $separator = ',',
string $enclosure = '"',
string $escape = '' // set to empty for future compatible
): array | false {
if (!is_resource($stream)) {
throw new \InvalidArgumentException("fgetcsv stream parameter must be a resrouce");
}
return fgetcsv($stream, $length, $separator, $enclosure, $escape);
}
/**
* This is a wrapper for str_getcsv to fix deprecated warning for $escape parameter
* See: https://www.php.net/manual/en/function.str-getcsv.php
* escape parameter deprecation and recommend to set to "" for compatible with PHP 9.0
*
* @param string $string
* @param string $separator
* @param string $enclosure
* @param string $escape
* @return array<mixed>
*/
// phpcs:disable PSR1.Methods.CamelCapsMethodName
public static function str_getcsv(
string $string,
string $separator = ",",
string $enclosure = '"',
string $escape = '' // set to empty for future compatible
): array {
return str_getcsv($string, $separator, $enclosure, $escape);
}
// phpcs:enable PSR1.Methods.CamelCapsMethodName
}
// __END__

View File

@@ -50,7 +50,6 @@ class GetLocale
$locale = defined('SITE_LOCALE') && !empty(SITE_LOCALE) ? $locale = defined('SITE_LOCALE') && !empty(SITE_LOCALE) ?
SITE_LOCALE : SITE_LOCALE :
// else parse from default, if not 'en' // else parse from default, if not 'en'
/** @phpstan-ignore-next-line DEFAULT_LOCALE could be empty */
(defined('DEFAULT_LOCALE') && !empty(DEFAULT_LOCALE) ? (defined('DEFAULT_LOCALE') && !empty(DEFAULT_LOCALE) ?
DEFAULT_LOCALE : 'en'); DEFAULT_LOCALE : 'en');
} }
@@ -97,8 +96,7 @@ class GetLocale
$encoding = defined('SITE_ENCODING') && !empty(SITE_ENCODING) ? $encoding = defined('SITE_ENCODING') && !empty(SITE_ENCODING) ?
SITE_ENCODING : SITE_ENCODING :
// or default encoding, if not 'UTF-8' // or default encoding, if not 'UTF-8'
/** @phpstan-ignore-next-line DEFAULT_LOCALE could be empty */ (defined('DEFAULT_ENCODING') ?
(defined('DEFAULT_ENCODING') && !empty(DEFAULT_ENCODING) ?
DEFAULT_ENCODING : 'UTF-8'); DEFAULT_ENCODING : 'UTF-8');
} }
} }

View File

@@ -30,7 +30,12 @@ class Logging
{ {
/** @var int minimum size for a max file size, so we don't set 1 byte, 10kb */ /** @var int minimum size for a max file size, so we don't set 1 byte, 10kb */
public const MIN_LOG_MAX_FILESIZE = 10 * 1024; public const MIN_LOG_MAX_FILESIZE = 10 * 1024;
/** @var string log file extension, not changeable */
private const LOG_FILE_NAME_EXT = "log";
/** @var string log file block separator, not changeable */
private const LOG_FILE_BLOCK_SEPARATOR = '.';
// MARK: OPTION array
// NOTE: the second party array{} hs some errors // NOTE: the second party array{} hs some errors
/** @var array<string,array<string,string|bool|Level>>|array{string:array{type:string,type_info?:string,mandatory:true,alias?:string,default:string|bool|Level,deprecated:bool,use?:string}} */ /** @var array<string,array<string,string|bool|Level>>|array{string:array{type:string,type_info?:string,mandatory:true,alias?:string,default:string|bool|Level,deprecated:bool,use?:string}} */
private const OPTIONS = [ private const OPTIONS = [
@@ -46,6 +51,7 @@ class Logging
'type' => 'string', 'mandatory' => false, 'type' => 'string', 'mandatory' => false,
'default' => '', 'deprecated' => true, 'use' => 'log_file_id' 'default' => '', 'deprecated' => true, 'use' => 'log_file_id'
], ],
// log level
'log_level' => [ 'log_level' => [
'type' => 'instance', 'type' => 'instance',
'type_info' => '\CoreLibs\Logging\Logger\Level', 'type_info' => '\CoreLibs\Logging\Logger\Level',
@@ -53,6 +59,14 @@ class Logging
'default' => Level::Debug, 'default' => Level::Debug,
'deprecated' => false 'deprecated' => false
], ],
// level to trigger write to error_log
'error_log_write_level' => [
'type' => 'instance',
'type_info' => '\CoreLibs\Logging\Logger\Level',
'mandatory' => false,
'default' => Level::Emergency,
'deprecated' => false,
],
// options // options
'log_per_run' => [ 'log_per_run' => [
'type' => 'bool', 'mandatory' => false, 'type' => 'bool', 'mandatory' => false,
@@ -88,8 +102,10 @@ class Logging
/** @var array<mixed> */ /** @var array<mixed> */
private array $options = []; private array $options = [];
/** @var Level set level */ /** @var Level set logging level */
private Level $log_level; private Level $log_level;
/** @var Level set level for writing to error_log, will not write if log level lower than error log write level */
private Level $error_log_write_level;
// page and host name // page and host name
/** @var string */ /** @var string */
@@ -104,8 +120,6 @@ class Logging
private string $log_folder = ''; private string $log_folder = '';
/** @var string a alphanumeric name that has to be set as global definition */ /** @var string a alphanumeric name that has to be set as global definition */
private string $log_file_id = ''; private string $log_file_id = '';
/** @var string log file name extension */
private string $log_file_name_ext = 'log';
/** @var string log file name with folder, for actual writing */ /** @var string log file name with folder, for actual writing */
private string $log_file_name = ''; private string $log_file_name = '';
/** @var int set in bytes */ /** @var int set in bytes */
@@ -143,12 +157,13 @@ class Logging
]; ];
/** /**
* Init logger * MARK: Init logger
* *
* options array layout * options array layout
* - log_folder: * - log_folder:
* - log_file_id / file_id (will be deprecated): * - log_file_id / file_id (will be deprecated):
* - log_level: * - log_level:
* - error_log_write_level: at what level we write to error_log
* *
* - log_per_run: * - log_per_run:
* - log_per_date: (was print_file_date) * - log_per_date: (was print_file_date)
@@ -170,6 +185,8 @@ class Logging
// set log level // set log level
$this->initLogLevel(); $this->initLogLevel();
// set error log write level
$this->initErrorLogWriteLevel();
// set log folder from options // set log folder from options
$this->initLogFolder(); $this->initLogFolder();
// set per run UID for logging // set per run UID for logging
@@ -188,8 +205,10 @@ class Logging
// PRIVATE METHODS // PRIVATE METHODS
// ********************************************************************* // *********************************************************************
// MARK: options check
/** /**
* Undocumented function * validate options
* *
* @param array<mixed> $options * @param array<mixed> $options
* @return bool * @return bool
@@ -261,6 +280,8 @@ class Logging
return true; return true;
} }
// MARK: init log elvels
/** /**
* init log level, just a wrapper to auto set from options * init log level, just a wrapper to auto set from options
* *
@@ -278,6 +299,24 @@ class Logging
$this->setLoggingLevel($this->options['log_level']); $this->setLoggingLevel($this->options['log_level']);
} }
/**
* init error log write level
*
* @return void
*/
private function initErrorLogWriteLevel()
{
if (
empty($this->options['error_log_write_level']) ||
!$this->options['error_log_write_level'] instanceof Level
) {
$this->options['error_log_write_level'] = Level::Emergency;
}
$this->setErrorLogWriteLevel($this->options['error_log_write_level']);
}
// MARK: set log folder
/** /**
* Set the log folder * Set the log folder
* If folder is not writeable the script will throw an E_USER_ERROR * If folder is not writeable the script will throw an E_USER_ERROR
@@ -319,6 +358,8 @@ class Logging
return $status; return $status;
} }
// MARK: set host name
/** /**
* Set the hostname and port * Set the hostname and port
* If port is not defaul 80 it will be added to the host name * If port is not defaul 80 it will be added to the host name
@@ -335,6 +376,8 @@ class Logging
} }
} }
// MARK: set log file id (file)
/** /**
* set log file prefix id * set log file prefix id
* *
@@ -393,6 +436,8 @@ class Logging
return $status; return $status;
} }
// MARK init log flags and levels
/** /**
* set flags from options and option flags connection internal settings * set flags from options and option flags connection internal settings
* *
@@ -421,6 +466,19 @@ class Logging
return $this->log_level->includes($level); return $this->log_level->includes($level);
} }
/**
* Checks that given level is matchins error_log write level
*
* @param Level $level
* @return bool
*/
private function checkErrorLogWriteLevel(Level $level): bool
{
return $this->error_log_write_level->includes($level);
}
// MARK: build log ifle name
/** /**
* Build the file name for writing * Build the file name for writing
* *
@@ -431,7 +489,7 @@ class Logging
private function buildLogFileName(Level $level, string $group_id = ''): string private function buildLogFileName(Level $level, string $group_id = ''): string
{ {
// init base file path // init base file path
$fn = $this->log_print_file . '.' . $this->log_file_name_ext; $fn = $this->log_print_file . '.' . self::LOG_FILE_NAME_EXT;
// log ID prefix settings, if not valid, replace with empty // log ID prefix settings, if not valid, replace with empty
if (!empty($this->log_file_id)) { if (!empty($this->log_file_id)) {
$rpl_string = $this->log_file_id; $rpl_string = $this->log_file_id;
@@ -440,14 +498,15 @@ class Logging
} }
$fn = str_replace('{LOGID}', $rpl_string, $fn); // log id (like a log file prefix) $fn = str_replace('{LOGID}', $rpl_string, $fn); // log id (like a log file prefix)
$rpl_string = !$this->getLogFlag(Flag::per_level) ? '' : $rpl_string = $this->getLogFlag(Flag::per_level) ?
'_' . $level->getName(); self::LOG_FILE_BLOCK_SEPARATOR . $level->getName() :
'';
$fn = str_replace('{LEVEL}', $rpl_string, $fn); // create output filename $fn = str_replace('{LEVEL}', $rpl_string, $fn); // create output filename
// write per level // write per level
$rpl_string = !$this->getLogFlag(Flag::per_group) ? '' : $rpl_string = $this->getLogFlag(Flag::per_group) ?
// normalize level, replace all non alphanumeric characters with - // normalize level, replace all non alphanumeric characters with -
'_' . ( self::LOG_FILE_BLOCK_SEPARATOR . (
// if return is only - then set error string // if return is only - then set error string
preg_match( preg_match(
"/^-+$/", "/^-+$/",
@@ -455,25 +514,29 @@ class Logging
) ? ) ?
'INVALID-LEVEL-STRING' : 'INVALID-LEVEL-STRING' :
$level_string $level_string
); ) :
'';
$fn = str_replace('{GROUP}', $rpl_string, $fn); // create output filename $fn = str_replace('{GROUP}', $rpl_string, $fn); // create output filename
// set per class, but don't use get_class as we will only get self // set per class, but don't use get_class as we will only get self
$rpl_string = !$this->getLogFlag(Flag::per_class) ? '' : '_' $rpl_string = $this->getLogFlag(Flag::per_class) ?
// set sub class settings // set sub class settings
. str_replace('\\', '-', Support::getCallerTopLevelClass()); self::LOG_FILE_BLOCK_SEPARATOR . str_replace('\\', '-', Support::getCallerTopLevelClass()) :
'';
$fn = str_replace('{CLASS}', $rpl_string, $fn); // create output filename $fn = str_replace('{CLASS}', $rpl_string, $fn); // create output filename
// if request to write to one file // if request to write to one file
$rpl_string = !$this->getLogFlag(Flag::per_page) ? $rpl_string = $this->getLogFlag(Flag::per_page) ?
'' : self::LOG_FILE_BLOCK_SEPARATOR . System::getPageName(System::NO_EXTENSION) :
'_' . System::getPageName(System::NO_EXTENSION); '';
$fn = str_replace('{PAGENAME}', $rpl_string, $fn); // create output filename $fn = str_replace('{PAGENAME}', $rpl_string, $fn); // create output filename
// if run id, we auto add ymd, so we ignore the log file date // if run id, we auto add ymd, so we ignore the log file date
if ($this->getLogFlag(Flag::per_run)) { if ($this->getLogFlag(Flag::per_run)) {
$rpl_string = '_' . $this->getLogUniqueId(); // add 8 char unique string // add 8 char unique string and date block with time
$rpl_string = self::LOG_FILE_BLOCK_SEPARATOR . $this->getLogUniqueId();
} elseif ($this->getLogFlag(Flag::per_date)) { } elseif ($this->getLogFlag(Flag::per_date)) {
$rpl_string = '_' . $this->getLogDate(); // add date to file // add date to file
$rpl_string = self::LOG_FILE_BLOCK_SEPARATOR . $this->getLogDate();
} else { } else {
$rpl_string = ''; $rpl_string = '';
} }
@@ -483,6 +546,8 @@ class Logging
return $fn; return $fn;
} }
// MARK: master write log to file
/** /**
* writes error msg data to file for current level * writes error msg data to file for current level
* *
@@ -500,6 +565,10 @@ class Logging
if (!$this->checkLogLevel($level)) { if (!$this->checkLogLevel($level)) {
return false; return false;
} }
// if we match level then write to error_log
if ($this->checkErrorLogWriteLevel($level)) {
error_log((string)$message);
}
// build logging file name // build logging file name
// fn is log folder + file name // fn is log folder + file name
@@ -524,6 +593,8 @@ class Logging
return true; return true;
} }
// MARK: master prepare log
/** /**
* Prepare the log message with all needed info blocks: * Prepare the log message with all needed info blocks:
* [timestamp] [host name] [file path + file::row number] [running uid] {class::/->method} * [timestamp] [host name] [file path + file::row number] [running uid] {class::/->method}
@@ -603,6 +674,7 @@ class Logging
// PUBLIC STATIC METHJODS // PUBLIC STATIC METHJODS
// ********************************************************************* // *********************************************************************
// MARK: set log level
/** /**
* set the log level * set the log level
* *
@@ -663,7 +735,7 @@ class Logging
// **** GET/SETTER // **** GET/SETTER
// log level set and get // MARK: log level
/** /**
* set new log level * set new log level
@@ -698,7 +770,30 @@ class Logging
); );
} }
// log file id set (file name prefix) // MARK: error log write level
/**
* set the error_log write level
*
* @param string|int|Level $level
* @return void
*/
public function setErrorLogWriteLevel(string|int|Level $level): void
{
$this->error_log_write_level = $this->processLogLevel($level);
}
/**
* get the current level for error_log write
*
* @return Level
*/
public function getErrorLogWriteLevel(): Level
{
return $this->error_log_write_level;
}
// MARK: log file id set (file name prefix)
/** /**
* sets the internal log file prefix id * sets the internal log file prefix id
@@ -726,7 +821,7 @@ class Logging
return $this->log_file_id; return $this->log_file_id;
} }
// log unique id set (for per run) // MARK: log unique id set (for per run)
/** /**
* Sets a unique id based on current date (y/m/d, h:i:s) and a unique id (8 chars) * Sets a unique id based on current date (y/m/d, h:i:s) and a unique id (8 chars)
@@ -739,7 +834,10 @@ class Logging
{ {
if (empty($this->log_file_unique_id) || $override == true) { if (empty($this->log_file_unique_id) || $override == true) {
$this->log_file_unique_id = $this->log_file_unique_id =
date('Y-m-d_His') . '_U_' date('Y-m-d_His')
. self::LOG_FILE_BLOCK_SEPARATOR
. 'U_'
// this doesn't have to be unique for everything, just for this logging purpose
. substr(hash( . substr(hash(
'sha1', 'sha1',
random_bytes(63) random_bytes(63)
@@ -758,7 +856,7 @@ class Logging
return $this->log_file_unique_id; return $this->log_file_unique_id;
} }
// general log date // MARK: general log date
/** /**
* set the log file date to Y-m-d * set the log file date to Y-m-d
@@ -781,7 +879,7 @@ class Logging
return $this->log_file_date; return $this->log_file_date;
} }
// general flag set // MARK: general flag set
/** /**
* set one of the basic flags * set one of the basic flags
@@ -836,7 +934,7 @@ class Logging
return $this->log_flags; return $this->log_flags;
} }
// log folder/file // MARK: log folder/file
/** /**
* set new log folder, check that folder is writeable * set new log folder, check that folder is writeable
@@ -880,7 +978,7 @@ class Logging
return $this->log_file_name; return $this->log_file_name;
} }
// max log file size // MARK: max log file size
/** /**
* set mag log file size * set mag log file size
@@ -911,7 +1009,7 @@ class Logging
} }
// ********************************************************************* // *********************************************************************
// OPTIONS CALLS // MARK: OPTIONS CALLS
// ********************************************************************* // *********************************************************************
/** /**
@@ -929,6 +1027,8 @@ class Logging
// MAIN CALLS // MAIN CALLS
// ********************************************************************* // *********************************************************************
// MARK: main log call
/** /**
* Commong log interface * Commong log interface
* *
@@ -966,7 +1066,7 @@ class Logging
} }
/** /**
* DEBUG: 100 * MARK: DEBUG: 100
* *
* write debug data to error_msg array * write debug data to error_msg array
* *
@@ -998,7 +1098,7 @@ class Logging
} }
/** /**
* INFO: 200 * MARK: INFO: 200
* *
* @param string|Stringable $message * @param string|Stringable $message
* @param mixed[] $context * @param mixed[] $context
@@ -1017,7 +1117,7 @@ class Logging
} }
/** /**
* NOTICE: 250 * MARK: NOTICE: 250
* *
* @param string|Stringable $message * @param string|Stringable $message
* @param mixed[] $context * @param mixed[] $context
@@ -1036,7 +1136,7 @@ class Logging
} }
/** /**
* WARNING: 300 * MARK: WARNING: 300
* *
* @param string|Stringable $message * @param string|Stringable $message
* @param mixed[] $context * @param mixed[] $context
@@ -1055,7 +1155,7 @@ class Logging
} }
/** /**
* ERROR: 400 * MARK: ERROR: 400
* *
* @param string|Stringable $message * @param string|Stringable $message
* @param mixed[] $context * @param mixed[] $context
@@ -1074,7 +1174,7 @@ class Logging
} }
/** /**
* CTRITICAL: 500 * MARK: CTRITICAL: 500
* *
* @param string|Stringable $message * @param string|Stringable $message
* @param mixed[] $context * @param mixed[] $context
@@ -1093,7 +1193,7 @@ class Logging
} }
/** /**
* ALERT: 550 * MARK: ALERT: 550
* *
* @param string|Stringable $message * @param string|Stringable $message
* @param mixed[] $context * @param mixed[] $context
@@ -1112,7 +1212,7 @@ class Logging
} }
/** /**
* EMERGENCY: 600 * MARK: EMERGENCY: 600
* *
* @param string|Stringable $message * @param string|Stringable $message
* @param mixed[] $context * @param mixed[] $context
@@ -1131,7 +1231,7 @@ class Logging
} }
// ********************************************************************* // *********************************************************************
// DEPRECATED SUPPORT CALLS // MARK: DEPRECATED SUPPORT CALLS
// ********************************************************************* // *********************************************************************
// legacy, but there are too many implemented // legacy, but there are too many implemented
@@ -1189,7 +1289,7 @@ class Logging
} }
// ********************************************************************* // *********************************************************************
// DEPRECATED METHODS // MARK: DEPRECATED METHODS
// ********************************************************************* // *********************************************************************
/** /**
@@ -1355,7 +1455,7 @@ class Logging
} }
// ********************************************************************* // *********************************************************************
// DEBUG METHODS // MARK: DEBUG METHODS
// ********************************************************************* // *********************************************************************
/** /**
@@ -1388,6 +1488,7 @@ class Logging
} }
// back to options level // back to options level
$this->initLogLevel(); $this->initLogLevel();
$this->initErrorLogWriteLevel();
print "OPT set level: " . $this->getLoggingLevel()->getName() . "<br>"; print "OPT set level: " . $this->getLoggingLevel()->getName() . "<br>";
} }
} }

View File

@@ -1371,7 +1371,7 @@ class Generate
) { ) {
$this->msg .= sprintf( $this->msg .= sprintf(
$this->l->__('Please enter a valid (%s) input for the <b>%s</b> Field!<br>'), $this->l->__('Please enter a valid (%s) input for the <b>%s</b> Field!<br>'),
$this->dba->getTableArray()[$key]['error_example'], $this->dba->getTableArray()[$key]['error_example'] ?? '[MISSING]',
$this->dba->getTableArray()[$key]['output_name'] $this->dba->getTableArray()[$key]['output_name']
); );
} }
@@ -2602,7 +2602,7 @@ class Generate
} }
} }
// add lost error ones // add lost error ones
$this->log->error('P: ' . $data['prefix'] . ', ' $this->log->error('Prefix: ' . $data['prefix'] . ', '
. Support::prAr($_POST['ERROR'][$data['prefix']] ?? [])); . Support::prAr($_POST['ERROR'][$data['prefix']] ?? []));
if ($this->error && !empty($_POST['ERROR'][$data['prefix']])) { if ($this->error && !empty($_POST['ERROR'][$data['prefix']])) {
$prfx = $data['prefix']; // short $prfx = $data['prefix']; // short

View File

@@ -50,7 +50,8 @@ class EditUsers implements Interface\TableArraysInterface
'HIDDEN_value' => $_POST['HIDDEN_password'] ?? '', 'HIDDEN_value' => $_POST['HIDDEN_password'] ?? '',
'CONFIRM_value' => $_POST['CONFIRM_password'] ?? '', 'CONFIRM_value' => $_POST['CONFIRM_password'] ?? '',
'output_name' => 'Password', 'output_name' => 'Password',
'mandatory' => 1, // make it not mandatory to create dummy accounts that can only login via login url id
'mandatory' => 0,
'type' => 'password', // later has to be password for encryption in database 'type' => 'password', // later has to be password for encryption in database
'update' => [ // connected field updates, and update data 'update' => [ // connected field updates, and update data
'password_change_date' => [ // db row to update 'password_change_date' => [ // db row to update
@@ -182,6 +183,7 @@ class EditUsers implements Interface\TableArraysInterface
'type' => 'text', 'type' => 'text',
'error_check' => 'unique|custom', 'error_check' => 'unique|custom',
'error_regex' => "/^[A-Za-z0-9]+$/", 'error_regex' => "/^[A-Za-z0-9]+$/",
'error_example' => "ABCdef123",
'emptynull' => 1,'min_edit_acl' => '100', 'emptynull' => 1,'min_edit_acl' => '100',
'min_show_acl' => '100', 'min_show_acl' => '100',
], ],

View File

@@ -418,9 +418,7 @@ class ProgressBar
// if this is percent, we ignore anything, it is auto positioned // if this is percent, we ignore anything, it is auto positioned
if ($this->label[$name]['type'] != 'percent') { if ($this->label[$name]['type'] != 'percent') {
foreach (['top', 'left', 'width', 'height'] as $pos_name) { foreach (['top', 'left', 'width', 'height'] as $pos_name) {
if ($$pos_name !== false) { $this->label[$name][$pos_name] = intval($$pos_name);
$this->label[$name][$pos_name] = intval($$pos_name);
}
} }
if ($align != '') { if ($align != '') {

View File

@@ -19,12 +19,13 @@ declare(strict_types=1);
namespace CoreLibs\Template; namespace CoreLibs\Template;
// leading slash if this is in lib\Smarty class SmartyExtend extends \Smarty\Smarty
class SmartyExtend extends \Smarty
{ {
// internal translation engine // internal translation engine
/** @var \CoreLibs\Language\L10n */ /** @var \CoreLibs\Language\L10n language class */
public \CoreLibs\Language\L10n $l10n; public \CoreLibs\Language\L10n $l10n;
/** @var \CoreLibs\Logging\Logging $log logging class */
public \CoreLibs\Logging\Logging $log;
// lang & encoding // lang & encoding
/** @var string */ /** @var string */
@@ -157,14 +158,18 @@ class SmartyExtend extends \Smarty
* calls L10 for pass on internaly in smarty * calls L10 for pass on internaly in smarty
* also registers the getvar caller plugin * also registers the getvar caller plugin
* *
* @param \CoreLibs\Language\L10n $l10n l10n language class * @param \CoreLibs\Language\L10n $l10n l10n language class
* @param string|null $cache_id * @param \CoreLibs\Logging\Logging $log Logger class
* @param string|null $compile_id * @param string|null $cache_id [default=null]
* @param string|null $compile_id [default=null]
* @param array<string,mixed> $options [default=[]]
*/ */
public function __construct( public function __construct(
\CoreLibs\Language\L10n $l10n, \CoreLibs\Language\L10n $l10n,
\CoreLibs\Logging\Logging $log,
?string $cache_id = null, ?string $cache_id = null,
?string $compile_id = null ?string $compile_id = null,
array $options = []
) { ) {
// trigger deprecation // trigger deprecation
if ( if (
@@ -177,14 +182,33 @@ class SmartyExtend extends \Smarty
E_USER_DEPRECATED E_USER_DEPRECATED
); );
} }
// set variables (to be deprecated) // set variables from global constants (deprecated)
$cache_id = $cache_id ?? if ($cache_id === null && defined('CACHE_ID')) {
(defined('CACHE_ID') ? CACHE_ID : ''); trigger_error(
$compile_id = $compile_id ?? 'SmartyExtended: No cache_id set and CACHE_ID constant set, this is deprecated',
(defined('COMPILE_ID') ? COMPILE_ID : ''); E_USER_DEPRECATED
);
$cache_id = CACHE_ID;
}
if ($compile_id === null && defined('COMPILE_ID')) {
trigger_error(
'SmartyExtended: No compile_id set and COMPILE_ID constant set, this is deprecated',
E_USER_DEPRECATED
);
$compile_id = COMPILE_ID;
}
if (empty($cache_id)) {
throw new \BadMethodCallException('cache_id parameter is not set');
}
if (empty($compile_id)) {
throw new \BadMethodCallException('compile_id parameter is not set');
}
// call basic smarty // call basic smarty
// or Smarty::__construct();
parent::__construct(); parent::__construct();
$this->log = $log;
// init lang // init lang
$this->l10n = $l10n; $this->l10n = $l10n;
// parse and read, legacy stuff // parse and read, legacy stuff
@@ -194,7 +218,6 @@ class SmartyExtend extends \Smarty
$this->lang_short = $locale['lang_short']; $this->lang_short = $locale['lang_short'];
$this->domain = $locale['domain']; $this->domain = $locale['domain'];
$this->lang_dir = $locale['path']; $this->lang_dir = $locale['path'];
// opt load functions so we can use legacy init for smarty run perhaps // opt load functions so we can use legacy init for smarty run perhaps
\CoreLibs\Language\L10n::loadFunctions(); \CoreLibs\Language\L10n::loadFunctions();
_setlocale(LC_MESSAGES, $locale['locale']); _setlocale(LC_MESSAGES, $locale['locale']);
@@ -203,7 +226,6 @@ class SmartyExtend extends \Smarty
_bind_textdomain_codeset($this->domain, $this->encoding); _bind_textdomain_codeset($this->domain, $this->encoding);
// register smarty variable // register smarty variable
// $this->registerPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, 'getvar', [&$this, 'getTemplateVars']);
$this->registerPlugin(self::PLUGIN_MODIFIER, 'getvar', [&$this, 'getTemplateVars']); $this->registerPlugin(self::PLUGIN_MODIFIER, 'getvar', [&$this, 'getTemplateVars']);
$this->page_name = \CoreLibs\Get\System::getPageName(); $this->page_name = \CoreLibs\Get\System::getPageName();
@@ -211,6 +233,77 @@ class SmartyExtend extends \Smarty
// set internal settings // set internal settings
$this->CACHE_ID = $cache_id; $this->CACHE_ID = $cache_id;
$this->COMPILE_ID = $compile_id; $this->COMPILE_ID = $compile_id;
// set options
$this->setOptions($options);
}
/**
* set options
*
* @param array<string,mixed> $options
* @return void
*/
private function setOptions(array $options): void
{
// set escape html if option is set
if (!empty($options['escape_html'])) {
$this->setEscapeHtml(true);
}
// load plugins
// plugin array:
// 'file': string, path to plugin content to load
// 'type': a valid smarty type see Smarty PLUGIN_ constants for correct names
// 'tag': the smarty tag
// 'callback': the function to call in 'file'
if (!empty($options['plugins'])) {
foreach ($options['plugins'] as $plugin) {
// file is readable
if (
empty($plugin['file']) ||
!is_file($plugin['file']) ||
!is_readable($plugin['file'])
) {
$this->log->warning('SmartyExtended plugin load failed, file not accessable', [
'plugin' => $plugin,
]);
continue;
}
// tag is alphanumeric
if (!preg_match("/^\w+$/", $plugin['tag'] ?? '')) {
$this->log->warning('SmartyExtended plugin load failed, invalid tag', [
'plugin' => $plugin,
]);
continue;
}
// callback is alphanumeric
if (!preg_match("/^\w+$/", $plugin['callback'] ?? '')) {
$this->log->warning('SmartyExtended plugin load failed, invalid callback', [
'plugin' => $plugin,
]);
continue;
}
try {
/** @phan-suppress-next-line PhanNoopNew */
new \ReflectionClassConstant($this, $plugin['type']);
} catch (\ReflectionException $e) {
$this->log->error('SmartyExtended plugin load failed, type is not valid', [
'message' => $e->getMessage(),
'plugin' => $plugin,
]);
continue;
}
try {
require $plugin['file'];
$this->registerPlugin($plugin['type'], $plugin['tag'], $plugin['callback']);
} catch (\Smarty\Exception $e) {
$this->log->error('SmartyExtended plugin load failed with exception', [
'message' => $e->getMessage(),
'plugin' => $plugin,
]);
continue;
}
}
}
} }
/** /**

View File

@@ -599,7 +599,7 @@ class Curl implements Interface\RequestsInterface
// for post we set POST option // for post we set POST option
if ($type == "post") { if ($type == "post") {
curl_setopt($handle, CURLOPT_POST, true); curl_setopt($handle, CURLOPT_POST, true);
} elseif (!empty($type) && in_array($type, self::CUSTOM_REQUESTS)) { } elseif (in_array($type, self::CUSTOM_REQUESTS)) {
curl_setopt($handle, CURLOPT_CUSTOMREQUEST, strtoupper($type)); curl_setopt($handle, CURLOPT_CUSTOMREQUEST, strtoupper($type));
} }
// set body data if not null, will send empty [] for empty data // set body data if not null, will send empty [] for empty data

View File

@@ -183,8 +183,9 @@ list($HOST_NAME) = array_pad(explode(':', $_SERVER['HTTP_HOST'], 2), 2, null);
define('HOST_NAME', $HOST_NAME); define('HOST_NAME', $HOST_NAME);
// BAIL ON MISSING MASTER SITE CONFIG // BAIL ON MISSING MASTER SITE CONFIG
if (!isset($SITE_CONFIG[HOST_NAME]['location'])) { if (!isset($SITE_CONFIG[HOST_NAME]['location'])) {
echo 'Missing SITE_CONFIG entry for: "' . HOST_NAME . '". Contact Administrator'; throw new \InvalidArgumentException(
exit; 'Missing SITE_CONFIG entry for: "' . HOST_NAME . '". Contact Administrator'
);
} }
// BAIL ON MISSING DB CONFIG: // BAIL ON MISSING DB CONFIG:
// we have either no db selction for this host but have db config entries // we have either no db selction for this host but have db config entries
@@ -200,8 +201,9 @@ if (
empty($DB_CONFIG[$SITE_CONFIG[HOST_NAME]['db_host']])) empty($DB_CONFIG[$SITE_CONFIG[HOST_NAME]['db_host']]))
) )
) { ) {
echo 'No matching DB config found for: "' . HOST_NAME . '". Contact Administrator'; throw new \InvalidArgumentException(
exit; 'No matching DB config found for: "' . HOST_NAME . '". Contact Administrator'
);
} }
// set SSL on // set SSL on
$is_secure = false; $is_secure = false;

View File

@@ -48,7 +48,7 @@ header("Content-Type: application/json; charset=UTF-8");
if (!empty($http_headers['HTTP_AUTHORIZATION']) && !empty($http_headers['HTTP_RUNAUTHTEST'])) { if (!empty($http_headers['HTTP_AUTHORIZATION']) && !empty($http_headers['HTTP_RUNAUTHTEST'])) {
header("HTTP/1.1 401 Unauthorized"); header("HTTP/1.1 401 Unauthorized");
print buildContent($http_headers, '{"code": 401, "content": {"Error": "Not Authorized"}}'); print buildContent($http_headers, '{"code": 401, "content": {"Error": "Not Authorized"}}');
exit; exit(1);
} }
// if server request type is get set file_get to null -> no body // if server request type is get set file_get to null -> no body
@@ -57,7 +57,7 @@ if ($_SERVER['REQUEST_METHOD'] == "GET") {
} elseif (($file_get = file_get_contents('php://input')) === false) { } elseif (($file_get = file_get_contents('php://input')) === false) {
header("HTTP/1.1 404 Not Found"); header("HTTP/1.1 404 Not Found");
print buildContent($http_headers, '{"code": 404, "content": {"Error": "file_get_contents failed"}}'); print buildContent($http_headers, '{"code": 404, "content": {"Error": "file_get_contents failed"}}');
exit; exit(1);
} }
print buildContent($http_headers, $file_get); print buildContent($http_headers, $file_get);

View File

@@ -12,6 +12,8 @@ Not yet covered tests:
- loginGetLocale - loginGetLocale
- loginGetHeaderColor - loginGetHeaderColor
- loginGetPages - loginGetPages
- loginGetPageLookupList
- loginPageAccessAllowed
- loginGetEuid - loginGetEuid
*/ */
@@ -152,7 +154,6 @@ final class CoreLibsACLLoginTest extends TestCase
// TARGET // TARGET
define('TARGET', 'test'); define('TARGET', 'test');
// LOGIN DB SCHEMA // LOGIN DB SCHEMA
// define('LOGIN_DB_SCHEMA', '');
// SHOULD SET // SHOULD SET
// DEFAULT_ACL_LEVEL (d80) // DEFAULT_ACL_LEVEL (d80)
@@ -1531,6 +1532,12 @@ final class CoreLibsACLLoginTest extends TestCase
$login_mock->loginGetEditAccessCuidFromUid($mock_settings['edit_access_uid']), $login_mock->loginGetEditAccessCuidFromUid($mock_settings['edit_access_uid']),
'Assert check access uid to cuid valid' 'Assert check access uid to cuid valid'
); );
// - loginGetEditAccessCuidFromId
$this->assertEquals(
$expected['check_access_cuid'],
$login_mock->loginGetEditAccessCuidFromUid($mock_settings['edit_access_id']),
'Assert check access id to cuid valid'
);
// Deprecated // Deprecated
// - loginCheckEditAccess // - loginCheckEditAccess
$this->assertEquals( $this->assertEquals(

View File

@@ -0,0 +1,404 @@
<?php
// This code was created by Claude Sonnet 4
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
use CoreLibs\Combined\ArrayHandler;
class CoreLibsCombinedArrayHandlerFindArraysMissingKeyTest extends TestCase
{
private const DATA_SEPARATOR = ':'; // Updated to match your class's separator
/**
* Test finding missing single key when searching by value without specific key
*/
public function testFindMissingSingleKeyWithValueSearch()
{
$array = [
'item1' => [
'name' => 'John',
'age' => 25
// missing 'email' key
],
'item2' => [
'name' => 'Jane',
'age' => 30,
'email' => 'jane@example.com'
],
'item3' => [
'name' => 'John', // same value as item1
'age' => 35,
'email' => 'john2@example.com'
]
];
$result = ArrayHandler::findArraysMissingKey($array, 'John', 'email');
$this->assertCount(1, $result);
$this->assertEquals($array['item1'], $result[0]['content']);
$this->assertEquals('item1', $result[0]['path']);
$this->assertEquals(['email'], $result[0]['missing_key']);
}
/**
* Test finding missing single key when searching by specific key-value pair
*/
public function testFindMissingSingleKeyWithKeyValueSearch()
{
$array = [
'user1' => [
'id' => 1,
'name' => 'Alice'
// missing 'status' key
],
'user2' => [
'id' => 2,
'name' => 'Bob',
'status' => 'active'
],
'user3' => [
'id' => 1, // same id as user1
'name' => 'Charlie',
'status' => 'inactive'
]
];
$result = ArrayHandler::findArraysMissingKey($array, 1, 'status', 'id');
$this->assertCount(1, $result);
$this->assertEquals($array['user1'], $result[0]['content']);
$this->assertEquals('user1', $result[0]['path']);
$this->assertEquals(['status'], $result[0]['missing_key']);
}
/**
* Test finding missing multiple keys
*/
public function testFindMissingMultipleKeys()
{
$array = [
'record1' => [
'name' => 'Test',
'value' => 100
// missing both 'date' and 'status' keys
],
'record2' => [
'name' => 'Test',
'value' => 200,
'date' => '2023-01-01'
// missing 'status' key
],
'record3' => [
'name' => 'Test',
'value' => 300,
'date' => '2023-01-02',
'status' => 'complete'
]
];
$result = ArrayHandler::findArraysMissingKey($array, 'Test', ['date', 'status']);
$this->assertCount(2, $result);
// First result should be record1 missing both keys
$this->assertEquals($array['record1'], $result[0]['content']);
$this->assertEquals('record1', $result[0]['path']);
$this->assertContains('date', $result[0]['missing_key']);
$this->assertContains('status', $result[0]['missing_key']);
$this->assertCount(2, $result[0]['missing_key']);
// Second result should be record2 missing status key
$this->assertEquals($array['record2'], $result[1]['content']);
$this->assertEquals('record2', $result[1]['path']);
$this->assertEquals(['status'], $result[1]['missing_key']);
}
/**
* Test with nested arrays
*/
public function testFindMissingKeyInNestedArrays()
{
$array = [
'section1' => [
'items' => [
'item1' => [
'name' => 'Product A',
'price' => 99.99
// missing 'category' key
],
'item2' => [
'name' => 'Product B',
'price' => 149.99,
'category' => 'electronics'
]
]
],
'section2' => [
'data' => [
'name' => 'Product A', // same name as nested item
'category' => 'books'
]
]
];
$result = ArrayHandler::findArraysMissingKey($array, 'Product A', 'category');
$this->assertCount(1, $result);
$this->assertEquals($array['section1']['items']['item1'], $result[0]['content']);
$this->assertEquals('section1:items:item1', $result[0]['path']);
$this->assertEquals(['category'], $result[0]['missing_key']);
}
/**
* Test when no arrays are missing the required key
*/
public function testNoMissingKeys()
{
$array = [
'item1' => [
'name' => 'John',
'email' => 'john@example.com'
],
'item2' => [
'name' => 'Jane',
'email' => 'jane@example.com'
]
];
$result = ArrayHandler::findArraysMissingKey($array, 'John', 'email');
$this->assertEmpty($result);
}
/**
* Test when search value is not found in any array
*/
public function testSearchValueNotFound()
{
$array = [
'item1' => [
'name' => 'John',
'age' => 25
],
'item2' => [
'name' => 'Jane',
'age' => 30
]
];
$result = ArrayHandler::findArraysMissingKey($array, 'Bob', 'email');
$this->assertEmpty($result);
}
/**
* Test with different data types for search value
*/
public function testDifferentSearchValueTypes()
{
$array = [
'item1' => [
'active' => true,
'count' => 5
// missing 'label' key
],
'item2' => [
'active' => false,
'count' => 10,
'label' => 'test'
],
'item3' => [
'active' => true, // same boolean as item1
'count' => 15,
'label' => 'another'
]
];
// Test with boolean
$result = ArrayHandler::findArraysMissingKey($array, true, 'label', 'active');
$this->assertCount(1, $result);
$this->assertEquals('item1', $result[0]['path']);
// Test with integer
$result = ArrayHandler::findArraysMissingKey($array, 5, 'label', 'count');
$this->assertCount(1, $result);
$this->assertEquals('item1', $result[0]['path']);
}
/**
* Test with empty array
*/
public function testEmptyArray()
{
$array = [];
$result = ArrayHandler::findArraysMissingKey($array, 'test', 'key');
$this->assertEmpty($result);
}
/**
* Test with array containing non-array values
*/
public function testMixedArrayTypes()
{
$array = [
'string_value' => 'hello',
'numeric_value' => 123,
'array_value' => [
'name' => 'test',
// missing 'type' key
],
'another_array' => [
'name' => 'test',
'type' => 'example'
]
];
$result = ArrayHandler::findArraysMissingKey($array, 'test', 'type');
$this->assertCount(1, $result);
$this->assertEquals($array['array_value'], $result[0]['content']);
$this->assertEquals('array_value', $result[0]['path']);
$this->assertEquals(['type'], $result[0]['missing_key']);
}
/**
* Test path building with deeper nesting
*/
public function testDeepNestingPathBuilding()
{
$array = [
'level1' => [
'level2' => [
'level3' => [
'items' => [
'target_item' => [
'name' => 'deep_test',
// missing 'required_field'
]
]
]
]
]
];
$result = ArrayHandler::findArraysMissingKey($array, 'deep_test', 'required_field');
$this->assertCount(1, $result);
$this->assertEquals('level1:level2:level3:items:target_item', $result[0]['path']);
}
/**
* Test with custom path separator
*/
public function testCustomPathSeparator()
{
$array = [
'level1' => [
'level2' => [
'item' => [
'name' => 'test',
// missing 'type' key
]
]
]
];
$result = ArrayHandler::findArraysMissingKey($array, 'test', 'type', null, '/');
$this->assertCount(1, $result);
$this->assertEquals('level1/level2/item', $result[0]['path']);
}
/**
* Test default path separator behavior
*/
public function testDefaultPathSeparator()
{
$array = [
'parent' => [
'child' => [
'name' => 'test',
// missing 'value' key
]
]
];
// Using default separator (should be ':')
$result = ArrayHandler::findArraysMissingKey($array, 'test', 'value');
$this->assertCount(1, $result);
$this->assertEquals('parent:child', $result[0]['path']);
}
/**
* Test different path separators don't affect search logic
*/
public function testPathSeparatorDoesNotAffectSearchLogic()
{
$array = [
'section' => [
'data' => [
'id' => 123,
'name' => 'item'
// missing 'status'
]
]
];
// Test with different separators - results should be identical except for path
$result1 = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id', ':');
$result2 = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id', '.');
$result3 = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id', '/');
$this->assertCount(1, $result1);
$this->assertCount(1, $result2);
$this->assertCount(1, $result3);
// Content and missing_key should be the same
$this->assertEquals($result1[0]['content'], $result2[0]['content']);
$this->assertEquals($result1[0]['content'], $result3[0]['content']);
$this->assertEquals($result1[0]['missing_key'], $result2[0]['missing_key']);
$this->assertEquals($result1[0]['missing_key'], $result3[0]['missing_key']);
// Paths should be different based on separator
$this->assertEquals('section:data', $result1[0]['path']);
$this->assertEquals('section.data', $result2[0]['path']);
$this->assertEquals('section/data', $result3[0]['path']);
}
/**
* test type checking
*/
public function testStrictTypeChecking()
{
$array = [
'item1' => [
'id' => '123', // string
'name' => 'test'
// missing 'status'
],
'item2' => [
'id' => 123, // integer
'name' => 'test2',
'status' => 'active'
]
];
// Search for integer 123 - should only match item2
$result = ArrayHandler::findArraysMissingKey($array, 123, 'status', 'id');
$this->assertEmpty($result); // item2 has the status key
// Search for string '123' - should only match item1
$result = ArrayHandler::findArraysMissingKey($array, '123', 'status', 'id');
$this->assertCount(1, $result);
$this->assertEquals('item1', $result[0]['path']);
}
}
// __END__

View File

@@ -0,0 +1,333 @@
<?php
// This code was created by Claude Sonnet 4
// modification for value checks with assertEqualsCanonicalizing
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
use CoreLibs\Combined\ArrayHandler;
class CoreLibsCombinedArrayHandlerKsortArrayTest extends TestCase
{
/**
* Test basic ascending sort (default behavior)
*/
public function testKsortArrayBasicAscending(): void
{
$input = [
'zebra' => 'value1',
'apple' => 'value2',
'banana' => 'value3',
'cherry' => 'value4'
];
$expected = [
'apple' => 'value2',
'banana' => 'value3',
'cherry' => 'value4',
'zebra' => 'value1'
];
$result = ArrayHandler::ksortArray($input);
$this->assertEquals($expected, $result);
$this->assertEquals(array_keys($expected), array_keys($result));
}
/**
* Test descending sort with reverse=true
*/
public function testKsortArrayDescending(): void
{
$input = [
'zebra' => 'value1',
'apple' => 'value2',
'banana' => 'value3',
'cherry' => 'value4'
];
$expected = [
'zebra' => 'value1',
'cherry' => 'value4',
'banana' => 'value3',
'apple' => 'value2'
];
$result = ArrayHandler::ksortArray($input, false, true);
$this->assertEquals($expected, $result);
$this->assertEquals(array_keys($expected), array_keys($result));
}
/**
* Test case-insensitive ascending sort
*/
public function testKsortArrayCaseInsensitiveAscending(): void
{
$input = [
'Zebra' => 'value1',
'apple' => 'value2',
'Banana' => 'value3',
'cherry' => 'value4'
];
$expected = [
'apple' => 'value2',
'Banana' => 'value3',
'cherry' => 'value4',
'Zebra' => 'value1'
];
$result = ArrayHandler::ksortArray($input, true);
$this->assertEquals($expected, $result);
$this->assertEquals(array_keys($expected), array_keys($result));
}
/**
* Test case-insensitive descending sort
*/
public function testKsortArrayCaseInsensitiveDescending(): void
{
$input = [
'Zebra' => 'value1',
'apple' => 'value2',
'Banana' => 'value3',
'cherry' => 'value4'
];
$expected = [
'Zebra' => 'value1',
'cherry' => 'value4',
'Banana' => 'value3',
'apple' => 'value2'
];
$result = ArrayHandler::ksortArray($input, true, true);
$this->assertEquals($expected, $result);
$this->assertEquals(array_keys($expected), array_keys($result));
}
/**
* Test with mixed case keys to verify case sensitivity behavior
*/
public function testKsortArrayCaseSensitivityComparison(): void
{
$input = [
'B' => 'value1',
'a' => 'value2',
'C' => 'value3',
'b' => 'value4'
];
// Case-sensitive sort (uppercase comes before lowercase in ASCII)
$expectedCaseSensitive = [
'B' => 'value1',
'C' => 'value3',
'a' => 'value2',
'b' => 'value4'
];
// Case-insensitive sort
$expectedCaseInsensitive = [
'a' => 'value2',
'B' => 'value1',
'b' => 'value4',
'C' => 'value3'
];
$resultCaseSensitive = ArrayHandler::ksortArray($input, false);
$resultCaseInsensitive = ArrayHandler::ksortArray($input, true);
$this->assertEquals($expectedCaseSensitive, $resultCaseSensitive);
$this->assertEquals($expectedCaseInsensitive, $resultCaseInsensitive);
}
/**
* Test with numeric string keys
*/
public function testKsortArrayNumericStringKeys(): void
{
$input = [
'10' => 'value1',
'2' => 'value2',
'1' => 'value3',
'20' => 'value4'
];
// String comparison, not numeric
$expected = [
'1' => 'value3',
'10' => 'value1',
'2' => 'value2',
'20' => 'value4'
];
$result = ArrayHandler::ksortArray($input);
$this->assertEquals($expected, $result);
}
/**
* Test with special characters in keys
*/
public function testKsortArraySpecialCharacters(): void
{
$input = [
'key_with_underscore' => 'value1',
'key-with-dash' => 'value2',
'key.with.dot' => 'value3',
'key with space' => 'value4',
'keyWithCamelCase' => 'value5'
];
$result = ArrayHandler::ksortArray($input);
// Verify it doesn't throw an error and maintains all keys
$this->assertCount(5, $result);
$this->assertArrayHasKey('key_with_underscore', $result);
$this->assertArrayHasKey('key-with-dash', $result);
$this->assertArrayHasKey('key.with.dot', $result);
$this->assertArrayHasKey('key with space', $result);
$this->assertArrayHasKey('keyWithCamelCase', $result);
}
/**
* Test with empty array
*/
public function testKsortArrayEmpty(): void
{
$input = [];
$result = ArrayHandler::ksortArray($input);
$this->assertEquals([], $result);
$this->assertIsArray($result);
}
/**
* Test with single element array
*/
public function testKsortArraySingleElement(): void
{
$input = ['onlykey' => 'onlyvalue'];
$result = ArrayHandler::ksortArray($input);
$this->assertEquals($input, $result);
}
/**
* Test that original array is not modified (function returns new array)
*/
public function testKsortArrayDoesNotModifyOriginal(): void
{
$original = [
'zebra' => 'value1',
'apple' => 'value2',
'banana' => 'value3'
];
$originalCopy = $original; // Keep a copy for comparison
$result = ArrayHandler::ksortArray($original);
// Original array should remain unchanged
$this->assertEquals($originalCopy, $original);
$this->assertNotEquals(array_keys($original), array_keys($result));
}
/**
* Test with complex mixed data types as values
*/
public function testKsortArrayMixedValueTypes(): void
{
$input = [
'string_key' => 'string_value',
'array_key' => ['nested', 'array'],
'int_key' => 42,
'bool_key' => true,
'null_key' => null
];
$result = ArrayHandler::ksortArray($input);
// Check that all keys are preserved and sorted
$expectedKeys = ['array_key', 'bool_key', 'int_key', 'null_key', 'string_key'];
$this->assertEquals($expectedKeys, array_keys($result));
// Check that values are preserved correctly
$this->assertEquals('string_value', $result['string_key']);
$this->assertEquals(['nested', 'array'], $result['array_key']);
$this->assertEquals(42, $result['int_key']);
$this->assertTrue($result['bool_key']);
$this->assertNull($result['null_key']);
}
/**
* Test all parameter combinations
*/
public function testKsortArrayAllParameterCombinations(): void
{
$input = [
'Delta' => 'value1',
'alpha' => 'value2',
'Charlie' => 'value3',
'bravo' => 'value4'
];
// Test all 4 combinations
$result1 = ArrayHandler::ksortArray($input, false, false); // default
$result2 = ArrayHandler::ksortArray($input, false, true); // reverse only
$result3 = ArrayHandler::ksortArray($input, true, false); // lowercase only
$result4 = ArrayHandler::ksortArray($input, true, true); // both
// Each should produce different ordering
$this->assertNotEquals(array_keys($result1), array_keys($result2));
$this->assertNotEquals(array_keys($result1), array_keys($result3));
$this->assertNotEquals(array_keys($result1), array_keys($result4));
$this->assertNotEquals(array_keys($result2), array_keys($result3));
$this->assertNotEquals(array_keys($result2), array_keys($result4));
$this->assertNotEquals(array_keys($result3), array_keys($result4));
// But all should have same keys and values, just different order
$this->assertEqualsCanonicalizing(array_values($input), array_values($result1));
$this->assertEqualsCanonicalizing(array_values($input), array_values($result2));
$this->assertEqualsCanonicalizing(array_values($input), array_values($result3));
$this->assertEqualsCanonicalizing(array_values($input), array_values($result4));
}
/**
* Data provider for comprehensive testing
*/
public function sortingParametersProvider(): array
{
return [
'default' => [false, false],
'reverse' => [false, true],
'lowercase' => [true, false],
'lowercase_reverse' => [true, true],
];
}
/**
* Test that function works with all parameter combinations using data provider
*
* @dataProvider sortingParametersProvider
*/
public function testKsortArrayWithDataProvider(bool $lowerCase, bool $reverse): void
{
$input = [
'Zebra' => 'animal1',
'apple' => 'fruit1',
'Banana' => 'fruit2',
'cat' => 'animal2'
];
$result = ArrayHandler::ksortArray($input, $lowerCase, $reverse);
// Basic assertions that apply to all combinations
$this->assertIsArray($result);
$this->assertCount(4, $result);
$this->assertArrayHasKey('Zebra', $result);
$this->assertArrayHasKey('apple', $result);
$this->assertArrayHasKey('Banana', $result);
$this->assertArrayHasKey('cat', $result);
}
}
// __END__

View File

@@ -0,0 +1,383 @@
<?php
// created by Claude Sonnet 4
// testRecursiveSearchWithFlatResult had wrong retunr count
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
use CoreLibs\Combined\ArrayHandler;
class CoreLibsCombinedArrayHandlerSelectArrayFromOptionTest extends TestCase
{
private array $testData;
private array $nestedTestData;
protected function setUp(): void
{
$this->testData = [
'item1' => [
'name' => 'John',
'age' => 25,
'status' => 'active',
'score' => 85.5
],
'item2' => [
'name' => 'jane',
'age' => 30,
'status' => 'inactive',
'score' => 92.0
],
'item3' => [
'name' => 'Bob',
'age' => 25,
'status' => 'active',
'score' => 78.3
],
'item4' => [
'name' => 'Alice',
'age' => 35,
'status' => 'pending',
'score' => 88.7
]
];
$this->nestedTestData = [
'level1_a' => [
'name' => 'Level1A',
'type' => 'parent',
'children' => [
'child1' => [
'name' => 'Child1',
'type' => 'child',
'active' => true
],
'child2' => [
'name' => 'Child2',
'type' => 'child',
'active' => false
]
]
],
'level1_b' => [
'name' => 'Level1B',
'type' => 'parent',
'children' => [
'child3' => [
'name' => 'Child3',
'type' => 'child',
'active' => true,
'nested' => [
'deep1' => [
'name' => 'Deep1',
'type' => 'deep',
'active' => true
]
]
]
]
],
'item5' => [
'name' => 'Direct',
'type' => 'child',
'active' => false
]
];
}
public function testEmptyArrayReturnsEmpty(): void
{
$result = ArrayHandler::selectArrayFromOption([], 'name', 'John');
$this->assertEmpty($result);
}
public function testBasicStringSearch(): void
{
$result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'John');
$this->assertCount(1, $result);
$this->assertArrayHasKey('item1', $result);
$this->assertEquals('John', $result['item1']['name']);
}
public function testBasicIntegerSearch(): void
{
$result = ArrayHandler::selectArrayFromOption($this->testData, 'age', 25);
$this->assertCount(2, $result);
$this->assertArrayHasKey('item1', $result);
$this->assertArrayHasKey('item3', $result);
}
public function testBasicFloatSearch(): void
{
$result = ArrayHandler::selectArrayFromOption($this->testData, 'score', 85.5);
$this->assertCount(1, $result);
$this->assertArrayHasKey('item1', $result);
$this->assertEquals(85.5, $result['item1']['score']);
}
public function testBasicBooleanSearch(): void
{
$data = [
'item1' => ['enabled' => true, 'name' => 'Test1'],
'item2' => ['enabled' => false, 'name' => 'Test2'],
'item3' => ['enabled' => true, 'name' => 'Test3']
];
$result = ArrayHandler::selectArrayFromOption($data, 'enabled', true);
$this->assertCount(2, $result);
$this->assertArrayHasKey('item1', $result);
$this->assertArrayHasKey('item3', $result);
}
public function testStrictComparison(): void
{
$data = [
'item1' => ['value' => '25', 'name' => 'String25'],
'item2' => ['value' => 25, 'name' => 'Int25'],
'item3' => ['value' => 25.0, 'name' => 'Float25']
];
// Non-strict should match all
$nonStrictResult = ArrayHandler::selectArrayFromOption($data, 'value', 25, false);
$this->assertCount(3, $nonStrictResult);
// Strict should only match exact type
$strictResult = ArrayHandler::selectArrayFromOption($data, 'value', 25, true);
$this->assertCount(1, $strictResult);
$this->assertArrayHasKey('item2', $strictResult);
}
public function testCaseInsensitiveSearch(): void
{
$result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'JANE', false, true);
$this->assertCount(1, $result);
$this->assertArrayHasKey('item2', $result);
$this->assertEquals('jane', $result['item2']['name']);
}
public function testCaseSensitiveSearch(): void
{
$result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'JANE', false, false);
$this->assertEmpty($result);
}
public function testRecursiveSearchWithFlatResult(): void
{
$result = ArrayHandler::selectArrayFromOption(
$this->nestedTestData,
'type',
'child',
false,
false,
true,
true,
':*'
);
$this->assertCount(4, $result);
$this->assertArrayHasKey('level1_a:*children:*child1', $result);
$this->assertArrayHasKey('level1_a:*children:*child2', $result);
$this->assertArrayHasKey('level1_b:*children:*child3', $result);
$this->assertArrayHasKey('item5', $result);
}
public function testRecursiveSearchWithNestedResult(): void
{
$result = ArrayHandler::selectArrayFromOption(
$this->nestedTestData,
'type',
'child',
false,
false,
true,
false
);
$this->assertCount(3, $result);
$this->assertArrayHasKey('level1_a', $result);
$this->assertArrayHasKey('level1_b', $result);
$this->assertArrayHasKey('item5', $result);
// Check nested structure is preserved
$this->assertArrayHasKey('children', $result['level1_a']);
$this->assertArrayHasKey('child1', $result['level1_a']['children']);
$this->assertArrayHasKey('child2', $result['level1_a']['children']);
}
public function testRecursiveSearchDeepNesting(): void
{
$result = ArrayHandler::selectArrayFromOption(
$this->nestedTestData,
'type',
'deep',
false,
false,
true,
true,
':*'
);
$this->assertCount(1, $result);
$this->assertArrayHasKey('level1_b:*children:*child3:*nested:*deep1', $result);
$this->assertEquals('Deep1', $result['level1_b:*children:*child3:*nested:*deep1']['name']);
}
public function testCustomFlatSeparator(): void
{
$result = ArrayHandler::selectArrayFromOption(
$this->nestedTestData,
'type',
'child',
false,
false,
true,
true,
'|'
);
$this->assertArrayHasKey('level1_a|children|child1', $result);
$this->assertArrayHasKey('level1_a|children|child2', $result);
$this->assertArrayHasKey('level1_b|children|child3', $result);
}
public function testNonRecursiveSearch(): void
{
$result = ArrayHandler::selectArrayFromOption(
$this->nestedTestData,
'type',
'child',
false,
false,
false
);
// Should only find direct matches, not nested ones
$this->assertCount(1, $result);
$this->assertArrayHasKey('item5', $result);
}
public function testNoMatchesFound(): void
{
$result = ArrayHandler::selectArrayFromOption($this->testData, 'name', 'NonExistent');
$this->assertEmpty($result);
}
public function testMissingLookupKey(): void
{
$result = ArrayHandler::selectArrayFromOption($this->testData, 'nonexistent_key', 'value');
$this->assertEmpty($result);
}
public function testCombinedStrictAndCaseInsensitive(): void
{
$data = [
'item1' => ['name' => 'Test', 'id' => '123'],
'item2' => ['name' => 'test', 'id' => 123],
'item3' => ['name' => 'TEST', 'id' => '123']
];
// Case insensitive but strict type matching
$result = ArrayHandler::selectArrayFromOption($data, 'id', '123', true, true);
$this->assertCount(2, $result);
$this->assertArrayHasKey('item1', $result);
$this->assertArrayHasKey('item3', $result);
}
public function testBooleanWithCaseInsensitive(): void
{
$data = [
'item1' => ['active' => true, 'name' => 'Test1'],
'item2' => ['active' => false, 'name' => 'Test2']
];
// Case insensitive flag should not affect boolean comparison
$result = ArrayHandler::selectArrayFromOption($data, 'active', true, false, true);
$this->assertCount(1, $result);
$this->assertArrayHasKey('item1', $result);
}
public function testArrayWithNumericKeys(): void
{
$data = [
0 => ['name' => 'First', 'type' => 'test'],
1 => ['name' => 'Second', 'type' => 'test'],
2 => ['name' => 'Third', 'type' => 'other']
];
$result = ArrayHandler::selectArrayFromOption($data, 'type', 'test');
$this->assertCount(2, $result);
$this->assertArrayHasKey(0, $result);
$this->assertArrayHasKey(1, $result);
}
public function testRecursiveWithMixedKeyTypes(): void
{
$data = [
'string_key' => [
'name' => 'Parent',
'type' => 'parent',
0 => [
'name' => 'Child0',
'type' => 'child'
],
'child_key' => [
'name' => 'ChildKey',
'type' => 'child'
]
]
];
$result = ArrayHandler::selectArrayFromOption($data, 'type', 'child', false, false, true, true, ':*');
$this->assertCount(2, $result);
$this->assertArrayHasKey('string_key:*0', $result);
$this->assertArrayHasKey('string_key:*child_key', $result);
}
public function testAllParametersCombined(): void
{
$data = [
'parent1' => [
'name' => 'Parent1',
'status' => 'ACTIVE',
'children' => [
'child1' => [
'name' => 'Child1',
'status' => 'active'
]
]
]
];
$result = ArrayHandler::selectArrayFromOption(
$data,
'status',
'active',
false, // not strict
true, // case insensitive
true, // recursive
true, // flat result
'|' // custom separator
);
$this->assertCount(2, $result);
$this->assertArrayHasKey('parent1', $result);
$this->assertArrayHasKey('parent1|children|child1', $result);
}
}
// __END__

View File

@@ -0,0 +1,328 @@
<?php
// This code was created by Claude Sonnet 4
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
use CoreLibs\Combined\ArrayHandler;
class CoreLibsCombinedArrayHandlerSortArrayTest extends TestCase
{
/**
* Test basic ascending sort without maintaining keys
*/
public function testBasicAscendingSort()
{
$input = [3, 1, 4, 1, 5, 9];
$expected = [1, 1, 3, 4, 5, 9];
$result = ArrayHandler::sortArray($input);
$this->assertEquals($expected, $result);
$this->assertEquals(array_keys($expected), array_keys($result));
}
/**
* Test basic descending sort without maintaining keys
*/
public function testBasicDescendingSort()
{
$input = [3, 1, 4, 1, 5, 9];
$expected = [9, 5, 4, 3, 1, 1];
$result = ArrayHandler::sortArray($input, false, true);
$this->assertEquals($expected, $result);
$this->assertEquals(array_keys($expected), array_keys($result));
}
/**
* Test ascending sort with key maintenance
*/
public function testAscendingSortWithKeyMaintenance()
{
$input = ['c' => 3, 'a' => 1, 'd' => 4, 'b' => 1, 'e' => 5];
$expected = ['a' => 1, 'b' => 1, 'c' => 3, 'd' => 4, 'e' => 5];
$result = ArrayHandler::sortArray($input, false, false, true);
$this->assertEquals($expected, $result);
}
/**
* Test descending sort with key maintenance
*/
public function testDescendingSortWithKeyMaintenance()
{
$input = ['c' => 3, 'a' => 1, 'd' => 4, 'b' => 1, 'e' => 5];
$expected = ['e' => 5, 'd' => 4, 'c' => 3, 'a' => 1, 'b' => 1];
$result = ArrayHandler::sortArray($input, false, true, true);
$this->assertEquals($expected, $result);
}
/**
* Test string sorting with lowercase conversion
*/
public function testStringLowerCaseSort()
{
$input = ['Banana', 'apple', 'Cherry', 'date'];
$expected = ['apple', 'Banana', 'Cherry', 'date'];
$result = ArrayHandler::sortArray($input, true);
$this->assertEquals($expected, $result);
}
/**
* Test string sorting with lowercase conversion in reverse
*/
public function testStringLowerCaseSortReverse()
{
$input = ['Banana', 'apple', 'Cherry', 'date'];
$expected = ['date', 'Cherry', 'Banana', 'apple'];
$result = ArrayHandler::sortArray($input, true, true);
$this->assertEquals($expected, $result);
}
/**
* Test string sorting with lowercase conversion and key maintenance
*/
public function testStringLowerCaseSortWithKeys()
{
$input = ['b' => 'Banana', 'a' => 'apple', 'c' => 'Cherry', 'd' => 'date'];
$expected = ['a' => 'apple', 'b' => 'Banana', 'c' => 'Cherry', 'd' => 'date'];
$result = ArrayHandler::sortArray($input, true, false, true);
$this->assertEquals($expected, $result);
}
/**
* Test string sorting with lowercase conversion, reverse, and key maintenance
*/
public function testStringLowerCaseSortReverseWithKeys()
{
$input = ['b' => 'Banana', 'a' => 'apple', 'c' => 'Cherry', 'd' => 'date'];
$expected = ['d' => 'date', 'c' => 'Cherry', 'b' => 'Banana', 'a' => 'apple'];
$result = ArrayHandler::sortArray($input, true, true, true);
$this->assertEquals($expected, $result);
}
/**
* Test numeric string sorting with SORT_NUMERIC flag
*/
public function testNumericStringSorting()
{
$input = ['10', '2', '1', '20'];
$expected = ['1', '2', '10', '20'];
$result = ArrayHandler::sortArray($input, false, false, false, SORT_NUMERIC);
$this->assertEquals($expected, $result);
}
/**
* Test natural string sorting with SORT_NATURAL flag
*/
public function testNaturalStringSorting()
{
$input = ['img1.png', 'img10.png', 'img2.png', 'img20.png'];
$expected = ['img1.png', 'img2.png', 'img10.png', 'img20.png'];
$result = ArrayHandler::sortArray($input, false, false, false, SORT_NATURAL);
$this->assertEquals($expected, $result);
}
/**
* Test with empty array
*/
public function testEmptyArray()
{
$input = [];
$expected = [];
$result = ArrayHandler::sortArray($input);
$this->assertEquals($expected, $result);
}
/**
* Test with single element array
*/
public function testSingleElementArray()
{
$input = [42];
$expected = [42];
$result = ArrayHandler::sortArray($input);
$this->assertEquals($expected, $result);
}
/**
* Test with array containing null values
*/
public function testArrayWithNullValues()
{
$input = [3, null, 1, null, 2];
$expected = [null, null, 1, 2, 3];
$result = ArrayHandler::sortArray($input);
$this->assertEquals($expected, $result);
}
/**
* Test with mixed data types
*/
public function testMixedDataTypes()
{
$input = [3, '1', 4.5, '2', 1];
$result = ArrayHandler::sortArray($input);
// Should sort according to PHP's natural comparison rules
$this->assertIsArray($result);
$this->assertCount(5, $result);
}
/**
* Test that original array is not modified (immutability)
*/
public function testOriginalArrayNotModified()
{
$original = [3, 1, 4, 1, 5, 9];
$input = $original;
$result = ArrayHandler::sortArray($input);
$this->assertEquals($original, $input);
$this->assertNotEquals($input, $result);
}
/**
* Test case sensitivity without lowercase flag
*/
public function testCaseSensitivityWithoutLowercase()
{
$input = ['Banana', 'apple', 'Cherry'];
$result = ArrayHandler::sortArray($input);
// Capital letters should come before lowercase in ASCII sort
$this->assertEquals('Banana', $result[0]);
$this->assertEquals('Cherry', $result[1]);
$this->assertEquals('apple', $result[2]);
}
/**
* Test all parameters combination
*/
public function testAllParametersCombination()
{
$input = ['z' => 'Zebra', 'a' => 'apple', 'b' => 'Banana'];
$result = ArrayHandler::sortArray($input, true, true, true, SORT_REGULAR);
// Should be sorted by lowercase, reversed, with keys maintained
$keys = array_keys($result);
$values = array_values($result);
$this->assertEquals(['z', 'b', 'a'], $keys);
$this->assertEquals(['Zebra', 'Banana', 'apple'], $values);
}
/**
* Test floating point numbers
*/
public function testFloatingPointNumbers()
{
$input = [3.14, 2.71, 1.41, 1.73];
$expected = [1.41, 1.73, 2.71, 3.14];
$result = ArrayHandler::sortArray($input);
$this->assertEquals($expected, $result);
}
/**
* Test with duplicate values and key maintenance
*/
public function testDuplicateValuesWithKeyMaintenance()
{
$input = ['first' => 1, 'second' => 2, 'third' => 1, 'fourth' => 2];
$result = ArrayHandler::sortArray($input, false, false, true);
$this->assertCount(4, $result);
$this->assertEquals([1, 1, 2, 2], array_values($result));
// Keys should be preserved
$this->assertArrayHasKey('first', $result);
$this->assertArrayHasKey('second', $result);
$this->assertArrayHasKey('third', $result);
$this->assertArrayHasKey('fourth', $result);
}
/**
* Data provider for comprehensive parameter testing
*/
public function sortParameterProvider(): array
{
return [
'basic_ascending' => [
[3, 1, 4, 2],
false, false, false, SORT_REGULAR,
[1, 2, 3, 4]
],
'basic_descending' => [
[3, 1, 4, 2],
false, true, false, SORT_REGULAR,
[4, 3, 2, 1]
],
'lowercase_ascending' => [
['Banana', 'apple', 'Cherry'],
true, false, false, SORT_REGULAR,
['apple', 'Banana', 'Cherry']
],
'lowercase_descending' => [
['Banana', 'apple', 'Cherry'],
true, true, false, SORT_REGULAR,
['Cherry', 'Banana', 'apple']
]
];
}
/**
* Test various parameter combinations using data provider
*
* @dataProvider sortParameterProvider
*/
public function testSortParameterCombinations(
array $input,
bool $lowercase,
bool $reverse,
bool $maintainKeys,
int $params,
array $expected
) {
$result = ArrayHandler::sortArray($input, $lowercase, $reverse, $maintainKeys, $params);
if (!$maintainKeys) {
$this->assertEquals($expected, $result);
} else {
$this->assertEquals($expected, array_values($result));
}
}
}
// __END__

File diff suppressed because it is too large Load Diff

View File

@@ -926,48 +926,114 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
public function daysIntervalProvider(): array public function daysIntervalProvider(): array
{ {
return [ return [
'valid interval /, not named array' => [ // normal and format tests
'2020/1/1', 'valid interval / not named array' => [
'2020/1/30', 'input_a' => '2020/1/1',
false, 'input_b' => '2020/1/30',
[29, 22, 8], 'return_named' => false, // return_named
'include_end_date' => true, // include_end_date
'exclude_start_date' => false, // exclude_start_date
'expected' => [30, 22, 8, false],
], ],
'valid interval /, named array' => [ 'valid interval / named array' => [
'2020/1/1', 'input_a' => '2020/1/1',
'2020/1/30', 'input_b' => '2020/1/30',
true, 'return_named' => true,
['overall' => 29, 'weekday' => 22, 'weekend' => 8], 'include_end_date' => true,
'exclude_start_date' => false,
'expected' => ['overall' => 30, 'weekday' => 22, 'weekend' => 8, 'reverse' => false],
], ],
'valid interval -' => [ 'valid interval with "-"' => [
'2020-1-1', 'input_a' => '2020-1-1',
'2020-1-30', 'input_b' => '2020-1-30',
false, 'return_named' => false,
[29, 22, 8], 'include_end_date' => true,
], 'exclude_start_date' => false,
'valid interval switched' => [ 'expected' => [30, 22, 8, false],
'2020/1/30',
'2020/1/1',
false,
[28, 0, 0],
], ],
'valid interval with time' => [ 'valid interval with time' => [
'2020/1/1 12:12:12', 'input_a' => '2020/1/1 12:12:12',
'2020/1/30 13:13:13', 'input_b' => '2020/1/30 13:13:13',
false, 'return_named' => false,
[28, 21, 8], 'include_end_date' => true,
'exclude_start_date' => false,
'expected' => [30, 22, 8, false],
], ],
// invalid
'invalid dates' => [ 'invalid dates' => [
'abc', 'input_a' => 'abc',
'xyz', 'input_b' => 'xyz',
false, 'return_named' => false,
[0, 0, 0] 'include_end_date' => true,
'exclude_start_date' => false,
'expected' => [0, 0, 0, false]
], ],
// this test will take a long imte // this test will take a long time
'out of bound dates' => [ 'out of bound dates' => [
'1900-1-1', 'input_a' => '1900-1-1',
'9999-12-31', 'input_b' => '9999-12-31',
false, 'return_named' => false,
[2958463,2113189,845274], 'include_end_date' => true,
'exclude_start_date' => false,
'expected' => [2958463, 2113189, 845274, false],
],
// tests for include/exclude
'exclude end date' => [
'input_b' => '2020/1/1',
'input_a' => '2020/1/30',
'return_named' => false,
'include_end_date' => false,
'exclude_start_date' => false,
'expected' => [29, 21, 8, false],
],
'exclude start date' => [
'input_b' => '2020/1/1',
'input_a' => '2020/1/30',
'return_named' => false,
'include_end_date' => true,
'exclude_start_date' => true,
'expected' => [29, 21, 8, false],
],
'exclude start and end date' => [
'input_b' => '2020/1/1',
'input_a' => '2020/1/30',
'return_named' => false,
'include_end_date' => false,
'exclude_start_date' => true,
'expected' => [28, 20, 8, false],
],
// reverse
'reverse: valid interval' => [
'input_a' => '2020/1/30',
'input_b' => '2020/1/1',
'return_named' => false,
'include_end_date' => true,
'exclude_start_date' => false,
'expected' => [30, 22, 8, true],
],
'reverse: exclude end date' => [
'input_a' => '2020/1/30',
'input_b' => '2020/1/1',
'return_named' => false,
'include_end_date' => false,
'exclude_start_date' => false,
'expected' => [29, 21, 8, true],
],
'reverse: exclude start date' => [
'input_a' => '2020/1/30',
'input_b' => '2020/1/1',
'return_named' => false,
'include_end_date' => true,
'exclude_start_date' => true,
'expected' => [29, 21, 8, true],
],
'reverse: exclude start and end date' => [
'input_a' => '2020/1/30',
'input_b' => '2020/1/1',
'return_named' => false,
'include_end_date' => false,
'exclude_start_date' => true,
'expected' => [28, 20, 8, true],
], ],
]; ];
} }
@@ -982,20 +1048,52 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
* *
* @param string $input_a * @param string $input_a
* @param string $input_b * @param string $input_b
* @param bool $flag * @param bool $return_named
* @param array $expected * @param array $expected
* @return void * @return void
*/ */
public function testCalcDaysInterval( public function testCalcDaysInterval(
string $input_a, string $input_a,
string $input_b, string $input_b,
bool $flag, bool $return_named,
bool $include_end_date,
bool $exclude_start_date,
$expected $expected
): void { ): void {
$this->assertEquals( $this->assertEquals(
$expected, $expected,
\CoreLibs\Combined\DateTime::calcDaysInterval($input_a, $input_b, $flag) \CoreLibs\Combined\DateTime::calcDaysInterval(
$input_a,
$input_b,
return_named:$return_named,
include_end_date:$include_end_date,
exclude_start_date:$exclude_start_date
),
'call calcDaysInterval'
); );
if ($return_named) {
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::calcDaysIntervalNamedIndex(
$input_a,
$input_b,
include_end_date:$include_end_date,
exclude_start_date:$exclude_start_date
),
'call calcDaysIntervalNamedIndex'
);
} else {
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::calcDaysIntervalNumIndex(
$input_a,
$input_b,
include_end_date:$include_end_date,
exclude_start_date:$exclude_start_date
),
'call calcDaysIntervalNamedIndex'
);
}
} }
/** /**
@@ -1187,7 +1285,38 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
'2023-07-03', '2023-07-03',
'2023-07-27', '2023-07-27',
true true
] ],
// reverse
'reverse: no weekend' => [
'2023-07-04',
'2023-07-03',
false
],
'reverse: start weekend sat' => [
'2023-07-04',
'2023-07-01',
true
],
'reverse: start weekend sun' => [
'2023-07-04',
'2023-07-02',
true
],
'reverse: end weekend sat' => [
'2023-07-08',
'2023-07-03',
true
],
'reverse: end weekend sun' => [
'2023-07-09',
'2023-07-03',
true
],
'reverse: long period > 6 days' => [
'2023-07-27',
'2023-07-03',
true
],
]; ];
} }

View File

@@ -40,7 +40,7 @@ final class CoreLibsConvertByteTest extends TestCase
4 => '1.00 KB', 4 => '1.00 KB',
5 => '1.02KiB', 5 => '1.02KiB',
], ],
'invalud string number' => [ 'invalid string number' => [
0 => '1024 MB', 0 => '1024 MB',
1 => '1024 MB', 1 => '1024 MB',
2 => '1024 MB', 2 => '1024 MB',

View File

@@ -0,0 +1,283 @@
<?php
// This code was created by Claude Sonnet 4
// FIX:
// '/test{/', // Unmatched brace -> this is valid
// '/test{1,}/', // Invalid quantifier -> this is valid
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
use CoreLibs\Convert\Strings;
/**
* Test class for CoreLibs\Convert\Strings regex validation methods
*/
class CoreLibsConvertStringsRegexValidateTest extends TestCase
{
/**
* Test isValidRegex with valid regex patterns
*/
public function testIsValidRegexWithValidPatterns(): void
{
$validPatterns = [
'/^[a-zA-Z0-9]+$/',
'/test/',
'/\d+/',
'/^hello.*world$/',
'/[0-9]{3}-[0-9]{3}-[0-9]{4}/',
'#^https?://.*#i',
'~^[a-z]+~',
'|test|',
'/^$/m',
'/\w+/u',
];
foreach ($validPatterns as $pattern) {
$this->assertTrue(
Strings::isValidRegex($pattern),
"Pattern '{$pattern}' should be valid"
);
}
}
/**
* Test isValidRegex with invalid regex patterns
*/
public function testIsValidRegexWithInvalidPatterns(): void
{
$invalidPatterns = [
'/[/', // Unmatched bracket
'/test[/', // Unmatched bracket
'/(?P<name>/', // Unmatched parenthesis
'/(?P<>test)/', // Invalid named group
'/test\\/', // Invalid escape at end
'/(test/', // Unmatched parenthesis
'/test)/', // Unmatched parenthesis
// '/test{/', // Unmatched brace -> this is valid
// '/test{1,}/', // Invalid quantifier -> this is valid
'/[z-a]/', // Invalid character range
'invalid', // No delimiters
'', // Empty string
'/(?P<123>test)/', // Invalid named group name
];
foreach ($invalidPatterns as $pattern) {
$this->assertFalse(
Strings::isValidRegex($pattern),
"Pattern '{$pattern}' should be invalid"
);
}
}
/**
* Test getLastRegexErrorString returns correct error messages
*/
public function testGetLastRegexErrorStringReturnsCorrectMessages(): void
{
// Test with a valid regex first to ensure clean state
Strings::isValidRegex('/valid/');
$this->assertEquals('No error', Strings::getLastRegexErrorString());
// Test with invalid regex to trigger an error
Strings::isValidRegex('/[/');
$errorMessage = Strings::getLastRegexErrorString();
// The error message should be one of the defined messages
$this->assertContains($errorMessage, array_values(Strings::PREG_ERROR_MESSAGES));
$this->assertNotEquals('Unknown error', $errorMessage);
}
/**
* Test getLastRegexErrorString with unknown error
*/
public function testGetLastRegexErrorStringWithUnknownError(): void
{
// This is harder to test directly since we can't easily mock preg_last_error()
// but we can test the fallback behavior by reflection or assume it works
// At minimum, ensure it returns a string
$result = Strings::getLastRegexErrorString();
$this->assertIsString($result);
$this->assertNotEmpty($result);
}
/**
* Test validateRegex with valid patterns
*/
public function testValidateRegexWithValidPatterns(): void
{
$validPatterns = [
'/^test$/',
'/\d+/',
'/[a-z]+/i',
];
foreach ($validPatterns as $pattern) {
$result = Strings::validateRegex($pattern);
$this->assertIsArray($result);
$this->assertArrayHasKey('valid', $result);
$this->assertArrayHasKey('preg_error', $result);
$this->assertArrayHasKey('error', $result);
$this->assertTrue($result['valid'], "Pattern '{$pattern}' should be valid");
$this->assertEquals(PREG_NO_ERROR, $result['preg_error']);
$this->assertNull($result['error']);
}
}
/**
* Test validateRegex with invalid patterns
*/
public function testValidateRegexWithInvalidPatterns(): void
{
$invalidPatterns = [
'/[/', // Unmatched bracket
'/(?P<name>/', // Unmatched parenthesis
'/test\\/', // Invalid escape at end
'/(test/', // Unmatched parenthesis
];
foreach ($invalidPatterns as $pattern) {
$result = Strings::validateRegex($pattern);
$this->assertIsArray($result);
$this->assertArrayHasKey('valid', $result);
$this->assertArrayHasKey('preg_error', $result);
$this->assertArrayHasKey('error', $result);
$this->assertArrayHasKey('pcre_error', $result);
$this->assertFalse($result['valid'], "Pattern '{$pattern}' should be invalid");
$this->assertNotEquals(PREG_NO_ERROR, $result['preg_error']);
$this->assertIsString($result['error']);
$this->assertNotNull($result['error']);
$this->assertNotEmpty($result['error']);
// Verify error message is from our defined messages or 'Unknown error'
$this->assertTrue(
in_array($result['error'], array_values(Strings::PREG_ERROR_MESSAGES)) ||
$result['error'] === 'Unknown error'
);
}
}
/**
* Test validateRegex array structure
*/
public function testValidateRegexArrayStructure(): void
{
$result = Strings::validateRegex('/test/');
// Test array structure for valid regex
$this->assertIsArray($result);
$this->assertCount(4, $result);
$this->assertArrayHasKey('valid', $result);
$this->assertArrayHasKey('preg_error', $result);
$this->assertArrayHasKey('error', $result);
$result = Strings::validateRegex('/[/');
// Test array structure for invalid regex
$this->assertIsArray($result);
$this->assertCount(4, $result);
$this->assertArrayHasKey('valid', $result);
$this->assertArrayHasKey('preg_error', $result);
$this->assertArrayHasKey('error', $result);
$this->assertArrayHasKey('pcre_error', $result);
}
/**
* Test that methods handle edge cases properly
*/
public function testEdgeCases(): void
{
// Empty string
$this->assertFalse(Strings::isValidRegex(''));
$result = Strings::validateRegex('');
$this->assertFalse($result['valid']);
// Very long pattern
$longPattern = '/' . str_repeat('a', 1000) . '/';
$this->assertTrue(Strings::isValidRegex($longPattern));
// Unicode patterns
$this->assertTrue(Strings::isValidRegex('/\p{L}+/u'));
$this->assertTrue(Strings::isValidRegex('/[α-ω]+/u'));
}
/**
* Test PREG_ERROR_MESSAGES constant accessibility
*/
public function testPregErrorMessagesConstant(): void
{
$this->assertIsArray(Strings::PREG_ERROR_MESSAGES);
$this->assertNotEmpty(Strings::PREG_ERROR_MESSAGES);
// Check that all expected PREG constants are defined
$expectedKeys = [
PREG_NO_ERROR,
PREG_INTERNAL_ERROR,
PREG_BACKTRACK_LIMIT_ERROR,
PREG_RECURSION_LIMIT_ERROR,
PREG_BAD_UTF8_ERROR,
PREG_BAD_UTF8_OFFSET_ERROR,
PREG_JIT_STACKLIMIT_ERROR,
];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, Strings::PREG_ERROR_MESSAGES);
$this->assertIsString(Strings::PREG_ERROR_MESSAGES[$key]);
$this->assertNotEmpty(Strings::PREG_ERROR_MESSAGES[$key]);
}
}
/**
* Test error state isolation between method calls
*/
public function testErrorStateIsolation(): void
{
// Start with invalid regex
Strings::isValidRegex('/[/');
$firstError = Strings::getLastRegexErrorString();
$this->assertNotEquals('No error', $firstError);
// Use valid regex
Strings::isValidRegex('/valid/');
$secondError = Strings::getLastRegexErrorString();
$this->assertEquals('No error', $secondError);
// Verify validateRegex clears previous errors
$result = Strings::validateRegex('/valid/');
$this->assertTrue($result['valid']);
$this->assertEquals(PREG_NO_ERROR, $result['preg_error']);
}
/**
* Test various regex delimiters
*/
public function testDifferentDelimiters(): void
{
$patterns = [
'/test/', // forward slash
'#test#', // hash
'~test~', // tilde
'|test|', // pipe
'@test@', // at symbol
'!test!', // exclamation
'%test%', // percent
];
foreach ($patterns as $pattern) {
$this->assertTrue(
Strings::isValidRegex($pattern),
"Pattern with delimiter '{$pattern}' should be valid"
);
}
}
}
// __END__

View File

@@ -24,117 +24,83 @@ final class CoreLibsConvertStringsTest extends TestCase
{ {
// 0: input // 0: input
// 1: format // 1: format
// 2: split characters as string, null for default
// 3: expected // 3: expected
return [ return [
'all empty string' => [ 'all empty string' => [
'', '',
'', '',
null,
'' ''
], ],
'empty input string' => [ 'empty input string' => [
'', '',
'2-2', '2-2',
null,
'' ''
], ],
'empty format string string' => [ 'empty format string string' => [
'1234', '1234',
'', '',
null,
'1234' '1234'
], ],
'string format match' => [ 'string format match' => [
'1234', '1234',
'2-2', '2-2',
null,
'12-34' '12-34'
], ],
'string format trailing match' => [ 'string format trailing match' => [
'1234', '1234',
'2-2-', '2-2-',
null,
'12-34' '12-34'
], ],
'string format leading match' => [ 'string format leading match' => [
'1234', '1234',
'-2-2', '-2-2',
null,
'12-34' '12-34'
], ],
'string format double inside match' => [ 'string format double inside match' => [
'1234', '1234',
'2--2', '2--2',
null,
'12--34', '12--34',
], ],
'string format short first' => [ 'string format short first' => [
'1', '1',
'2-2', '2-2',
null,
'1' '1'
], ],
'string format match first' => [ 'string format match first' => [
'12', '12',
'2-2', '2-2',
null,
'12' '12'
], ],
'string format short second' => [ 'string format short second' => [
'123', '123',
'2-2', '2-2',
null,
'12-3' '12-3'
], ],
'string format too long' => [ 'string format too long' => [
'1234567', '1234567',
'2-2', '2-2',
null,
'12-34-567' '12-34-567'
], ],
'string format invalid format string' => [
'1234',
'2_2',
null,
'1234'
],
'different split character' => [ 'different split character' => [
'1234', '1234',
'2_2', '2_2',
'_',
'12_34' '12_34'
], ],
'mixed split characters' => [ 'mixed split characters' => [
'123456', '123456',
'2-2_2', '2-2_2',
'-_',
'12-34_56' '12-34_56'
], ],
'length mixed' => [ 'length mixed' => [
'ABCD12345568ABC13', 'ABCD12345568ABC13',
'2-4_5-2#4', '2-4_5-2#4',
'-_#',
'AB-CD12_34556-8A#BC13' 'AB-CD12_34556-8A#BC13'
], ],
'split with split chars in string' => [ 'split with split chars in string' => [
'12-34', '12-34',
'2-2', '2-2',
null,
'12--3-4' '12--3-4'
], ],
'mutltibyte string' => [
'あいうえ',
'2-2',
null,
'あいうえ'
],
'mutltibyte split string' => [
'1234',
'-',
null,
'1234'
],
]; ];
} }
@@ -143,29 +109,137 @@ final class CoreLibsConvertStringsTest extends TestCase
* *
* @covers ::splitFormatString * @covers ::splitFormatString
* @dataProvider splitFormatStringProvider * @dataProvider splitFormatStringProvider
* @testdox splitFormatString $input with format $format and splitters $split_characters will be $expected [$_dataName] * @testdox splitFormatString $input with format $format will be $expected [$_dataName]
* *
* @param string $input * @param string $input
* @param string $format * @param string $format
* @param string|null $split_characters
* @param string $expected * @param string $expected
* @return void * @return void
*/ */
public function testSplitFormatString( public function testSplitFormatString(
string $input, string $input,
string $format, string $format,
string $expected
): void {
$output = \CoreLibs\Convert\Strings::splitFormatString(
$input,
$format,
);
$this->assertEquals(
$expected,
$output
);
}
/** check exceptions */
public function splitFormatStringExceptionProvider(): array
{
return [
'string format with no splitter match' => [
'1234',
'22',
'12-34'
],
'invalid format string' => [
'1234',
'2あ2',
],
'mutltibyte string' => [
'あいうえ',
'2-2',
],
'mutltibyte split string' => [
'1234',
'-',
],
];
}
/**
* Undocumented function
*
* @covers ::splitFormatStringFixed
* @dataProvider splitFormatStringExceptionProvider
* @testdox splitFormatString Exception catch checks for $input with $format[$_dataName]
*
* @return void
*/
public function testSplitFormatStringExceptions(string $input, string $format): void
{
// catch exception
$this->expectException(\InvalidArgumentException::class);
\CoreLibs\Convert\Strings::splitFormatString($input, $format);
}
/**
* test for split Format string fixed length
*
* @return array
*/
public function splitFormatStringFixedProvider(): array
{
return [
'normal split, default split char' => [
'abcdefg',
4,
null,
'abcd-efg'
],
'noraml split, other single split char' => [
'abcdefg',
4,
"=",
'abcd=efg'
],
'noraml split, other multiple split char' => [
'abcdefg',
4,
"-=-",
'abcd-=-efg'
],
'non ascii characters' => [
'あいうえお',
2,
"-",
'あい-うえ-お'
],
'empty string' => [
'',
4,
"-",
''
]
];
}
/**
* Undocumented function
*
* @covers ::splitFormatStringFixed
* @dataProvider splitFormatStringFixedProvider
* @testdox splitFormatStringFixed $input with length $split_length and split chars $split_characters will be $expected [$_dataName]
*
* @param string $input
* @param int $split_length
* @param string|null $split_characters
* @param string $expected
* @return void
*/
public function testSplitFormatStringFixed(
string $input,
int $split_length,
?string $split_characters, ?string $split_characters,
string $expected string $expected
): void { ): void {
if ($split_characters === null) { if ($split_characters === null) {
$output = \CoreLibs\Convert\Strings::splitFormatString( $output = \CoreLibs\Convert\Strings::splitFormatStringFixed(
$input, $input,
$format $split_length
); );
} else { } else {
$output = \CoreLibs\Convert\Strings::splitFormatString( $output = \CoreLibs\Convert\Strings::splitFormatStringFixed(
$input, $input,
$format, $split_length,
$split_characters $split_characters
); );
} }
@@ -175,6 +249,36 @@ final class CoreLibsConvertStringsTest extends TestCase
); );
} }
public function splitFormatStringFixedExceptionProvider(): array
{
return [
'split length too short' => [
'abcdefg',
-1,
],
'split length longer than string' => [
'abcdefg',
20,
],
];
}
/**
* Undocumented function
*
* @covers ::splitFormatStringFixed
* @dataProvider splitFormatStringFixedExceptionProvider
* @testdox splitFormatStringFixed Exception catch checks for $input with $length [$_dataName]
*
* @return void
*/
public function testSplitFormatStringFixedExceptions(string $input, int $length): void
{
// catch exception
$this->expectException(\InvalidArgumentException::class);
\CoreLibs\Convert\Strings::splitFormatStringFixed($input, $length);
}
/** /**
* Undocumented function * Undocumented function
* *
@@ -378,6 +482,222 @@ final class CoreLibsConvertStringsTest extends TestCase
\CoreLibs\Convert\Strings::stripUTF8BomBytes($file) \CoreLibs\Convert\Strings::stripUTF8BomBytes($file)
); );
} }
/**
* Undocumented function
*
* @return array
*/
public function allCharsInSetProvider(): array
{
return [
'find' => [
'abc',
'abcdef',
true
],
'not found' => [
'abcz',
'abcdef',
false
]
];
}
/**
* Undocumented function
*
* @covers ::allCharsInSet
* @dataProvider allCharsInSetProvider
* @testdox allCharsInSet $input in $haystack with expected $expected [$_dataName]
*
* @param string $needle
* @param string $haystack
* @param bool $expected
* @return void
*/
public function testAllCharsInSet(string $needle, string $haystack, bool $expected): void
{
$this->assertEquals(
$expected,
\CoreLibs\Convert\Strings::allCharsInSet($needle, $haystack)
);
}
public function buildCharStringFromListsProvider(): array
{
return [
'test a' => [
'abc',
['a', 'b', 'c'],
],
'test b' => [
'abc123',
['a', 'b', 'c'],
['1', '2', '3'],
],
'test c: no params' => [
'',
],
'test c: empty 1' => [
'',
[]
],
'test nested' => [
'abc',
[['a'], ['b'], ['c']],
],
];
}
/**
* Undocumented function
*
* @covers ::buildCharStringFromLists
* @dataProvider buildCharStringFromListsProvider
* @testdox buildCharStringFromLists all $input convert to $expected [$_dataName]
*
* @param string $expected
* @param array ...$input
* @return void
*/
public function testBuildCharStringFromLists(string $expected, array ...$input): void
{
$this->assertEquals(
$expected,
\CoreLibs\Convert\Strings::buildCharStringFromLists(...$input)
);
}
/**
* Undocumented function
*
* @return array
*/
public function removeDuplicatesProvider(): array
{
return [
'test no change' => [
'ABCDEFG',
'ABCDEFG',
],
'test simple' => [
'aa',
'a'
],
'test keep lower and uppwer case' => [
'AaBbCc',
'AaBbCc'
],
'test unqiue' => [
'aabbcc',
'abc'
],
'test multibyte no change' => [
'あいうえお',
'あいうえお',
],
'test multibyte' => [
'ああいいううええおお',
'あいうえお',
],
'test multibyte special' => [
'あぁいぃうぅえぇおぉ',
'あぁいぃうぅえぇおぉ',
]
];
}
/**
* Undocumented function
*
* @covers ::removeDuplicates
* @dataProvider removeDuplicatesProvider
* @testdox removeDuplicates make $input unqiue to $expected [$_dataName]
*
* @param string $input
* @param string $expected
* @return void
*/
public function testRemoveDuplicates(string $input, string $expected): void
{
$this->assertEquals(
$expected,
\CoreLibs\Convert\Strings::removeDuplicates($input)
);
}
/**
* Undocumented function
*
* @return array
*/
public function isValidRegexSimpleProvider(): array
{
return [
'valid regex' => [
'/^[A-z]$/',
true,
[
'valid' => true,
'preg_error' => 0,
'error' => null,
'pcre_error' => null
],
],
'invalid regex A' => [
'/^[A-z]$',
false,
[
'valid' => false,
'preg_error' => 1,
'error' => 'Internal PCRE error',
'pcre_error' => 'Internal error'
],
],
'invalid regex B' => [
'/^[A-z$',
false,
[
'valid' => false,
'preg_error' => 1,
'error' => 'Internal PCRE error',
'pcre_error' => 'Internal error'
],
],
];
}
/**
* Undocumented function
*
* @covers ::isValidRegexSimple
* @dataProvider isValidRegexSimpleProvider
* @testdox isValidRegexSimple make $input unqiue to $expected [$_dataName]
*
* @param string $input
* @param bool $expected
* @return void
*/
public function testIsValidRegexSimple(string $input, bool $expected, array $expected_extended): void
{
$this->assertEquals(
$expected,
\CoreLibs\Convert\Strings::isValidRegex($input),
'Regex is not valid'
);
$this->assertEquals(
$expected_extended,
\CoreLibs\Convert\Strings::validateRegex($input),
'Validation of regex failed'
);
$this->assertEquals(
// for true null is set, so we get here No Error
$expected_extended['error'] ?? \CoreLibs\Convert\Strings::PREG_ERROR_MESSAGES[0],
\CoreLibs\Convert\Strings::getLastRegexErrorString(),
'Cannot match last preg error string'
);
}
} }
// __END__ // __END__

View File

@@ -21,8 +21,10 @@ final class CoreLibsCreateHashTest extends TestCase
public function hashData(): array public function hashData(): array
{ {
return [ return [
'any string' => [ 'hash tests' => [
// this is the string
'text' => 'Some String Text', 'text' => 'Some String Text',
// hash list special
'crc32b_reverse' => 'c5c21d91', // crc32b (in revere) 'crc32b_reverse' => 'c5c21d91', // crc32b (in revere)
'sha1Short' => '4d2bc9ba0', // sha1Short 'sha1Short' => '4d2bc9ba0', // sha1Short
// via hash // via hash
@@ -31,6 +33,8 @@ final class CoreLibsCreateHashTest extends TestCase
'fnv132' => '9df444f9', // hash: fnv132 'fnv132' => '9df444f9', // hash: fnv132
'fnv1a32' => '2c5f91b9', // hash: fnv1a32 'fnv1a32' => '2c5f91b9', // hash: fnv1a32
'joaat' => '50dab846', // hash: joaat 'joaat' => '50dab846', // hash: joaat
'ripemd160' => 'aeae3f041b20136451519edd9361570909300342', // hash: ripemd160,
'sha256' => '9055080e022f224fa835929b80582b3c71c672206fa3a49a87412c25d9d42ceb', // hash: sha256
] ]
]; ];
} }
@@ -81,7 +85,7 @@ final class CoreLibsCreateHashTest extends TestCase
{ {
$list = []; $list = [];
foreach ($this->hashData() as $name => $values) { foreach ($this->hashData() as $name => $values) {
foreach ([null, 'crc32b', 'adler32', 'fnv132', 'fnv1a32', 'joaat'] as $_hash_type) { foreach ([null, 'crc32b', 'adler32', 'fnv132', 'fnv1a32', 'joaat', 'ripemd160', 'sha256'] as $_hash_type) {
// default value test // default value test
if ($_hash_type === null) { if ($_hash_type === null) {
$hash_type = \CoreLibs\Create\Hash::STANDARD_HASH_SHORT; $hash_type = \CoreLibs\Create\Hash::STANDARD_HASH_SHORT;
@@ -114,6 +118,22 @@ final class CoreLibsCreateHashTest extends TestCase
]; ];
} }
/**
* Undocumented function
*
* @return array
*/
public function hashStandardProvider(): array
{
$hash_source = 'Some String Text';
return [
'Long Hash check: ' . \CoreLibs\Create\Hash::STANDARD_HASH => [
$hash_source,
hash(\CoreLibs\Create\Hash::STANDARD_HASH, $hash_source)
],
];
}
/** /**
* Undocumented function * Undocumented function
* *
@@ -136,9 +156,13 @@ final class CoreLibsCreateHashTest extends TestCase
/** /**
* Undocumented function * Undocumented function
* *
* phpcs:disable Generic.Files.LineLength
* @covers ::__sha1Short * @covers ::__sha1Short
* @covers ::__crc32b
* @covers ::sha1Short
* @dataProvider sha1ShortProvider * @dataProvider sha1ShortProvider
* @testdox __sha1Short $input will be $expected (crc32b) and $expected_sha1 (sha1 short) [$_dataName] * @testdox __sha1Short/__crc32b/sha1short $input will be $expected (crc32b) and $expected_sha1 (sha1 short) [$_dataName]
* phpcs:enable Generic.Files.LineLength
* *
* @param string $input * @param string $input
* @param string $expected * @param string $expected
@@ -149,16 +173,29 @@ final class CoreLibsCreateHashTest extends TestCase
// uses crc32b // uses crc32b
$this->assertEquals( $this->assertEquals(
$expected, $expected,
\CoreLibs\Create\Hash::__sha1Short($input) \CoreLibs\Create\Hash::__sha1Short($input),
'__sha1Short depreacted'
); );
$this->assertEquals( $this->assertEquals(
$expected, $expected,
\CoreLibs\Create\Hash::__sha1Short($input, false) \CoreLibs\Create\Hash::__sha1Short($input, false),
'__sha1Short (false) depreacted'
);
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::__crc32b($input),
'__crc32b'
); );
// sha1 type // sha1 type
$this->assertEquals( $this->assertEquals(
$expected_sha1, $expected_sha1,
\CoreLibs\Create\Hash::__sha1Short($input, true) \CoreLibs\Create\Hash::__sha1Short($input, true),
'__sha1Short (true) depreacted'
);
$this->assertEquals(
$expected_sha1,
\CoreLibs\Create\Hash::sha1Short($input),
'sha1Short'
); );
} }
@@ -166,8 +203,10 @@ final class CoreLibsCreateHashTest extends TestCase
* Undocumented function * Undocumented function
* *
* @covers ::__hash * @covers ::__hash
* @covers ::hashShort
* @covers ::hashShort
* @dataProvider hashProvider * @dataProvider hashProvider
* @testdox __hash $input with $hash_type will be $expected [$_dataName] * @testdox __hash/hashShort/hash $input with $hash_type will be $expected [$_dataName]
* *
* @param string $input * @param string $input
* @param string|null $hash_type * @param string|null $hash_type
@@ -179,12 +218,24 @@ final class CoreLibsCreateHashTest extends TestCase
if ($hash_type === null) { if ($hash_type === null) {
$this->assertEquals( $this->assertEquals(
$expected, $expected,
\CoreLibs\Create\Hash::__hash($input) \CoreLibs\Create\Hash::__hash($input),
'__hash'
);
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::hashShort($input),
'hashShort'
); );
} else { } else {
$this->assertEquals( $this->assertEquals(
$expected, $expected,
\CoreLibs\Create\Hash::__hash($input, $hash_type) \CoreLibs\Create\Hash::__hash($input, $hash_type),
'__hash with hash type'
);
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::hash($input, $hash_type),
'hash with hash type'
); );
} }
} }
@@ -193,8 +244,9 @@ final class CoreLibsCreateHashTest extends TestCase
* Undocumented function * Undocumented function
* *
* @covers ::__hashLong * @covers ::__hashLong
* @covers ::hashLong
* @dataProvider hashLongProvider * @dataProvider hashLongProvider
* @testdox __hashLong $input will be $expected [$_dataName] * @testdox __hashLong/hashLong $input will be $expected [$_dataName]
* *
* @param string $input * @param string $input
* @param string $expected * @param string $expected
@@ -206,6 +258,168 @@ final class CoreLibsCreateHashTest extends TestCase
$expected, $expected,
\CoreLibs\Create\Hash::__hashLong($input) \CoreLibs\Create\Hash::__hashLong($input)
); );
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::hashLong($input)
);
}
/**
* Undocumented function
*
* @covers ::hash
* @covers ::hashStd
* @dataProvider hashStandardProvider
* @testdox hash/hashStd $input will be $expected [$_dataName]
*
* @param string $input
* @param string $expected
* @return void
*/
public function testHashStandard(string $input, string $expected): void
{
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::hashStd($input)
);
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::hash($input)
);
}
/**
* Undocumented function
*
* @covers ::hash
* @testdox hash with invalid type
*
* @return void
*/
public function testInvalidHashType(): void
{
$hash_source = 'Some String Text';
$expected = hash(\CoreLibs\Create\Hash::STANDARD_HASH, $hash_source);
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::hash($hash_source, 'DOES_NOT_EXIST')
);
}
/**
* Note: this only tests default sha256
*
* @covers ::hashHmac
* @testdox hash hmac test
*
* @return void
*/
public function testHashMac(): void
{
$hash_key = 'FIX KEY';
$hash_source = 'Some String Text';
$expected = '16479b3ef6fa44e1cdd8b2dcfaadf314d1a7763635e8738f1e7996d714d9b6bf';
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::hashHmac($hash_source, $hash_key)
);
}
/**
* Undocumented function
*
* @covers ::hashHmac
* @testdox hash hmac with invalid type
*
* @return void
*/
public function testInvalidHashMacType(): void
{
$hash_key = 'FIX KEY';
$hash_source = 'Some String Text';
$expected = hash_hmac(\CoreLibs\Create\Hash::STANDARD_HASH, $hash_source, $hash_key);
$this->assertEquals(
$expected,
\CoreLibs\Create\Hash::hashHmac($hash_source, $hash_key, 'DOES_NOT_EXIST')
);
}
/**
* Undocumented function
*
* @return array<mixed>
*/
public function providerHashTypes(): array
{
return [
'Hash crc32b' => [
'crc32b',
true,
false,
],
'Hash adler32' => [
'adler32',
true,
false,
],
'HAsh fnv132' => [
'fnv132',
true,
false,
],
'Hash fnv1a32' => [
'fnv1a32',
true,
false,
],
'Hash: joaat' => [
'joaat',
true,
false,
],
'Hash: ripemd160' => [
'ripemd160',
true,
true,
],
'Hash: sha256' => [
'sha256',
true,
true,
],
'Hash: invalid' => [
'invalid',
false,
false
]
];
}
/**
* Undocumented function
*
* @covers ::isValidHashType
* @covers ::isValidHashHmacType
* @dataProvider providerHashTypes
* @testdox check if $hash_type is valid for hash $hash_ok and hash hmac $hash_hmac_ok [$_dataName]
*
* @param string $hash_type
* @param bool $hash_ok
* @param bool $hash_hmac_ok
* @return void
*/
public function testIsValidHashAndHashHmacTypes(string $hash_type, bool $hash_ok, bool $hash_hmac_ok): void
{
$this->assertEquals(
$hash_ok,
\CoreLibs\Create\Hash::isValidHashType($hash_type),
'hash valid'
);
$this->assertEquals(
$hash_hmac_ok,
\CoreLibs\Create\Hash::isValidHashHmacType($hash_type),
'hash hmac valid'
);
} }
} }

View File

@@ -13,32 +13,6 @@ use PHPUnit\Framework\TestCase;
*/ */
final class CoreLibsCreateRandomKeyTest extends TestCase final class CoreLibsCreateRandomKeyTest extends TestCase
{ {
/**
* Undocumented function
*
* @return array
*/
public function keyLenghtProvider(): array
{
return [
'valid key length' => [
0 => 6,
1 => true,
2 => 6,
],
'negative key length' => [
0 => -1,
1 => false,
2 => 4,
],
'tpp big key length' => [
0 => 300,
1 => false,
2 => 4,
],
];
}
/** /**
* Undocumented function * Undocumented function
* *
@@ -47,109 +21,67 @@ final class CoreLibsCreateRandomKeyTest extends TestCase
public function randomKeyGenProvider(): array public function randomKeyGenProvider(): array
{ {
return [ return [
'default key length' => [ // just key length
'default key length, default char set' => [
0 => null, 0 => null,
1 => 4 1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT
], ],
'set -1 key length default' => [ 'set -1 key length, default char set' => [
0 => -1, 0 => -1,
1 => 4, 1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT,
], ],
'set too large key length' => [ 'set 0 key length, default char set' => [
0 => -1,
1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT,
],
'set too large key length, default char set' => [
0 => 300, 0 => 300,
1 => 4, 1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT,
], ],
'set override key lenght' => [ 'set override key lenght, default char set' => [
0 => 6, 0 => 6,
1 => 6, 1 => 6,
], ],
// just character set
'default key length, different char set A' => [
0 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT,
1 => \CoreLibs\Create\RandomKey::KEY_LENGTH_DEFAULT,
2 => [
'A', 'B', 'C'
],
],
'different key length, different char set B' => [
0 => 16,
1 => 16,
2 => [
'A', 'B', 'C'
],
3 => [
'1', '2', '3'
]
],
]; ];
} }
// Alternative more efficient version using strpos
/** /**
* 1 * check if all characters are in set
* *
* @return array * @param string $input
* @param string $allowed_chars
* @return bool
*/ */
public function keepKeyLengthProvider(): array private function allCharsInSet(string $input, string $allowed_chars): bool
{ {
return [ $inputLength = strlen($input);
'set too large' => [
0 => 6,
1 => 300,
2 => 6,
],
'set too small' => [
0 => 8,
1 => -2,
2 => 8,
],
'change valid' => [
0 => 10,
1 => 6,
2 => 6,
]
];
}
/** for ($i = 0; $i < $inputLength; $i++) {
* run before each test and reset to default 4 if (strpos($allowed_chars, $input[$i]) === false) {
* return false;
* @before }
*
* @return void
*/
public function resetKeyLength(): void
{
\CoreLibs\Create\RandomKey::setRandomKeyLength(4);
}
/**
* check that first length is 4
*
* @covers ::getRandomKeyLength
* @testWith [4]
* @testdox getRandomKeyLength on init will be $expected [$_dataName]
*
* @param integer $expected
* @return void
*/
public function testGetRandomKeyLengthInit(int $expected): void
{
$this->assertEquals(
$expected,
\CoreLibs\Create\RandomKey::getRandomKeyLength()
);
}
/**
* Undocumented function
*
* @covers ::setRandomKeyLength
* @covers ::getRandomKeyLength
* @dataProvider keyLenghtProvider
* @testdox setRandomKeyLength $input will be $expected, compare to $compare [$_dataName]
*
* @param integer $input
* @param boolean $expected
* @param integer $compare
* @return void
*/
public function testSetRandomKeyLength(int $input, bool $expected, int $compare): void
{
// set
$this->assertEquals(
$expected,
\CoreLibs\Create\RandomKey::setRandomKeyLength($input)
);
// read test, if false, use compare check
if ($expected === false) {
$input = $compare;
} }
$this->assertEquals(
$input, return true;
\CoreLibs\Create\RandomKey::getRandomKeyLength()
);
} }
/** /**
@@ -163,43 +95,41 @@ final class CoreLibsCreateRandomKeyTest extends TestCase
* @param integer $expected * @param integer $expected
* @return void * @return void
*/ */
public function testRandomKeyGen(?int $input, int $expected): void public function testRandomKeyGen(?int $input, int $expected, array ...$key_range): void
{ {
$__key_data = \CoreLibs\Create\RandomKey::KEY_CHARACTER_RANGE_DEFAULT;
if (count($key_range)) {
$__key_data = join('', array_unique(array_merge(...$key_range)));
}
if ($input === null) { if ($input === null) {
$this->assertEquals( $this->assertEquals(
$expected, $expected,
strlen(\CoreLibs\Create\RandomKey::randomKeyGen()) strlen(\CoreLibs\Create\RandomKey::randomKeyGen())
); );
} else { } elseif ($input !== null && !count($key_range)) {
$random_key = \CoreLibs\Create\RandomKey::randomKeyGen($input);
$this->assertTrue(
$this->allCharsInSet($random_key, $__key_data),
'Characters not valid'
);
$this->assertEquals( $this->assertEquals(
$expected, $expected,
strlen(\CoreLibs\Create\RandomKey::randomKeyGen($input)) strlen($random_key),
'String length not matching'
);
} elseif (count($key_range)) {
$random_key = \CoreLibs\Create\RandomKey::randomKeyGen($input, ...$key_range);
$this->assertTrue(
$this->allCharsInSet($random_key, $__key_data),
'Characters not valid'
);
$this->assertEquals(
$expected,
strlen($random_key),
'String length not matching'
); );
} }
} }
/**
* Check that if set to n and then invalid, it keeps the previous one
* or if second change valid, second will be shown
*
* @covers ::setRandomKeyLength
* @dataProvider keepKeyLengthProvider
* @testdox keep setRandomKeyLength set with $input_valid and then $input_invalid will be $expected [$_dataName]
*
* @param integer $input_valid
* @param integer $input_invalid
* @param integer $expected
* @return void
*/
public function testKeepKeyLength(int $input_valid, int $input_invalid, int $expected): void
{
\CoreLibs\Create\RandomKey::setRandomKeyLength($input_valid);
\CoreLibs\Create\RandomKey::setRandomKeyLength($input_invalid);
$this->assertEquals(
$expected,
\CoreLibs\Create\RandomKey::getRandomKeyLength()
);
}
} }
// __END__ // __END__

View File

@@ -135,6 +135,7 @@ final class CoreLibsDBIOTest extends TestCase
} }
// check if they already exist, drop them // check if they already exist, drop them
if ($db->dbShowTableMetaData('table_with_primary_key') !== false) { if ($db->dbShowTableMetaData('table_with_primary_key') !== false) {
$db->dbExec("CREATE EXTENSION IF NOT EXISTS pgcrypto");
$db->dbExec("DROP TABLE table_with_primary_key"); $db->dbExec("DROP TABLE table_with_primary_key");
$db->dbExec("DROP TABLE table_without_primary_key"); $db->dbExec("DROP TABLE table_without_primary_key");
$db->dbExec("DROP TABLE test_meta"); $db->dbExec("DROP TABLE test_meta");
@@ -3692,7 +3693,7 @@ final class CoreLibsDBIOTest extends TestCase
* *
* @return array * @return array
*/ */
public function preparedProviderValue(): array public function providerDbGetPrepareCursorValue(): array
{ {
// 1: query (can be empty for do not set) // 1: query (can be empty for do not set)
// 2: stm name // 2: stm name
@@ -3736,7 +3737,7 @@ final class CoreLibsDBIOTest extends TestCase
* test return prepare cursor errors * test return prepare cursor errors
* *
* @covers ::dbGetPrepareCursorValue * @covers ::dbGetPrepareCursorValue
* @dataProvider preparedProviderValue * @dataProvider providerDbGetPrepareCursorValue
* @testdox prepared query $stm_name with $key expect error id $error_id [$_dataName] * @testdox prepared query $stm_name with $key expect error id $error_id [$_dataName]
* *
* @param string $query * @param string $query
@@ -3769,6 +3770,94 @@ final class CoreLibsDBIOTest extends TestCase
); );
} }
/**
* Undocumented function
*
* @return array
*/
public function providerDbPreparedCursorStatus(): array
{
return [
'empty statement pararm' => [
'query' => 'SELECT row_int, uid FROM table_with_primary_key',
'stm_name' => 'test_stm_a',
'check_stm_name' => '',
'check_query' => '',
'expected' => false
],
'different stm_name' => [
'query' => 'SELECT row_int, uid FROM table_with_primary_key',
'stm_name' => 'test_stm_b',
'check_stm_name' => 'other_name',
'check_query' => '',
'expected' => 0
],
'same stm_name' => [
'query' => 'SELECT row_int, uid FROM table_with_primary_key',
'stm_name' => 'test_stm_c',
'check_stm_name' => 'test_stm_c',
'check_query' => '',
'expected' => 1
],
'same stm_name and query' => [
'query' => 'SELECT row_int, uid FROM table_with_primary_key',
'stm_name' => 'test_stm_d',
'check_stm_name' => 'test_stm_d',
'check_query' => 'SELECT row_int, uid FROM table_with_primary_key',
'expected' => 2
],
'same stm_name and different query' => [
'query' => 'SELECT row_int, uid FROM table_with_primary_key',
'stm_name' => 'test_stm_e',
'check_stm_name' => 'test_stm_e',
'check_query' => 'SELECT row_int, uid, row_int FROM table_with_primary_key',
'expected' => 1
],
'insert query test' => [
'query' => 'INSERT INTO table_with_primary_key (row_int, uid) VALUES ($1, $2)',
'stm_name' => 'test_stm_f',
'check_stm_name' => 'test_stm_f',
'check_query' => 'INSERT INTO table_with_primary_key (row_int, uid) VALUES ($1, $2)',
'expected' => 2
]
];
}
/**
* test cursor status for prepared statement
*
* @covers ::dbPreparedCursorStatus
* @dataProvider providerDbPreparedCursorStatus
* @testdox Check prepared $stm_name ($check_stm_name) status is $expected [$_dataName]
*
* @param string $query
* @param string $stm_name
* @param string $check_stm_name
* @param string $check_query
* @param bool|int $expected
* @return void
*/
public function testDbPreparedCursorStatus(
string $query,
string $stm_name,
string $check_stm_name,
string $check_query,
bool|int $expected
): void {
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
);
$db->dbPrepare($stm_name, $query);
// $db->dbExecute($stm_name);
$this->assertEquals(
$expected,
$db->dbPreparedCursorStatus($check_stm_name, $check_query),
'check prepared stement cursor status'
);
unset($db);
}
// - schema set/get tests // - schema set/get tests
// dbGetSchema, dbSetSchema // dbGetSchema, dbSetSchema
@@ -4656,7 +4745,7 @@ final class CoreLibsDBIOTest extends TestCase
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); $res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
// all hast to be string // all hast to be string
foreach ($res as $key => $value) { foreach ($res as $key => $value) {
$this->assertIsString($value, 'Aseert string for column: ' . $key); $this->assertIsString($value, 'Assert string for column: ' . $key);
} }
// convert base only // convert base only
$db->dbSetConvertFlag(Convert::on); $db->dbSetConvertFlag(Convert::on);
@@ -4669,10 +4758,10 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -4686,13 +4775,13 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
case 'float': case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -4706,17 +4795,17 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
case 'float': case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break; break;
case 'json': case 'json':
case 'jsonb': case 'jsonb':
$this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name); $this->assertIsArray($value, 'Assert array for column: ' . $key . '/' . $name);
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -4730,25 +4819,25 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
case 'float': case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break; break;
case 'json': case 'json':
case 'jsonb': case 'jsonb':
$this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name); $this->assertIsArray($value, 'Assert array for column: ' . $key . '/' . $name);
break; break;
case 'bytea': case 'bytea':
// for hex types it must not start with \x // for hex types it must not start with \x
$this->assertStringStartsNotWith( $this->assertStringStartsNotWith(
'\x', '\x',
$value, $value,
'Aseert bytes not starts with \x for column: ' . $key . '/' . $name 'Assert bytes not starts with \x for column: ' . $key . '/' . $name
); );
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -4920,8 +5009,8 @@ final class CoreLibsDBIOTest extends TestCase
) )
), ),
($params === null ? ($params === null ?
$db->dbGetQueryHash($query) : $db->dbBuildQueryHash($query) :
$db->dbGetQueryHash($query, $params) $db->dbBuildQueryHash($query, $params)
), ),
'Failed assertdbGetQueryHash ' 'Failed assertdbGetQueryHash '
); );
@@ -5147,6 +5236,9 @@ final class CoreLibsDBIOTest extends TestCase
$3 $3
-- comment 3 -- comment 3
, $4 , $4
-- ignore $5, $6
-- $7, $8
-- digest($9, 10)
) )
SQL, SQL,
'count' => 4, 'count' => 4,
@@ -5217,8 +5309,57 @@ final class CoreLibsDBIOTest extends TestCase
SQL, SQL,
'count' => 2, 'count' => 2,
'convert' => false, 'convert' => false,
],
// special $$ string case
'text string, with $ placehoders that could be seen as $$ string' => [
'query' => <<<SQL
SELECT row_int
FROM table_with_primary_key
WHERE
row_bytea = digest($3::VARCHAR, $4) OR
row_varchar = encode(digest($3, $4), 'hex') OR
row_bytea = hmac($3, $5, $4) OR
row_varchar = encode(hmac($3, $5, $4), 'hex') OR
row_bytea = pgp_sym_encrypt($3, $6) OR
row_varchar = encode(pgp_sym_encrypt($1, $6), 'hex') OR
row_varchar = CASE WHEN row_int = 1 THEN $1 ELSE $2 END
SQL,
'count' => 6,
'convert' => false,
],
// NOTE, in SQL heredoc we cannot write $$ strings parts
'text string, with $ placehoders are in $$ strings' => [
'query' => '
SELECT row_int
FROM table_with_primary_key
WHERE
row_varchar = $$some string$$ OR
row_varchar = $tag$some string$tag$ OR
row_varchar = $btag$some $1 string$btag$ OR
row_varchar = $btag$some $1 $subtag$ something $subtag$string$btag$ OR
row_varchar = $1
',
'count' => 1,
'convert' => false,
],
// a text string with escaped quite
'text string, with escaped quote' => [
'query' => <<<SQL
SELECT row_int
FROM table_with_primary_key
WHERE
row_varchar = 'foo bar bar baz $5' OR
row_varchar = 'foo bar '' barbar $6' OR
row_varchar = E'foo bar \' barbar $7' OR
row_varchar = CASE WHEN row_int = 1 THEN $1 ELSE $2 END
SQL,
'count' => 2,
'convert' => false,
] ]
]; ];
$string = <<<SQL
'''
SQL;
} }
/** /**

View File

@@ -249,7 +249,7 @@ final class CoreLibsLoggingLoggingTest extends TestCase
$this->assertFalse( $this->assertFalse(
$log->loggingLevelIsDebug() $log->loggingLevelIsDebug()
); );
// not set, should be debug] // not set, should be debug
$log = new \CoreLibs\Logging\Logging([ $log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testSetLoggingLevel', 'log_file_id' => 'testSetLoggingLevel',
'log_folder' => self::LOG_FOLDER, 'log_folder' => self::LOG_FOLDER,
@@ -297,6 +297,71 @@ final class CoreLibsLoggingLoggingTest extends TestCase
$log->setLoggingLevel('NotGood'); $log->setLoggingLevel('NotGood');
} }
/**
* Undocumented function
*
* @covers ::setErrorLogWriteLevel
* @covers ::getErrorLogWriteLevel
* @testdox setErrorLogWriteLevel set/get checks
*
* @return void
*/
public function testSetErrorLogWriteLevel(): void
{
// valid that is not Debug
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testSetErrorLogWriteLevel',
'log_folder' => self::LOG_FOLDER,
'error_log_write_level' => Level::Error
]);
$this->assertEquals(
Level::Error,
$log->getErrorLogWriteLevel()
);
// not set on init
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testSetErrorLogWriteLevel',
'log_folder' => self::LOG_FOLDER,
]);
$this->assertEquals(
Level::Emergency,
$log->getErrorLogWriteLevel()
);
// invalid, should be Emergency, will throw excpetion too
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage(
'Option: "error_log_write_level" is not of instance \CoreLibs\Logging\Logger\Level'
);
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testSetLoggingLevel',
'log_folder' => self::LOG_FOLDER,
'error_log_write_level' => 'I'
]);
$this->assertEquals(
Level::Emergency,
$log->getErrorLogWriteLevel()
);
// set valid then change
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testSetErrorLogWriteLevel',
'log_folder' => self::LOG_FOLDER,
'error_log_write_level' => Level::Error
]);
$this->assertEquals(
Level::Error,
$log->getErrorLogWriteLevel()
);
$log->setErrorLogWriteLevel(Level::Notice);
$this->assertEquals(
Level::Notice,
$log->getErrorLogWriteLevel()
);
// illegal logging level
$this->expectException(\Psr\Log\InvalidArgumentException::class);
$this->expectExceptionMessageMatches("/^Level \"NotGood\" is not defined, use one of: /");
$log->setErrorLogWriteLevel('NotGood');
}
// setLogFileId // setLogFileId
// getLogFileId // getLogFileId
@@ -395,7 +460,7 @@ final class CoreLibsLoggingLoggingTest extends TestCase
} }
$per_run_id = $log->getLogUniqueId(); $per_run_id = $log->getLogUniqueId();
$this->assertMatchesRegularExpression( $this->assertMatchesRegularExpression(
"/^\d{4}-\d{2}-\d{2}_\d{6}_U_[a-z0-9]{8}$/", "/^\d{4}-\d{2}-\d{2}_\d{6}\.U_[a-z0-9]{8}$/",
$per_run_id, $per_run_id,
'assert per log run id 1st' 'assert per log run id 1st'
); );
@@ -403,7 +468,7 @@ final class CoreLibsLoggingLoggingTest extends TestCase
$log->setLogUniqueId(true); $log->setLogUniqueId(true);
$per_run_id_2nd = $log->getLogUniqueId(); $per_run_id_2nd = $log->getLogUniqueId();
$this->assertMatchesRegularExpression( $this->assertMatchesRegularExpression(
"/^\d{4}-\d{2}-\d{2}_\d{6}_U_[a-z0-9]{8}$/", "/^\d{4}-\d{2}-\d{2}_\d{6}\.U_[a-z0-9]{8}$/",
$per_run_id_2nd, $per_run_id_2nd,
'assert per log run id 2nd' 'assert per log run id 2nd'
); );
@@ -824,13 +889,13 @@ final class CoreLibsLoggingLoggingTest extends TestCase
$this->assertTrue($log_ok, 'assert ::log (debug) OK'); $this->assertTrue($log_ok, 'assert ::log (debug) OK');
$this->assertEquals( $this->assertEquals(
$log->getLogFile(), $log->getLogFile(),
$log->getLogFileId() . '_DEBUG.log' $log->getLogFileId() . '.DEBUG.log'
); );
$log_ok = $log->log(Level::Info, 'INFO', group_id: 'GROUP_ID', prefix: 'PREFIX:'); $log_ok = $log->log(Level::Info, 'INFO', group_id: 'GROUP_ID', prefix: 'PREFIX:');
$this->assertTrue($log_ok, 'assert ::log (info) OK'); $this->assertTrue($log_ok, 'assert ::log (info) OK');
$this->assertEquals( $this->assertEquals(
$log->getLogFile(), $log->getLogFile(),
$log->getLogFileId() . '_INFO.log' $log->getLogFileId() . '.INFO.log'
); );
} }

View File

@@ -13,6 +13,11 @@ use PHPUnit\Framework\TestCase;
*/ */
final class CoreLibsSecurityPasswordTest extends TestCase final class CoreLibsSecurityPasswordTest extends TestCase
{ {
/**
* Undocumented function
*
* @return array
*/
public function passwordProvider(): array public function passwordProvider(): array
{ {
return [ return [
@@ -21,6 +26,11 @@ final class CoreLibsSecurityPasswordTest extends TestCase
]; ];
} }
/**
* Note: we need different hash types for PHP versions
*
* @return array
*/
public function passwordRehashProvider(): array public function passwordRehashProvider(): array
{ {
return [ return [
@@ -63,6 +73,10 @@ final class CoreLibsSecurityPasswordTest extends TestCase
*/ */
public function testPasswordRehashCheck(string $input, bool $expected): void public function testPasswordRehashCheck(string $input, bool $expected): void
{ {
// in PHP 8.4 the length is $12
if (PHP_VERSION_ID > 80400) {
$input = str_replace('$2y$10$', '$2y$12$', $input);
}
$this->assertEquals( $this->assertEquals(
$expected, $expected,
\CoreLibs\Security\Password::passwordRehashCheck($input) \CoreLibs\Security\Password::passwordRehashCheck($input)

View File

@@ -969,44 +969,76 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase
"query" => ["foo-get" => "bar"] "query" => ["foo-get" => "bar"]
]); ]);
$this->assertEquals("200", $response["code"], "multi call: get response code not matching"); $this->assertEquals("200", $response["code"], "multi call: get response code not matching");
$this->assertEquals( if (PHP_VERSION_ID >= 80400) {
'{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' $this->assertEquals(
. '"HTTP_FIRST_CALL":"get","HTTP_ACCEPT":"*\/*",' '{"HEADERS":{"HTTP_HOST":"soba.egplusww.jp",'
. '"HTTP_HOST":"soba.egplusww.jp"},' . '"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_FIRST_CALL":"get",'
. '"REQUEST_TYPE":"GET",' . '"HTTP_ACCEPT":"*\/*"},"REQUEST_TYPE":"GET","PARAMS":{"foo-get":"bar"},"BODY":null}',
. '"PARAMS":{"foo-get":"bar"},"BODY":null}', $response['content'],
$response['content'], 'multi call: get content not matching'
'multi call: get content not matching' );
); } else {
$this->assertEquals(
'{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",'
. '"HTTP_FIRST_CALL":"get","HTTP_ACCEPT":"*\/*",'
. '"HTTP_HOST":"soba.egplusww.jp"},'
. '"REQUEST_TYPE":"GET",'
. '"PARAMS":{"foo-get":"bar"},"BODY":null}',
$response['content'],
'multi call: get content not matching'
);
}
// post // post
$response = $curl->post($this->url_basic, [ $response = $curl->post($this->url_basic, [
"headers" => ["second-call" => "post"], "headers" => ["second-call" => "post"],
"body" => ["foo-post" => "baz"] "body" => ["foo-post" => "baz"]
]); ]);
$this->assertEquals("200", $response["code"], "multi call: post response code not matching"); $this->assertEquals("200", $response["code"], "multi call: post response code not matching");
$this->assertEquals( if (PHP_VERSION_ID >= 80400) {
'{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' $this->assertEquals(
. '"HTTP_SECOND_CALL":"post","HTTP_ACCEPT":"*\/*",' '{"HEADERS":{"HTTP_HOST":"soba.egplusww.jp",'
. '"HTTP_HOST":"soba.egplusww.jp"},' . '"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",'
. '"REQUEST_TYPE":"POST",' . '"HTTP_SECOND_CALL":"post","HTTP_ACCEPT":"*\/*"},'
. '"PARAMS":[],"BODY":{"foo-post":"baz"}}', . '"REQUEST_TYPE":"POST","PARAMS":[],"BODY":{"foo-post":"baz"}}',
$response['content'], $response['content'],
'multi call: post content not matching' 'multi call: post content not matching'
); );
} else {
$this->assertEquals(
'{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",'
. '"HTTP_SECOND_CALL":"post","HTTP_ACCEPT":"*\/*",'
. '"HTTP_HOST":"soba.egplusww.jp"},'
. '"REQUEST_TYPE":"POST",'
. '"PARAMS":[],"BODY":{"foo-post":"baz"}}',
$response['content'],
'multi call: post content not matching'
);
}
// delete // delete
$response = $curl->delete($this->url_basic, [ $response = $curl->delete($this->url_basic, [
"headers" => ["third-call" => "delete"], "headers" => ["third-call" => "delete"],
]); ]);
$this->assertEquals("200", $response["code"], "multi call: delete response code not matching"); $this->assertEquals("200", $response["code"], "multi call: delete response code not matching");
$this->assertEquals( if (PHP_VERSION_ID >= 80400) {
'{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",' $this->assertEquals(
. '"HTTP_THIRD_CALL":"delete","HTTP_ACCEPT":"*\/*",' '{"HEADERS":{"HTTP_HOST":"soba.egplusww.jp",'
. '"HTTP_HOST":"soba.egplusww.jp"},' . '"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",'
. '"REQUEST_TYPE":"DELETE",' . '"HTTP_THIRD_CALL":"delete","HTTP_ACCEPT":"*\/*"},'
. '"PARAMS":[],"BODY":[]}', . '"REQUEST_TYPE":"DELETE","PARAMS":[],"BODY":[]}',
$response['content'], $response['content'],
'multi call: delete content not matching' 'multi call: delete content not matching'
); );
} else {
$this->assertEquals(
'{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",'
. '"HTTP_THIRD_CALL":"delete","HTTP_ACCEPT":"*\/*",'
. '"HTTP_HOST":"soba.egplusww.jp"},'
. '"REQUEST_TYPE":"DELETE",'
. '"PARAMS":[],"BODY":[]}',
$response['content'],
'multi call: delete content not matching'
);
}
} }
// MARK: auth header set via config // MARK: auth header set via config