Compare commits

...

17 Commits

Author SHA1 Message Date
Clemens Schwaighofer
0f7bf0ab44 Session class rewrite 2024-12-04 14:22:26 +09:00
Clemens Schwaighofer
10dc56c7cb Release: v9.22.0 2024-12-03 13:34:56 +09:00
Clemens Schwaighofer
d19842007c ACL Login cuid and cuuid update and add 2024-12-03 13:29:50 +09:00
Clemens Schwaighofer
c5bd16de74 Release: v9.21.1 2024-12-02 17:20:25 +09:00
Clemens Schwaighofer
e580e5430c Add ECUID (edit user cuid) to session and return in acl array 2024-12-02 17:18:34 +09:00
Clemens Schwaighofer
da9717265c Release: v9.21.0 2024-12-02 15:55:33 +09:00
Clemens Schwaighofer
6c6b33cacc add uuidv4 verify method 2024-12-02 15:54:35 +09:00
Clemens Schwaighofer
5509d1c92e Release: v9.20.1 2024-11-27 14:43:58 +09:00
Clemens Schwaighofer
16d789f061 Merge branch 'development' 2024-11-27 14:42:18 +09:00
Clemens Schwaighofer
3450b6263b Fixes in SQL PgSQL for identity column 2024-11-27 14:42:04 +09:00
Clemens Schwaighofer
bcc1e833c1 Release: v9.20.0 2024-11-20 22:54:37 +09:00
Clemens Schwaighofer
fea4c315f3 Merge branch 'development' 2024-11-20 22:52:27 +09:00
Clemens Schwaighofer
3ac57e05b0 DB SQL placeholder code updated 2024-11-20 22:52:15 +09:00
Clemens Schwaighofer
b0230e7050 Release: v9.19.1 2024-11-19 12:04:32 +09:00
Clemens Schwaighofer
70f78a5f9c Math update for Matrix multiplications 2024-11-19 12:03:12 +09:00
Clemens Schwaighofer
814ec50d3d phpstan 2.0 update 2024-11-18 18:34:31 +09:00
Clemens Schwaighofer
e60ce3f0dc Release: v9.19.0 2024-11-18 15:04:03 +09:00
26 changed files with 1628 additions and 1075 deletions

View File

@@ -20,7 +20,9 @@
"psr/log": "^3.0@dev" "psr/log": "^3.0@dev"
}, },
"require-dev": { "require-dev": {
"phpstan/phpstan": "^1.12", "phpstan/phpstan": "^2.0",
"phpstan/phpdoc-parser": "^2.0",
"phpstan/phpstan-deprecation-rules": "^2.0",
"phan/phan": "^5.4", "phan/phan": "^5.4",
"egrajp/smarty-extended": "^4.3", "egrajp/smarty-extended": "^4.3",
"gullevek/dotenv": "dev-master", "gullevek/dotenv": "dev-master",

View File

@@ -1 +1 @@
9.18.1 9.22.0

View File

@@ -69,12 +69,17 @@ declare(strict_types=1);
namespace CoreLibs\ACL; namespace CoreLibs\ACL;
use CoreLibs\Security\Password; use CoreLibs\Security\Password;
use CoreLibs\Create\Uids;
use CoreLibs\Convert\Json; use CoreLibs\Convert\Json;
class Login class Login
{ {
/** @var ?int the user id var*/ /** @var ?int the user id var*/
private ?int $euid; private ?int $euid;
/** @var ?string the user cuid (note will be super seeded with uuid v4 later) */
private ?string $ecuid;
/** @var ?string UUIDv4, will superseed the ecuid and replace euid as login id */
private ?string $ecuuid;
/** @var string _GET/_POST loginUserId parameter for non password login */ /** @var string _GET/_POST loginUserId parameter for non password login */
private string $login_user_id = ''; private string $login_user_id = '';
/** @var string source, either _GET or _POST or empty */ /** @var string source, either _GET or _POST or empty */
@@ -193,6 +198,12 @@ class Login
/** @var bool */ /** @var bool */
private bool $login_is_ajax_page = false; private bool $login_is_ajax_page = false;
// logging
/** @var array<string> list of allowed types for edit log write */
private const WRITE_TYPES = ['BINARY', 'BZIP2', 'LZIP', 'STRING', 'SERIAL', 'JSON'];
/** @var array<string> list of available write types for log */
private array $write_types_available = [];
// settings // settings
/** @var array<string,mixed> options */ /** @var array<string,mixed> options */
private array $options = []; private array $options = [];
@@ -379,6 +390,8 @@ class Login
$_SESSION['DEFAULT_ACL_LIST'] = $this->default_acl_list; $_SESSION['DEFAULT_ACL_LIST'] = $this->default_acl_list;
$_SESSION['DEFAULT_ACL_LIST_TYPE'] = $this->default_acl_list_type; $_SESSION['DEFAULT_ACL_LIST_TYPE'] = $this->default_acl_list_type;
$this->loginSetEditLogWriteTypeAvailable();
// this will be deprecated // this will be deprecated
if ($this->options['auto_login'] === true) { if ($this->options['auto_login'] === true) {
$this->loginMainCall(); $this->loginMainCall();
@@ -757,7 +770,7 @@ class Login
} }
// have to get the global stuff here for setting it later // have to get the global stuff here for setting it later
// we have to get the themes in here too // we have to get the themes in here too
$q = "SELECT eu.edit_user_id, eu.username, eu.password, " $q = "SELECT eu.edit_user_id, eu.cuid, eu.cuuid, eu.username, eu.password, "
. "eu.edit_group_id, " . "eu.edit_group_id, "
. "eg.name AS edit_group_name, eu.admin, " . "eg.name AS edit_group_name, eu.admin, "
// additinal acl lists // additinal acl lists
@@ -889,6 +902,8 @@ class Login
// normal user processing // normal user processing
// set class var and session var // set class var and session var
$_SESSION['EUID'] = $this->euid = (int)$res['edit_user_id']; $_SESSION['EUID'] = $this->euid = (int)$res['edit_user_id'];
$_SESSION['ECUID'] = $this->ecuid = (string)$res['cuid'];
$_SESSION['ECUUID'] = $this->ecuuid = (string)$res['cuuid'];
// check if user is okay // check if user is okay
$this->loginCheckPermissions(); $this->loginCheckPermissions();
if ($this->login_error == 0) { if ($this->login_error == 0) {
@@ -960,10 +975,7 @@ class Login
. "AND ear.edit_access_right_id = epa.edit_access_right_id " . "AND ear.edit_access_right_id = epa.edit_access_right_id "
. "AND epa.enabled = 1 AND epa.edit_group_id = " . $res["edit_group_id"] . " " . "AND epa.enabled = 1 AND epa.edit_group_id = " . $res["edit_group_id"] . " "
. "ORDER BY ep.order_number"; . "ORDER BY ep.order_number";
while ($res = $this->db->dbReturn($q)) { while (is_array($res = $this->db->dbReturn($q))) {
if (!is_array($res)) {
break;
}
// page id array for sub data readout // page id array for sub data readout
$edit_page_ids[$res['edit_page_id']] = $res['cuid']; $edit_page_ids[$res['edit_page_id']] = $res['cuid'];
// create the array for pages // create the array for pages
@@ -1135,6 +1147,9 @@ class Login
// username (login), group name // username (login), group name
$this->acl['user_name'] = $_SESSION['USER_NAME']; $this->acl['user_name'] = $_SESSION['USER_NAME'];
$this->acl['group_name'] = $_SESSION['GROUP_NAME']; $this->acl['group_name'] = $_SESSION['GROUP_NAME'];
// edit user cuid
$this->acl['ecuid'] = $_SESSION['ECUID'];
$this->acl['ecuuid'] = $_SESSION['ECUUID'];
// set additional acl // set additional acl
$this->acl['additional_acl'] = [ $this->acl['additional_acl'] = [
'user' => $_SESSION['USER_ADDITIONAL_ACL'], 'user' => $_SESSION['USER_ADDITIONAL_ACL'],
@@ -1303,13 +1318,11 @@ class Login
{ {
$is_valid_password = true; $is_valid_password = true;
// check for valid in regex arrays in list // check for valid in regex arrays in list
if (is_array($this->password_valid_chars)) {
foreach ($this->password_valid_chars as $password_valid_chars) { foreach ($this->password_valid_chars as $password_valid_chars) {
if (!preg_match("/$password_valid_chars/", $password)) { if (!preg_match("/$password_valid_chars/", $password)) {
$is_valid_password = false; $is_valid_password = false;
} }
} }
}
// check for min length // check for min length
if ( if (
strlen($password) < $this->password_min_length || strlen($password) < $this->password_min_length ||
@@ -1430,7 +1443,7 @@ class Login
$data = 'Illegal user for password change: ' . $this->pw_username; $data = 'Illegal user for password change: ' . $this->pw_username;
} }
// log this password change attempt // log this password change attempt
$this->writeLog($event, $data, $this->login_error, $this->pw_username); $this->writeEditLog($event, $data, $this->login_error, $this->pw_username);
} }
/** /**
@@ -1571,7 +1584,7 @@ class Login
$username = $res['username']; $username = $res['username'];
} }
} // if euid is set, get username (or try) } // if euid is set, get username (or try)
$this->writeLog($event, '', $this->login_error, $username); $this->writeEditLog($event, '', $this->login_error, $username);
} // write log under certain settings } // write log under certain settings
// now close DB connection // now close DB connection
// $this->error_msg = $this->_login(); // $this->error_msg = $this->_login();
@@ -1727,6 +1740,8 @@ HTML;
} }
} }
// MARK: LOGGING
/** /**
* writes detailed data into the edit user log table (keep log what user does) * writes detailed data into the edit user log table (keep log what user does)
* *
@@ -1736,7 +1751,7 @@ HTML;
* @param string $username login user username * @param string $username login user username
* @return void has no return * @return void has no return
*/ */
private function writeLog( private function writeEditLog(
string $event, string $event,
string $data, string $data,
string|int $error = '', string|int $error = '',
@@ -1754,50 +1769,191 @@ HTML;
'_GET' => $_GET, '_GET' => $_GET,
'_POST' => $_POST, '_POST' => $_POST,
'_FILES' => $_FILES, '_FILES' => $_FILES,
'error' => $this->login_error 'error' => $this->login_error,
'data' => $data,
]; ];
$data_binary = $this->db->dbEscapeBytea((string)bzcompress(serialize($_data_binary))); $_action_set = [
// SQL querie for log entry 'action' => $this->action,
$q = "INSERT INTO edit_log " 'action_id' => $this->username,
. "(username, password, euid, event_date, event, error, data, data_binary, page, " 'action_flag' => (string)$this->login_error,
. "ip, user_agent, referer, script_name, query_string, server_name, http_host, " 'action_value' => (string)$this->permission_okay,
. "http_accept, http_accept_charset, http_accept_encoding, session_id, " ];
. "action, action_id, action_yes, action_flag, action_menu, action_loaded, "
. "action_value, action_error) " $this->writeLog($event, $_data_binary, $_action_set, $error, $username);
. "VALUES ('" . $this->db->dbEscapeString($username) . "', 'PASSWORD', " }
. ($this->euid ? $this->euid : 'NULL') . ", "
. "NOW(), '" . $this->db->dbEscapeString($event) . "', " /**
. "'" . $this->db->dbEscapeString((string)$error) . "', " * writes all action vars plus other info into edit_log table
. "'" . $this->db->dbEscapeString($data) . "', '" . $data_binary . "', " * this is for public class
. "'" . $this->page_name . "', "; *
foreach ( * phpcs:disable Generic.Files.LineLength
* @param string $event [default=''] any kind of event description,
* @param string|array<mixed> $data [default=''] any kind of data related to that event
* @param array{action?:?string,action_id?:null|string|int,action_sub_id?:null|string|int,action_yes?:null|string|int|bool,action_flag?:?string,action_menu?:?string,action_loaded?:?string,action_value?:?string,action_type?:?string,action_error?:?string} $action_set [default=[]] action set names
* @param string|int $error error id (mostly an int)
* @param string $write_type [default=JSON] write type can be
* JSON, STRING/SERIEAL, BINARY/BZIP or ZLIB
* @param string|null $db_schema [default=null] override target schema
* @return void
* phpcs:enable Generic.Files.LineLength
*/
public function writeLog(
string $event = '',
string|array $data = '',
array $action_set = [],
string|int $error = '',
string $username = '',
string $write_type = 'JSON',
?string $db_schema = null
): void {
$data_binary = '';
$data_write = '';
// check if write type is valid, if not fallback to JSON
if (!in_array(strtoupper($write_type), $this->write_types_available)) {
$this->log->warning('Write type not in allowed array, fallback to JSON', context:[
"write_type" => $write_type,
"write_list" => $this->write_types_available,
]);
$write_type = 'JSON';
}
switch ($write_type) {
case 'BINARY':
case 'BZIP':
$data_binary = $this->db->dbEscapeBytea((string)bzcompress(serialize($data)));
$data_write = Json::jsonConvertArrayTo([
'type' => 'BZIP',
'message' => 'see bzip compressed data_binary field'
]);
break;
case 'ZLIB':
$data_binary = $this->db->dbEscapeBytea((string)gzcompress(serialize($data)));
$data_write = Json::jsonConvertArrayTo([
'type' => 'ZLIB',
'message' => 'see zlib compressed data_binary field'
]);
break;
case 'STRING':
case 'SERIAL':
$data_binary = $this->db->dbEscapeBytea(Json::jsonConvertArrayTo([
'type' => 'SERIAL',
'message' => 'see serial string data field'
]));
$data_write = serialize($data);
break;
case 'JSON':
$data_binary = $this->db->dbEscapeBytea(Json::jsonConvertArrayTo([
'type' => 'JSON',
'message' => 'see json string data field'
]));
// must be converted to array
if (!is_array($data)) {
$data = ["data" => $data];
}
$data_write = Json::jsonConvertArrayTo($data);
break;
default:
$this->log->alert('Invalid type for data compression was set', context:[
"write_type" => $write_type
]);
break;
}
/** @var string $DB_SCHEMA check schema */
$DB_SCHEMA = 'public';
if ($db_schema !== null) {
$DB_SCHEMA = $db_schema;
} elseif (!empty($this->db->dbGetSchema())) {
$DB_SCHEMA = $this->db->dbGetSchema();
}
$q = <<<SQL
INSERT INTO {DB_SCHEMA}.edit_log (
username, euid, ecuid, ecuuid, event_date, event, error, data, data_binary, page,
ip, user_agent, referer, script_name, query_string, server_name, http_host,
http_accept, http_accept_charset, http_accept_encoding, session_id,
action, action_id, action_sub_id, action_yes, action_flag, action_menu, action_loaded,
action_value, action_type, action_error
) VALUES (
$1, $2, $3, $4, NOW(), $5, $6, $7, $8, $9,
$10, $11, $12, $13, $14, $15, $16,
$17, $18, $19, $20,
$21, $22, $23, $24, $25, $26, $27,
$28, $29, $30
)
SQL;
$this->db->dbExecParams(
str_replace(
['{DB_SCHEMA}'],
[$DB_SCHEMA],
$q
),
[ [
'REMOTE_ADDR', 'HTTP_USER_AGENT', 'HTTP_REFERER', 'SCRIPT_FILENAME', // row 1
'QUERY_STRING', 'SERVER_NAME', 'HTTP_HOST', 'HTTP_ACCEPT', empty($username) ? $_SESSION['USER_NAME'] ?? '' : $username,
'HTTP_ACCEPT_CHARSET', 'HTTP_ACCEPT_ENCODING' !empty($_SESSION['EUID']) && is_numeric($_SESSION['EUID']) ?
] as $server_code $_SESSION['EUID'] : null,
) { !empty($_SESSION['ECUID']) && is_string($_SESSION['ECUID']) ?
if (array_key_exists($server_code, $_SERVER)) { $_SESSION['ECUID'] : null,
$q .= "'" . $this->db->dbEscapeString($_SERVER[$server_code]) . "', "; !empty($_SESSION['ECUUID']) && Uids::validateUuuidv4($_SESSION['ECUUID']) ?
} else { $_SESSION['ECUUID'] : null,
$q .= "NULL, "; (string)$event,
(string)$error,
$data_write,
$data_binary,
(string)$this->page_name,
// row 2
$_SERVER["REMOTE_ADDR"] ?? null,
$_SERVER['HTTP_USER_AGENT'] ?? null,
$_SERVER['HTTP_REFERER'] ?? null,
$_SERVER['SCRIPT_FILENAME'] ?? null,
$_SERVER['QUERY_STRING'] ?? null,
$_SERVER['SERVER_NAME'] ?? null,
$_SERVER['HTTP_HOST'] ?? null,
// row 3
$_SERVER['HTTP_ACCEPT'] ?? null,
$_SERVER['HTTP_ACCEPT_CHARSET'] ?? null,
$_SERVER['HTTP_ACCEPT_ENCODING'] ?? null,
$this->session->getSessionId() !== '' ?
$this->session->getSessionId() : null,
// row 4
$action_set['action'] ?? null,
$action_set['action_id'] ?? null,
$action_set['action_sub_id'] ?? null,
$action_set['action_yes'] ?? null,
$action_set['action_flag'] ?? null,
$action_set['action_menu'] ?? null,
$action_set['action_loaded'] ?? null,
$action_set['action_value'] ?? null,
$action_set['action_type'] ?? null,
$action_set['action_error'] ?? null,
],
'NULL'
);
} }
/**
* set the write types that are allowed
*
* @return void
*/
private function loginSetEditLogWriteTypeAvailable()
{
// check what edit log data write types are allowed
$this->write_types_available = self::WRITE_TYPES;
if (!function_exists('bzcompress')) {
$this->write_types_available = array_diff($this->write_types_available, ['BINARY', 'BZIP']);
}
if (!function_exists('gzcompress')) {
$this->write_types_available = array_diff($this->write_types_available, ['LZIP']);
} }
$q .= "'" . $this->session->getSessionId() . "', ";
$q .= "'" . $this->db->dbEscapeString($this->action) . "', ";
$q .= "'" . $this->db->dbEscapeString($this->username) . "', ";
$q .= "NULL, ";
$q .= "'" . $this->db->dbEscapeString((string)$this->login_error) . "', ";
$q .= "NULL, NULL, ";
$q .= "'" . $this->db->dbEscapeString((string)$this->permission_okay) . "', ";
$q .= "NULL)";
$this->db->dbExec($q, 'NULL');
} }
// ************************************************************************* // *************************************************************************
// **** PUBLIC INTERNAL // **** PUBLIC INTERNAL
// ************************************************************************* // *************************************************************************
// MARK: LOGIN CALL
/** /**
* Main call that needs to be run to actaully check for login * Main call that needs to be run to actaully check for login
* If this is not called, no login checks are done, unless the class * If this is not called, no login checks are done, unless the class
@@ -1867,6 +2023,9 @@ HTML;
} }
// if there is none, there is none, saves me POST/GET check // if there is none, there is none, saves me POST/GET check
$this->euid = array_key_exists('EUID', $_SESSION) ? (int)$_SESSION['EUID'] : 0; $this->euid = array_key_exists('EUID', $_SESSION) ? (int)$_SESSION['EUID'] : 0;
// TODO: allow load from cuid
// $this->ecuid = array_key_exists('ECUID', $_SESSION) ? (string)$_SESSION['ECUID'] : '';
// $this->ecuuid = array_key_exists('ECUUID', $_SESSION) ? (string)$_SESSION['ECUUID'] : '';
// get login vars, are so, can't be changed // get login vars, are so, can't be changed
// prepare // prepare
// pass on vars to Object vars // pass on vars to Object vars
@@ -1947,6 +2106,8 @@ HTML;
$this->loginSetAcl(); $this->loginSetAcl();
} }
// MARK: setters/getters
/** /**
* Returns current set login_html content * Returns current set login_html content
* *
@@ -2116,6 +2277,8 @@ HTML;
$this->session->sessionDestroy(); $this->session->sessionDestroy();
// unset euid // unset euid
$this->euid = null; $this->euid = null;
$this->ecuid = null;
$this->ecuuid = null;
// then prints the login screen again // then prints the login screen again
$this->permission_okay = false; $this->permission_okay = false;
} }
@@ -2133,11 +2296,12 @@ HTML;
if (empty($this->euid)) { if (empty($this->euid)) {
return $this->permission_okay; return $this->permission_okay;
} }
// euid must match ecuid and ecuuid
// bail for previous wrong page match, eg if method is called twice // bail for previous wrong page match, eg if method is called twice
if ($this->login_error == 103) { if ($this->login_error == 103) {
return $this->permission_okay; return $this->permission_okay;
} }
$q = "SELECT ep.filename, " $q = "SELECT ep.filename, eu.cuid, eu.cuuid, "
// base lock flags // base lock flags
. "eu.deleted, eu.enabled, eu.locked, " . "eu.deleted, eu.enabled, eu.locked, "
// date based lock // date based lock
@@ -2203,6 +2367,9 @@ HTML;
} else { } else {
$this->login_error = 103; $this->login_error = 103;
} }
// set ECUID
$_SESSION['ECUID'] = $this->ecuid = (string)$res['cuid'];
$_SESSION['ECUUID'] = $this->ecuuid = (string)$res['cuuid'];
// if called from public, so we can check if the permissions are ok // if called from public, so we can check if the permissions are ok
return $this->permission_okay; return $this->permission_okay;
} }
@@ -2508,6 +2675,26 @@ HTML;
{ {
return (string)$this->euid; return (string)$this->euid;
} }
/**
* Get the current set ECUID (edit user cuid)
*
* @return string ECUID as string
*/
public function loginGetEcuid(): string
{
return (string)$this->ecuid;
}
/**
* Get the current set ECUUID (edit user cuuid)
*
* @return string ECUUID as string
*/
public function loginGetEcuuid(): string
{
return (string)$this->ecuuid;
}
} }
// __END__ // __END__

View File

@@ -31,6 +31,7 @@ declare(strict_types=1);
namespace CoreLibs\Admin; namespace CoreLibs\Admin;
use CoreLibs\Create\Uids;
use CoreLibs\Convert\Json; use CoreLibs\Convert\Json;
class Backend class Backend
@@ -258,6 +259,27 @@ class Backend
} }
} }
/**
* return all the action data, if not set, sets entry to null
*
* @return array{action:?string,action_id:null|string|int,action_sub_id:null|string|int,action_yes:null|string|int|bool,action_flag:?string,action_menu:?string,action_loaded:?string,action_value:?string,action_type:?string,action_error:?string}
*/
public function adbGetActionSet(): array
{
return [
'action' => $this->action ?? null,
'action_id' => $this->action_id ?? null,
'action_sub_id' => $this->action_sub_id ?? null,
'action_yes' => $this->action_yes ?? null,
'action_flag' => $this->action_flag ?? null,
'action_menu' => $this->action_menu ?? null,
'action_loaded' => $this->action_loaded ?? null,
'action_value' => $this->action_value ?? null,
'action_type' => $this->action_type ?? null,
'action_error' => $this->action_error ?? null,
];
}
/** /**
* writes all action vars plus other info into edit_log table * writes all action vars plus other info into edit_log table
* *
@@ -267,6 +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()
*/ */
public function adbEditLog( public function adbEditLog(
string $event = '', string $event = '',
@@ -335,17 +358,17 @@ class Backend
} }
$q = <<<SQL $q = <<<SQL
INSERT INTO {DB_SCHEMA}.edit_log ( INSERT INTO {DB_SCHEMA}.edit_log (
euid, event_date, event, data, data_binary, page, username, euid, ecuid, ecuuid, event_date, event, error, data, data_binary, page,
ip, user_agent, referer, script_name, query_string, server_name, http_host, ip, user_agent, referer, script_name, query_string, server_name, http_host,
http_accept, http_accept_charset, http_accept_encoding, session_id, http_accept, http_accept_charset, http_accept_encoding, session_id,
action, action_id, action_yes, action_flag, action_menu, action_loaded, action, action_id, action_sub_id, action_yes, action_flag, action_menu, action_loaded,
action_value, action_type, action_error action_value, action_type, action_error
) VALUES ( ) VALUES (
$1, NOW(), $2, $3, $4, $5, $1, $2, $3, $4, NOW(), $5, $6, $7, $8, $9,
$6, $7, $8, $9, $10, $11, $12, $10, $11, $12, $13, $14, $15, $16,
$13, $14, $15, $16, $17, $18, $19, $20,
$17, $18, $19, $20, $21, $22, $21, $22, $23, $24, $25, $26, $27,
$23, $24, $25 $28, $29, $30
) )
SQL; SQL;
$this->db->dbExecParams( $this->db->dbExecParams(
@@ -356,9 +379,15 @@ class Backend
), ),
[ [
// row 1 // row 1
isset($_SESSION['EUID']) && is_numeric($_SESSION['EUID']) ? '',
!empty($_SESSION['EUID']) && is_numeric($_SESSION['EUID']) ?
$_SESSION['EUID'] : null, $_SESSION['EUID'] : null,
!empty($_SESSION['ECUID']) && is_string($_SESSION['ECUID']) ?
$_SESSION['ECUID'] : null,
!empty($_SESSION['ECUUID']) && Uids::validateUuuidv4($_SESSION['ECUID']) ?
$_SESSION['ECUID'] : null,
(string)$event, (string)$event,
'',
$data_write, $data_write,
$data_binary, $data_binary,
(string)$this->page_name, (string)$this->page_name,
@@ -374,11 +403,12 @@ class Backend
$_SERVER['HTTP_ACCEPT'] ?? '', $_SERVER['HTTP_ACCEPT'] ?? '',
$_SERVER['HTTP_ACCEPT_CHARSET'] ?? '', $_SERVER['HTTP_ACCEPT_CHARSET'] ?? '',
$_SERVER['HTTP_ACCEPT_ENCODING'] ?? '', $_SERVER['HTTP_ACCEPT_ENCODING'] ?? '',
$this->session->getSessionId() !== false ? $this->session->getSessionId() !== '' ?
$this->session->getSessionId() : null, $this->session->getSessionId() : null,
// row 4 // row 4
$this->action ?? '', $this->action ?? '',
$this->action_id ?? '', $this->action_id ?? '',
$this->action_sub_id ?? '',
$this->action_yes ?? '', $this->action_yes ?? '',
$this->action_flag ?? '', $this->action_flag ?? '',
$this->action_menu ?? '', $this->action_menu ?? '',
@@ -425,10 +455,7 @@ class Backend
?string $set_content_path = null, ?string $set_content_path = null,
int $flag = 0, int $flag = 0,
): array { ): array {
if ( if ($set_content_path === null) {
$set_content_path === null ||
!is_string($set_content_path)
) {
/** @deprecated adbTopMenu missing set_content_path parameter */ /** @deprecated adbTopMenu missing set_content_path parameter */
trigger_error( trigger_error(
'Calling adbTopMenu without set_content_path parameter is deprecated', 'Calling adbTopMenu without set_content_path parameter is deprecated',

View File

@@ -119,6 +119,13 @@ class Colors
/** /**
* check if html/css color string is valid * check if html/css color string is valid
*
* TODO: update check for correct validate values
* - space instead of ","
* - / opcatiy checks
* - loose numeric values
* - lab/lch,oklab/oklch validation too
*
* @param string $color A color string of any format * @param string $color A color string of any format
* @param int $flags defaults to ALL, else use | to combined from * @param int $flags defaults to ALL, else use | to combined from
* HEX_RGB, HEX_RGBA, RGB, RGBA, HSL, HSLA * HEX_RGB, HEX_RGBA, RGB, RGBA, HSL, HSLA
@@ -168,9 +175,9 @@ class Colors
if (preg_match("/$regex/", $color)) { if (preg_match("/$regex/", $color)) {
// if valid regex, we now need to check if the content is actually valid // if valid regex, we now need to check if the content is actually valid
// only for rgb/hsl type // only for rgb/hsl type
/** @var int|false */ /** @var int<0, max>|false */
$rgb_flag = strpos($color, 'rgb'); $rgb_flag = strpos($color, 'rgb');
/** @var int|false */ /** @var int<0, max>|false */
$hsl_flag = strpos($color, 'hsl'); $hsl_flag = strpos($color, 'hsl');
// if both not match, return true // if both not match, return true
if ( if (

View File

@@ -246,7 +246,7 @@ class CieXyz
self::convertArray(array_map( self::convertArray(array_map(
fn ($k, $v) => $v * $d50[$k], fn ($k, $v) => $v * $d50[$k],
array_keys($xyz), array_keys($xyz),
array_values($xyz), $xyz,
)), )),
options: ["whitepoint" => 'D50'] options: ["whitepoint" => 'D50']
); );

View File

@@ -26,7 +26,7 @@ class HSB implements Interface\CoordinatesInterface
private float $B = 0.0; private float $B = 0.0;
/** @var string color space: either ok or cie */ /** @var string color space: either ok or cie */
private string $colorspace = ''; private string $colorspace = ''; /** @phpstan-ignore-line */
/** /**
* HSB (HSV) color coordinates * HSB (HSV) color coordinates

View File

@@ -25,7 +25,7 @@ class HSL implements Interface\CoordinatesInterface
/** @var float lightness (luminance) */ /** @var float lightness (luminance) */
private float $L = 0.0; private float $L = 0.0;
/** @var string color space: either ok or cie */ /** @var string color space: either sRGB */
private string $colorspace = ''; private string $colorspace = '';
/** /**

View File

@@ -158,6 +158,8 @@ class Math
* [0, 0, 0] <- automatically added * [0, 0, 0] <- automatically added
* ] * ]
* *
* The same is done for unbalanced entries, they are filled with 0
*
* @param array<float|int|array<int|float>> $a m x n matrice * @param array<float|int|array<int|float>> $a m x n matrice
* @param array<float|int|array<int|float>> $b n x p matrice * @param array<float|int|array<int|float>> $b n x p matrice
* *
@@ -186,7 +188,7 @@ class Math
// so that we can multiply row by row // so that we can multiply row by row
$bCols = array_map( $bCols = array_map(
callback: fn ($k) => array_map( callback: fn ($k) => array_map(
(fn ($i) => is_array($i) ? $i[$k] : 0), (fn ($i) => is_array($i) ? $i[$k] ?? 0 : 0),
$b, $b,
), ),
array: array_keys($b[0]), array: array_keys($b[0]),

View File

@@ -15,19 +15,27 @@ namespace CoreLibs\Create;
class Session class Session
{ {
/** @var string current session name */
private string $session_name = '';
/** @var string current session id */
private string $session_id = '';
/** @var bool flag auto write close */
private bool $auto_write_close = false;
/** /**
* init a session, if array is empty or array does not have session_name set * init a session, if array is empty or array does not have session_name set
* then no auto init is run * then no auto init is run
* *
* @param string $session_name if set and not empty, will start session * @param string $session_name if set and not empty, will start session
*/ */
public function __construct(string $session_name = '') public function __construct(string $session_name, bool $auto_write_close = false)
{ {
if (!empty($session_name)) { $this->initSession($session_name);
$this->startSession($session_name); $this->auto_write_close = $auto_write_close;
}
} }
// MARK: private methods
/** /**
* Start session * Start session
* startSession should be called for complete check * startSession should be called for complete check
@@ -36,36 +44,32 @@ class Session
* *
* @return void * @return void
*/ */
protected function startSessionCall(): void private function startSessionCall(): void
{ {
session_start(); session_start();
} }
/** /**
* check if we are in CLI, we set this, so we can mock this * get current set session id or false if none started
* Not this is just a wrapper for the static System::checkCLI call
* *
* @return bool True if we are in a CLI enviroment, or false for everything else * @return string|false
*/ */
public function checkCliStatus(): bool public function getSessionIdCall(): string|false
{ {
return \CoreLibs\Get\System::checkCLI(); return session_id();
} }
/** /**
* Set session name call. If not valid session name, will return false * automatically closes a session if the auto write close flag is set
* *
* @param string $session_name A valid string for session name * @return bool
* @return bool True if session name is valid,
* False if not
*/ */
public function setSessionName(string $session_name): bool private function closeSessionCall(): bool
{ {
if (!$this->checkValidSessionName($session_name)) { if ($this->auto_write_close) {
return false; return $this->writeClose();
} }
session_name($session_name); return false;
return true;
} }
/** /**
@@ -93,16 +97,18 @@ class Session
return true; return true;
} }
// MARK: init session (on class start)
/** /**
* start session with given session name if set * stinitart session with given session name if set
* aborts on command line or if sessions are not enabled * aborts on command line or if sessions are not enabled
* also aborts if session cannot be started * also aborts if session cannot be started
* On sucess returns the session id * On sucess returns the session id
* *
* @param string|null $session_name * @param string $session_name
* @return string|bool * @return void
*/ */
public function startSession(?string $session_name = null): string|bool private function initSession(string $session_name): void
{ {
// we can't start sessions on command line // we can't start sessions on command line
if ($this->checkCliStatus()) { if ($this->checkCliStatus()) {
@@ -115,39 +121,82 @@ class Session
// session_status // session_status
// initial the session if there is no session running already // initial the session if there is no session running already
if (!$this->checkActiveSession()) { if (!$this->checkActiveSession()) {
// if session name is emtpy, check if there is a global set
// this is a deprecated fallback
$session_name = $session_name ?? $GLOBALS['SET_SESSION_NAME'] ?? '';
// DEPRECTED: constant SET_SESSION_NAME is no longer used
// if set, set special session name
if (!empty($session_name)) {
// invalid session name, abort // invalid session name, abort
if (!$this->checkValidSessionName($session_name)) { if (!$this->checkValidSessionName($session_name)) {
throw new \UnexpectedValueException('[SESSION] Invalid session name: ' . $session_name, 3); throw new \UnexpectedValueException('[SESSION] Invalid session name: ' . $this->session_name, 3);
}
$this->setSessionName($session_name);
} }
// set session name
$this->session_name = $session_name;
session_name($this->session_name);
// start session // start session
$this->startSessionCall(); $this->startSessionCall();
} // if we faild to start the session
// if we still have no active session
if (!$this->checkActiveSession()) { if (!$this->checkActiveSession()) {
throw new \RuntimeException('[SESSION] Failed to activate session', 4); throw new \RuntimeException('[SESSION] Failed to activate session', 5);
} }
if (false === ($session_id = $this->getSessionId())) { } elseif ($session_name != $this->getSessionName()) {
throw new \UnexpectedValueException('[SESSION] getSessionId did not return a session id', 5); throw new \UnexpectedValueException(
'[SESSION] Another session exists with a different name: ' . $this->getSessionName(),
4
);
} }
return $session_id; // check session id
if (false === ($session_id = $this->getSessionIdCall())) {
throw new \UnexpectedValueException('[SESSION] getSessionId did not return a session id', 6);
}
// set session id
$this->session_id = $session_id;
// if flagged auto close, write close session
if ($this->auto_write_close) {
$this->writeClose();
}
}
// MARK: public set/get status
/**
* start session, will only run after initSession
*
* @return bool True if started, False if alrady running
*/
public function restartSession(): bool
{
if (!$this->checkActiveSession()) {
$this->startSessionCall();
return true;
}
return false;
} }
/** /**
* get current set session id or false if none started * current set session id
* *
* @return string|bool * @return string
*/ */
public function getSessionId(): string|bool public function getSessionId(): string
{ {
return session_id(); return $this->session_id;
}
/**
* set the auto write close flag
*
* @param bool $flag
* @return void
*/
public function setAutoWriteClose(bool $flag): void
{
$this->auto_write_close = $flag;
}
/**
* return the auto write close flag
*
* @return bool
*/
public function checkAutoWriteClose(): bool
{
return $this->auto_write_close;
} }
/** /**
@@ -175,6 +224,19 @@ class Session
} }
} }
/**
* check if we are in CLI, we set this, so we can mock this
* Not this is just a wrapper for the static System::checkCLI call
*
* @return bool True if we are in a CLI enviroment, or false for everything else
*/
public function checkCliStatus(): bool
{
return \CoreLibs\Get\System::checkCLI();
}
// MARK: write close session
/** /**
* unlock the session file, so concurrent AJAX requests can be done * unlock the session file, so concurrent AJAX requests can be done
* NOTE: after this has been called, no changes in _SESSION will be stored * NOTE: after this has been called, no changes in _SESSION will be stored
@@ -188,6 +250,8 @@ class Session
return session_write_close(); return session_write_close();
} }
// MARK: session close and clean up
/** /**
* Proper destroy a session * Proper destroy a session
* - unset the _SESSION array * - unset the _SESSION array
@@ -236,18 +300,20 @@ class Session
return session_status(); return session_status();
} }
// _SESSION set/unset methods // MARK: _SESSION set/unset methods
/** /**
* unset all _SESSION entries * unset all _SESSION entries
* *
* @return void * @return void
*/ */
public function unsetAllS(): void public function unsetAll(): void
{ {
$this->restartSession();
foreach (array_keys($_SESSION ?? []) as $name) { foreach (array_keys($_SESSION ?? []) as $name) {
unset($_SESSION[$name]); unset($_SESSION[$name]);
} }
$this->closeSessionCall();
} }
/** /**
@@ -257,9 +323,11 @@ class Session
* @param mixed $value value to set (can be anything) * @param mixed $value value to set (can be anything)
* @return void * @return void
*/ */
public function setS(string|int $name, mixed $value): void public function set(string|int $name, mixed $value): void
{ {
$this->restartSession();
$_SESSION[$name] = $value; $_SESSION[$name] = $value;
$this->closeSessionCall();
} }
/** /**
@@ -268,9 +336,9 @@ class Session
* @param string|int $name value key to get from _SESSION * @param string|int $name value key to get from _SESSION
* @return mixed value stored in _SESSION * @return mixed value stored in _SESSION
*/ */
public function getS(string|int $name): mixed public function get(string|int $name): mixed
{ {
return $_SESSION[$name] ?? ''; return $_SESSION[$name] ?? null;
} }
/** /**
@@ -279,7 +347,7 @@ class Session
* @param string|int $name Name to check for * @param string|int $name Name to check for
* @return bool True for set, False fornot set * @return bool True for set, False fornot set
*/ */
public function issetS(string|int $name): bool public function isset(string|int $name): bool
{ {
return isset($_SESSION[$name]); return isset($_SESSION[$name]);
} }
@@ -290,14 +358,17 @@ class Session
* @param string|int $name _SESSION key name to remove * @param string|int $name _SESSION key name to remove
* @return void * @return void
*/ */
public function unsetS(string|int $name): void public function unset(string|int $name): void
{ {
if (isset($_SESSION[$name])) { if (!isset($_SESSION[$name])) {
unset($_SESSION[$name]); return;
} }
$this->restartSession();
unset($_SESSION[$name]);
$this->closeSessionCall();
} }
// set/get below // MARK: [DEPRECATED] __set/__get magic methods
// ->var = value; // ->var = value;
/** /**
@@ -306,10 +377,13 @@ class Session
* @param string|int $name * @param string|int $name
* @param mixed $value * @param mixed $value
* @return void * @return void
* @deprecated use ->set()
*/ */
public function __set(string|int $name, mixed $value): void public function __set(string|int $name, mixed $value): void
{ {
$this->restartSession();
$_SESSION[$name] = $value; $_SESSION[$name] = $value;
$this->closeSessionCall();
} }
/** /**
@@ -317,6 +391,7 @@ class Session
* *
* @param string|int $name * @param string|int $name
* @return mixed If name is not found, it will return null * @return mixed If name is not found, it will return null
* @deprecated use ->get()
*/ */
public function __get(string|int $name): mixed public function __get(string|int $name): mixed
{ {
@@ -331,6 +406,7 @@ class Session
* *
* @param string|int $name * @param string|int $name
* @return bool * @return bool
* @deprecated use ->isset()
*/ */
public function __isset(string|int $name): bool public function __isset(string|int $name): bool
{ {
@@ -342,12 +418,16 @@ class Session
* *
* @param string|int $name * @param string|int $name
* @return void * @return void
* @deprecated use ->unset()
*/ */
public function __unset(string|int $name): void public function __unset(string|int $name): void
{ {
if (isset($_SESSION[$name])) { if (!isset($_SESSION[$name])) {
unset($_SESSION[$name]); return;
} }
$this->restartSession();
unset($_SESSION[$name]);
$this->closeSessionCall();
} }
} }

View File

@@ -38,7 +38,7 @@ class Uids
$uniqid_length++; $uniqid_length++;
} }
/** @var int<1,max> make sure that internal this is correct */ /** @var int<1,max> make sure that internal this is correct */
$random_bytes_length = ($uniqid_length - ($uniqid_length % 2)) / 2; $random_bytes_length = (int)(($uniqid_length - ($uniqid_length % 2)) / 2);
$uniqid = bin2hex(random_bytes($random_bytes_length)); $uniqid = bin2hex(random_bytes($random_bytes_length));
// if not forced shorten return next lower length // if not forced shorten return next lower length
if (!$force_length) { if (!$force_length) {
@@ -56,26 +56,6 @@ class Uids
*/ */
public static function uuidv4(): string public static function uuidv4(): string
{ {
/* return sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
// 32 bits for "time_low"
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
// 16 bits for "time_mid"
mt_rand(0, 0xffff),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
mt_rand(0, 0x0fff) | 0x4000,
// 16 bits, 8 bits for "clk_seq_hi_res",
// 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
mt_rand(0, 0x3fff) | 0x8000,
// 48 bits for "node"
mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0xffff)
); */
$data = random_bytes(16); $data = random_bytes(16);
assert(strlen($data) == 16); assert(strlen($data) == 16);
@@ -93,6 +73,20 @@ class Uids
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
} }
/**
* regex validate uuid v4
*
* @param string $uuidv4
* @return bool
*/
public static function validateUuuidv4(string $uuidv4): bool
{
if (!preg_match("/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/", $uuidv4)) {
return false;
}
return true;
}
/** /**
* creates a uniq id based on lengths * creates a uniq id based on lengths
* *

View File

@@ -374,7 +374,7 @@ class ArrayIO extends \CoreLibs\DB\IO
public function dbDelete(array $table_array = [], bool $acl_limit = false): array public function dbDelete(array $table_array = [], bool $acl_limit = false): array
{ {
// is array and has values, override set and set new // is array and has values, override set and set new
if (is_array($table_array) && count($table_array)) { if (count($table_array)) {
$this->table_array = $table_array; $this->table_array = $table_array;
} }
if (!$this->dbCheckPkSet()) { if (!$this->dbCheckPkSet()) {
@@ -440,7 +440,7 @@ class ArrayIO extends \CoreLibs\DB\IO
public function dbRead(bool $edit = false, array $table_array = []): array public function dbRead(bool $edit = false, array $table_array = []): array
{ {
// if array give, overrules internal array // if array give, overrules internal array
if (is_array($table_array) && count($table_array)) { if (count($table_array)) {
$this->table_array = $table_array; $this->table_array = $table_array;
} }
if (!$this->dbCheckPkSet()) { if (!$this->dbCheckPkSet()) {

View File

@@ -284,7 +284,8 @@ class IO
public const ERROR_HASH_TYPE = 'adler32'; public const ERROR_HASH_TYPE = 'adler32';
/** @var string regex to get returning with matches at position 1 */ /** @var string regex to get returning with matches at position 1 */
public const REGEX_RETURNING = '/\s+returning\s+(.+\s*(?:.+\s*)+);?$/i'; public const REGEX_RETURNING = '/\s+returning\s+(.+\s*(?:.+\s*)+);?$/i';
/** @var array<string> allowed convert target for placeholder: pg or pdo (currently not available) */ /** @var array<string> allowed convert target for placeholder:
* pg or pdo (currently not available) */
public const DB_CONVERT_PLACEHOLDER_TARGET = ['pg']; public const DB_CONVERT_PLACEHOLDER_TARGET = ['pg'];
// REGEX_SELECT // REGEX_SELECT
// REGEX_UPDATE // REGEX_UPDATE
@@ -914,7 +915,7 @@ class IO
if ($cursor !== false) { if ($cursor !== false) {
[$db_prefix, $db_error_string] = $this->db_functions->__dbPrintError($cursor); [$db_prefix, $db_error_string] = $this->db_functions->__dbPrintError($cursor);
} }
if ($cursor === false && method_exists($this->db_functions, '__dbPrintError')) { if ($cursor === false && method_exists($this->db_functions, '__dbPrintError')) { /** @phpstan-ignore-line */
[$db_prefix, $db_error_string] = $this->db_functions->__dbPrintError(); [$db_prefix, $db_error_string] = $this->db_functions->__dbPrintError();
} }
// prefix the master if not the same // prefix the master if not the same
@@ -1311,33 +1312,14 @@ class IO
} }
/** /**
* count $ leading parameters only * count placeholder entries in the query
* *
* @param string $query Query to check * @param string $query Query to check
* @return int Number of parameters found * @return int Number of parameters found
*/ */
private function __dbCountQueryParams(string $query): int private function __dbCountQueryParams(string $query): int
{ {
$match = []; return $this->db_functions->__dbCountQueryParams($query);
// regex for params: only stand alone $number allowed
// exclude all '' enclosed strings, ignore all numbers [note must start with digit]
// can have space/tab/new line
// must have <> = , ( [not equal, equal, comma, opening round bracket]
// can have space/tab/new line
// $ number with 1-9 for first and 0-9 for further digits
// /s for matching new line in . list
// [disabled, we don't used ^ or $] /m for multi line match
// Matches in 1:, must be array_filtered to remove empty, count with array_unique
$query_split = '[(=,?-]|->|->>|#>|#>>|@>|<@|\?\|\?\&|\|\||#-';
preg_match_all(
'/'
. '(?:\'.*?\')?\s*(?:\?\?|<>|' . $query_split . ')\s*'
. '(?:\d+|(?:\'.*?\')|(\$[1-9]{1}(?:[0-9]{1,})?))'
. '/s',
$query,
$match
);
return count(array_unique(array_filter($match[1])));
} }
/** /**
@@ -1737,7 +1719,7 @@ class IO
{ {
if ( if (
!empty($this->dbh) && !empty($this->dbh) &&
$this->dbh instanceof \PgSql\Connection $this->dbh instanceof \PgSql\Connection /** @phpstan-ignore-line future could be other */
) { ) {
// reset any client encodings set // reset any client encodings set
$this->dbResetEncoding(); $this->dbResetEncoding();
@@ -3160,7 +3142,8 @@ class IO
'count' => 0, 'count' => 0,
'query' => '', 'query' => '',
'result' => null, 'result' => null,
'returning_id' => false 'returning_id' => false,
'placeholder_converted' => [],
]; ];
// if this is an insert query, check if we can add a return // if this is an insert query, check if we can add a return
if ($this->dbCheckQueryForInsert($query, true)) { if ($this->dbCheckQueryForInsert($query, true)) {
@@ -3200,6 +3183,39 @@ class IO
$this->prepare_cursor[$stm_name]['pk_name'] = $pk_name; $this->prepare_cursor[$stm_name]['pk_name'] = $pk_name;
} }
} }
// QUERY PARAMS: run query params check and rewrite
if ($this->dbGetConvertPlaceholder() === true) {
try {
$this->placeholder_converted = ConvertPlaceholder::convertPlaceholderInQuery(
$query,
null,
$this->dbGetConvertPlaceholderTarget()
);
// write the new queries over the old
if (!empty($this->placeholder_converted['query'])) {
$query = $this->placeholder_converted['query'];
}
$this->prepare_cursor[$stm_name]['placeholder_converted'] = $this->placeholder_converted;
} catch (\OutOfRangeException $e) {
$this->__dbError($e->getCode(), context:[
'statement_name' => $stm_name,
'query' => $query,
'location' => 'dbPrepare',
'error' => 'OutOfRangeException',
'exception' => $e
]);
return false;
} catch (\RuntimeException $e) {
$this->__dbError($e->getCode(), context:[
'statement_name' => $stm_name,
'query' => $query,
'location' => 'dbPrepare',
'error' => 'RuntimeException',
'exception' => $e
]);
return false;
}
}
// check prepared curser parameter count // check prepared curser parameter count
$this->prepare_cursor[$stm_name]['count'] = $this->__dbCountQueryParams($query); $this->prepare_cursor[$stm_name]['count'] = $this->__dbCountQueryParams($query);
$this->prepare_cursor[$stm_name]['query'] = $query; $this->prepare_cursor[$stm_name]['query'] = $query;
@@ -3735,7 +3751,7 @@ class IO
} }
/** /**
* convert db values (set) * convert db values (set) to php matching types
* *
* @param Convert $convert * @param Convert $convert
* @return void * @return void
@@ -3746,7 +3762,7 @@ class IO
} }
/** /**
* unsert convert db values flag * unsert convert db values flag for converting db to php matching types
* *
* @param Convert $convert * @param Convert $convert
* @return void * @return void
@@ -3757,7 +3773,7 @@ class IO
} }
/** /**
* Reset to origincal config file set * Reset to original config file set for converting db to php matching type
* *
* @return void * @return void
*/ */
@@ -3769,7 +3785,7 @@ class IO
} }
/** /**
* check if a conert flag is set * check if a convert flag is set for converting db to php matching type
* *
* @param Convert $convert * @param Convert $convert
* @return bool * @return bool
@@ -3783,7 +3799,7 @@ class IO
} }
/** /**
* Set if we want to auto convert PDO/\Pg placeholders * Set if we want to auto convert to PDO/\Pg placeholders
* *
* @param bool $flag * @param bool $flag
* @return void * @return void
@@ -4294,7 +4310,7 @@ class IO
* @param string $stm_name The name of the stored statement * @param string $stm_name The name of the stored statement
* @param string $key Key field name in prepared cursor array * @param string $key Key field name in prepared cursor array
* Allowed are: pk_name, count, query, returning_id * Allowed are: pk_name, count, query, returning_id
* @return null|string|int|bool Entry from each of the valid keys * @return null|string|int|bool|array<string,mixed> Entry from each of the valid keys
* Will return false on error * Will return false on error
* Not ethat returnin_id also can return false * Not ethat returnin_id also can return false
* but will not set an error entry * but will not set an error entry
@@ -4302,7 +4318,7 @@ class IO
public function dbGetPrepareCursorValue( public function dbGetPrepareCursorValue(
string $stm_name, string $stm_name,
string $key string $key
): null|string|int|bool { ): null|string|int|bool|array {
// if no statement name // if no statement name
if (empty($stm_name)) { if (empty($stm_name)) {
$this->__dbError( $this->__dbError(
@@ -4313,7 +4329,7 @@ class IO
return false; return false;
} }
// if not a valid key // if not a valid key
if (!in_array($key, ['pk_name', 'count', 'query', 'returning_id'])) { if (!in_array($key, ['pk_name', 'count', 'query', 'returning_id', 'placeholder_converted'])) {
$this->__dbError( $this->__dbError(
102, 102,
false, false,

View File

@@ -285,6 +285,22 @@ interface SqlFunctions
*/ */
public function __dbConnectionBusySocketWait(int $timeout_seconds = 3): bool; public function __dbConnectionBusySocketWait(int $timeout_seconds = 3): bool;
/**
* Undocumented function
*
* @param string $parameter
* @param bool $strip
* @return string
*/
public function __dbVersionInfo(string $parameter, bool $strip = true): string;
/**
* Undocumented function
*
* @return array<mixed>
*/
public function __dbVersionInfoParameterList(): array;
/** /**
* Undocumented function * Undocumented function
* *
@@ -292,6 +308,13 @@ interface SqlFunctions
*/ */
public function __dbVersion(): string; public function __dbVersion(): string;
/**
* Undocumented function
*
* @return int
*/
public function __dbVersionNumeric(): int;
/** /**
* Undocumented function * Undocumented function
* *
@@ -306,6 +329,14 @@ interface SqlFunctions
?int &$end = null ?int &$end = null
): ?array; ): ?array;
/**
* Undocumented function
*
* @param string $parameter
* @return string|bool
*/
public function __dbParameter(string $parameter): string|bool;
/** /**
* Undocumented function * Undocumented function
* *
@@ -343,6 +374,14 @@ interface SqlFunctions
* @return string * @return string
*/ */
public function __dbGetEncoding(): string; public function __dbGetEncoding(): string;
/**
* Undocumented function
*
* @param string $query
* @return int
*/
public function __dbCountQueryParams(string $query): int;
} }
// __END__ // __END__

View File

@@ -51,6 +51,8 @@ declare(strict_types=1);
namespace CoreLibs\DB\SQL; namespace CoreLibs\DB\SQL;
use CoreLibs\DB\Support\ConvertPlaceholder;
// below no ignore is needed if we want to use PgSql interface checks with PHP 8.0 // below no ignore is needed if we want to use PgSql interface checks with PHP 8.0
// as main system. Currently all @var sets are written as object // as main system. Currently all @var sets are written as object
/** @#phan-file-suppress PhanUndeclaredTypeProperty,PhanUndeclaredTypeParameter,PhanUndeclaredTypeReturnType */ /** @#phan-file-suppress PhanUndeclaredTypeProperty,PhanUndeclaredTypeParameter,PhanUndeclaredTypeReturnType */
@@ -102,7 +104,7 @@ class PgSQL implements Interface\SqlFunctions
* SELECT foo FROM bar WHERE foobar = $1 * SELECT foo FROM bar WHERE foobar = $1
* *
* @param string $query Query string with placeholders $1, .. * @param string $query Query string with placeholders $1, ..
* @param array<mixed> $params Matching parameters for each placerhold * @param array<mixed> $params Matching parameters for each placeholder
* @return \PgSql\Result|false Query result * @return \PgSql\Result|false Query result
*/ */
public function __dbQueryParams(string $query, array $params): \PgSql\Result|false public function __dbQueryParams(string $query, array $params): \PgSql\Result|false
@@ -140,7 +142,7 @@ class PgSQL implements Interface\SqlFunctions
* sends an async query to the server with params * sends an async query to the server with params
* *
* @param string $query Query string with placeholders $1, .. * @param string $query Query string with placeholders $1, ..
* @param array<mixed> $params Matching parameters for each placerhold * @param array<mixed> $params Matching parameters for each placeholder
* @return bool true/false Query sent successful status * @return bool true/false Query sent successful status
*/ */
public function __dbSendQueryParams(string $query, array $params): bool public function __dbSendQueryParams(string $query, array $params): bool
@@ -405,17 +407,13 @@ class PgSQL implements Interface\SqlFunctions
} }
// no PK name given at all // no PK name given at all
if (empty($pk_name)) { if (empty($pk_name)) {
// if name is plurar, make it singular
// if (preg_match("/.*s$/i", $table))
// $table = substr($table, 0, -1);
// set pk_name to "id" // set pk_name to "id"
$pk_name = $table . "_id"; $pk_name = $table . "_id";
} }
$seq = ($schema ? $schema . '.' : '') . $table . "_" . $pk_name . "_seq"; $q = "SELECT CURRVAL(pg_get_serial_sequence($1, $2)) AS insert_id";
$q = "SELECT CURRVAL('$seq') AS insert_id";
// I have to do manually or I overwrite the original insert internal vars ... // I have to do manually or I overwrite the original insert internal vars ...
if ($q = $this->__dbQuery($q)) { if ($cursor = $this->__dbQueryParams($q, [$table, $pk_name])) {
if (is_array($res = $this->__dbFetchArray($q))) { if (is_array($res = $this->__dbFetchArray($cursor))) {
list($id) = $res; list($id) = $res;
} else { } else {
return false; return false;
@@ -449,26 +447,36 @@ class PgSQL implements Interface\SqlFunctions
$table_prefix = $schema . '.'; $table_prefix = $schema . '.';
} }
} }
$params = [$table_prefix . $table];
$replace = ['', ''];
// read from table the PK name // read from table the PK name
// faster primary key get // faster primary key get
$q = "SELECT pg_attribute.attname AS column_name, " $q = <<<SQL
. "format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS type " SELECT
. "FROM pg_index, pg_class, pg_attribute "; pg_attribute.attname AS column_name,
format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS type
FROM pg_index, pg_class, pg_attribute{PG_NAMESPACE}
WHERE
-- regclass translates the OID to the name
pg_class.oid = $1::regclass AND
indrelid = pg_class.oid AND
pg_attribute.attrelid = pg_class.oid AND
pg_attribute.attnum = any(pg_index.indkey) AND
indisprimary
{NSPNAME}
SQL;
if ($schema) { if ($schema) {
$q .= ", pg_namespace "; $params[] = $schema;
$replace = [
", pg_namespace",
"AND pg_class.relnamespace = pg_namespace.oid AND nspname = $2"
];
} }
$q .= "WHERE " $cursor = $this->__dbQueryParams(str_replace(
// regclass translates the OID to the name ['{PG_NAMESPACE}', '{NSPNAME}'],
. "pg_class.oid = '" . $table_prefix . $table . "'::regclass AND " $replace,
. "indrelid = pg_class.oid AND "; $q
if ($schema) { ), $params);
$q .= "nspname = '" . $schema . "' AND "
. "pg_class.relnamespace = pg_namespace.oid AND ";
}
$q .= "pg_attribute.attrelid = pg_class.oid AND "
. "pg_attribute.attnum = any(pg_index.indkey) "
. "AND indisprimary";
$cursor = $this->__dbQuery($q);
if ($cursor !== false) { if ($cursor !== false) {
$__db_fetch_array = $this->__dbFetchArray($cursor); $__db_fetch_array = $this->__dbFetchArray($cursor);
if (!is_array($__db_fetch_array)) { if (!is_array($__db_fetch_array)) {
@@ -893,11 +901,13 @@ class PgSQL implements Interface\SqlFunctions
public function __dbSetSchema(string $db_schema): int public function __dbSetSchema(string $db_schema): int
{ {
// check if schema actually exists // check if schema actually exists
$query = "SELECT EXISTS(" $query = <<<SQL
. "SELECT 1 FROM information_schema.schemata " SELECT EXISTS (
. "WHERE schema_name = " . $this->__dbEscapeLiteral($db_schema) SELECT 1 FROM information_schema.schemata
. ")"; WHERE schema_name = $1
$cursor = $this->__dbQuery($query); )
SQL;
$cursor = $this->__dbQueryParams($query, [$db_schema]);
// abort if execution fails // abort if execution fails
if ($cursor === false) { if ($cursor === false) {
return 1; return 1;
@@ -966,6 +976,34 @@ class PgSQL implements Interface\SqlFunctions
{ {
return $this->__dbShow('client_encoding'); return $this->__dbShow('client_encoding');
} }
/**
* Count placeholder queries. $ only
*
* @param string $query
* @return int
*/
public function __dbCountQueryParams(string $query): int
{
$matches = [];
// regex for params: only stand alone $number allowed
// exclude all '' enclosed strings, ignore all numbers [note must start with digit]
// can have space/tab/new line
// must have <> = , ( [not equal, equal, comma, opening round bracket]
// can have space/tab/new line
// $ number with 1-9 for first and 0-9 for further digits
// Collects also PDO ? and :named, but they are ignored
// /s for matching new line in . list
// [disabled, we don't used ^ or $] /m for multi line match
// Matches in 1:, must be array_filtered to remove empty, count with array_unique
// Regex located in the ConvertPlaceholder class
preg_match_all(
ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS,
$query,
$matches
);
return count(array_unique(array_filter($matches[3])));
}
} }
// __END__ // __END__

View File

@@ -14,6 +14,67 @@ namespace CoreLibs\DB\Support;
class ConvertPlaceholder class ConvertPlaceholder
{ {
/** @var string split regex */
private const PATTERN_QUERY_SPLIT = '[(<>=,?-]|->|->>|#>|#>>|@>|<@|\?\|\?\&|\|\||#-';
/** @var string the main regex including the pattern query split */
private const PATTERN_ELEMENT = '(?:\'.*?\')?\s*(?:\?\?|' . self::PATTERN_QUERY_SPLIT . ')\s*';
/** @var string parts to ignore in the SQL */
private const PATTERN_IGNORE =
// digit -> ignore
'\d+|'
// other string -> ignore
. '(?:\'.*?\')|';
/** @var string named parameters */
private const PATTERN_NAMED = '(:\w+)';
/** @var string question mark parameters */
private const PATTERN_QUESTION_MARK = '(?:(?:\?\?)?\s*(\?{1}))';
/** @var string numbered parameters */
private const PATTERN_NUMBERED = '(\$[1-9]{1}(?:[0-9]{1,})?)';
// below here are full regex that will be used
/** @var string replace regex for named (:...) entries */
public const REGEX_REPLACE_NAMED = '/'
. '(' . self::PATTERN_ELEMENT . ')'
. '('
. self::PATTERN_IGNORE
. self::PATTERN_NAMED
. ')'
. '/s';
/** @var string replace regex for question mark (?) entries */
public const REGEX_REPLACE_QUESTION_MARK = '/'
. '(' . self::PATTERN_ELEMENT . ')'
. '('
. self::PATTERN_IGNORE
. self::PATTERN_QUESTION_MARK
. ')'
. '/s';
/** @var string replace regex for numbered ($n) entries */
public const REGEX_REPLACE_NUMBERED = '/'
. '(' . self::PATTERN_ELEMENT . ')'
. '('
. self::PATTERN_IGNORE
. self::PATTERN_NUMBERED
. ')'
. '/s';
/** @var string the main lookup query for all placeholders */
public const REGEX_LOOKUP_PLACEHOLDERS = '/'
// prefix string part, must match towards
// seperator for ( = , ? - [and json/jsonb in pg doc section 9.15]
. self::PATTERN_ELEMENT
// match for replace part
. '(?:'
// ignore parts
. self::PATTERN_IGNORE
// :name named part (PDO) [1]
. self::PATTERN_NAMED . '|'
// ? question mark part (PDO) [2]
. self::PATTERN_QUESTION_MARK . '|'
// $n numbered part (\PG php) [3]
. self::PATTERN_NUMBERED
// end match
. ')'
// single line -> add line break to matches in "."
. '/s';
/** /**
* Convert PDO type query with placeholders to \PG style and vica versa * Convert PDO type query with placeholders to \PG style and vica versa
* For PDO to: ? and :named * For PDO to: ? and :named
@@ -27,44 +88,24 @@ class ConvertPlaceholder
* found has -1 if an error occoured in the preg_match_all call * found has -1 if an error occoured in the preg_match_all call
* *
* @param string $query Query with placeholders to convert * @param string $query Query with placeholders to convert
* @param array<mixed> $params The parameters that are used for the query, and will be updated * @param ?array<mixed> $params The parameters that are used for the query, and will be updated
* @param string $convert_to Either pdo or pg, will be converted to lower case for check * @param string $convert_to Either pdo or pg, will be converted to lower case for check
* @return 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>} * @return array{original:array{query:string,params:array<mixed>,empty_params:bool},type:''|'named'|'numbered'|'question_mark',found:int,matches:array<string>,params_lookup:array<mixed>,query:string,params:array<mixed>}
* @throws \OutOfRangeException 200 * @throws \OutOfRangeException 200 If mixed placeholder types
* @throws \InvalidArgumentException 300 or 301 if wrong convert to with found placeholders
*/ */
public static function convertPlaceholderInQuery( public static function convertPlaceholderInQuery(
string $query, string $query,
array $params, ?array $params,
string $convert_to = 'pg' string $convert_to = 'pg'
): array { ): array {
$convert_to = strtolower($convert_to); $convert_to = strtolower($convert_to);
$matches = []; $matches = [];
$query_split = '[(=,?-]|->|->>|#>|#>>|@>|<@|\?\|\?\&|\|\||#-';
$pattern = '/'
// prefix string part, must match towards
// seperator for ( = , ? - [and json/jsonb in pg doc section 9.15]
. '(?:\'.*?\')?\s*(?:\?\?|' . $query_split . ')\s*'
// match for replace part
. '(?:'
// digit -> ignore
. '\d+|'
// other string -> ignore
. '(?:\'.*?\')|'
// :name named part (PDO)
. '(:\w+)|'
// ? question mark part (PDO)
. '(?:(?:\?\?)?\s*(\?{1}))|'
// $n numbered part (\PG php)
. '(\$[1-9]{1}(?:[0-9]{1,})?)'
// end match
. ')'
// single line -> add line break to matches in "."
. '/s';
// matches: // matches:
// 1: :named // 1: :named
// 2: ? question mark // 2: ? question mark
// 3: $n numbered // 3: $n numbered
$found = preg_match_all($pattern, $query, $matches, PREG_UNMATCHED_AS_NULL); $found = preg_match_all(self::REGEX_LOOKUP_PLACEHOLDERS, $query, $matches, PREG_UNMATCHED_AS_NULL);
// if false or null set to -1 // if false or null set to -1
// || $found === null // || $found === null
if ($found === false) { if ($found === false) {
@@ -77,10 +118,10 @@ class ConvertPlaceholder
/** @var array<string> 3: $n matches */ /** @var array<string> 3: $n matches */
$numbered_matches = array_filter($matches[3]); $numbered_matches = array_filter($matches[3]);
// count matches // count matches
$count_named = count($named_matches); $count_named = count(array_unique($named_matches));
$count_qmark = count($qmark_matches); $count_qmark = count($qmark_matches);
$count_numbered = count($numbered_matches); $count_numbered = count(array_unique($numbered_matches));
// throw if mixed // throw exception if mixed found
if ( if (
($count_named && $count_qmark) || ($count_named && $count_qmark) ||
($count_named && $count_numbered) || ($count_named && $count_numbered) ||
@@ -88,38 +129,114 @@ class ConvertPlaceholder
) { ) {
throw new \OutOfRangeException('Cannot have named, question mark and numbered in the same query', 200); throw new \OutOfRangeException('Cannot have named, question mark and numbered in the same query', 200);
} }
// rebuild // // throw if invalid conversion
$matches_return = []; // if (($count_named || $count_qmark) && $convert_to != 'pg') {
$type = ''; // throw new \InvalidArgumentException('Cannot convert from named or question mark placeholders to PDO', 300);
// }
// if ($count_numbered && $convert_to != 'pdo') {
// throw new \InvalidArgumentException('Cannot convert from numbered placeholders to Pg', 301);
// }
// return array
$return_placeholders = [
// original
'original' => [
'query' => $query,
'params' => $params ?? [],
'empty_params' => $params === null ? true : false,
],
// type found, empty if nothing was done
'type' => '',
// int: found, not found; -1: problem (set from false)
'found' => (int)$found,
'matches' => [],
// old to new lookup check
'params_lookup' => [],
// this must match the count in params in new
'needed' => 0,
// new
'query' => '',
'params' => [],
];
// replace basic regex and name settings
if ($count_named) {
$return_placeholders['type'] = 'named';
$return_placeholders['matches'] = $named_matches;
$return_placeholders['needed'] = $count_named;
} elseif ($count_qmark) {
$return_placeholders['type'] = 'question_mark';
$return_placeholders['matches'] = $qmark_matches;
$return_placeholders['needed'] = $count_qmark;
// for each ?:DTN: -> replace with $1 ... $n, any remaining :DTN: remove
} elseif ($count_numbered) {
$return_placeholders['type'] = 'numbered';
$return_placeholders['matches'] = $numbered_matches;
$return_placeholders['needed'] = $count_numbered;
}
// run convert only if matching type and direction
if (
(($count_named || $count_qmark) && $convert_to == 'pg') ||
($count_numbered && $convert_to == 'pdo')
) {
$param_list = self::updateParamList($return_placeholders);
$return_placeholders['params_lookup'] = $param_list['params_lookup'];
$return_placeholders['query'] = $param_list['query'];
$return_placeholders['params'] = $param_list['params'];
}
// return data
return $return_placeholders;
}
/**
* Updates the params list from one style to the other to match the query output
* if original.empty_params is set to true, no params replacement is done
* if param replacement has been done in a dbPrepare then this has to be run
* with the return palceholders array with params in original filled and empty_params turned off
*
* phpcs:disable Generic.Files.LineLength
* @param array{original:array{query:string,params:array<mixed>,empty_params:bool},type:''|'named'|'numbered'|'question_mark',found:int,matches?:array<string>,params_lookup?:array<mixed>,query?:string,params?:array<mixed>} $converted_placeholders
* phpcs:enable Generic.Files.LineLength
* @return array{params_lookup:array<mixed>,query:string,params:array<mixed>}
*/
public static function updateParamList(array $converted_placeholders): array
{
// skip if nothing set
if (!$converted_placeholders['found']) {
return [
'params_lookup' => [],
'query' => '',
'params' => []
];
}
$query_new = ''; $query_new = '';
$params_new = []; $params_new = [];
$params_lookup = []; $params_lookup = [];
if ($count_named && $convert_to == 'pg') { // set to null if params is empty
$type = 'named'; $params = $converted_placeholders['original']['params'];
$matches_return = $named_matches; $empty_params = $converted_placeholders['original']['empty_params'];
// only check for :named switch ($converted_placeholders['type']) {
$pattern_replace = '/' case 'named':
. '((?:\'.*?\')?\s*(?:\?\?|' . $query_split . ')\s*)' // 0: full
. '(\d+|(?:\'.*?\')|(:\w+))'
. '/s';
// 0: full // 0: full
// 1: pre part // 1: pre part
// 2: keep part UNLESS '3' is set // 2: keep part UNLESS '3' is set
// 3: replace part :named // 3: replace part :named
$pos = 0; $pos = 0;
$query_new = preg_replace_callback( $query_new = preg_replace_callback(
$pattern_replace, self::REGEX_REPLACE_NAMED,
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params) { function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) {
// only count up if $match[3] is not yet in lookup table // only count up if $match[3] is not yet in lookup table
if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) { if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) {
$pos++; $pos++;
$params_lookup[$matches[3]] = '$' . $pos; $params_lookup[$matches[3]] = '$' . $pos;
// skip params setup if param list is empty
if (!$empty_params) {
$params_new[] = $params[$matches[3]] ?? $params_new[] = $params[$matches[3]] ??
throw new \RuntimeException( throw new \RuntimeException(
'Cannot lookup ' . $matches[3] . ' in params list', 'Cannot lookup ' . $matches[3] . ' 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 $matches[1] . (
empty($matches[3]) ? empty($matches[3]) ?
@@ -131,25 +248,21 @@ class ConvertPlaceholder
) )
); );
}, },
$query $converted_placeholders['original']['query']
); );
} elseif ($count_qmark && $convert_to == 'pg') { break;
$type = 'question_mark'; case 'question_mark':
$matches_return = $qmark_matches; if (!$empty_params) {
// order and data stays the same // order and data stays the same
$params_new = $params; $params_new = $params ?? [];
// only check for ? }
$pattern_replace = '/'
. '((?:\'.*?\')?\s*(?:\?\?|' . $query_split . ')\s*)'
. '(\d+|(?:\'.*?\')|(?:(?:\?\?)?\s*(\?{1})))'
. '/s';
// 0: full // 0: full
// 1: pre part // 1: pre part
// 2: keep part UNLESS '3' is set // 2: keep part UNLESS '3' is set
// 3: replace part ? // 3: replace part ?
$pos = 0; $pos = 0;
$query_new = preg_replace_callback( $query_new = preg_replace_callback(
$pattern_replace, self::REGEX_REPLACE_QUESTION_MARK,
function ($matches) use (&$pos, &$params_lookup) { function ($matches) use (&$pos, &$params_lookup) {
// 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($matches[3])) {
@@ -163,36 +276,31 @@ class ConvertPlaceholder
'$' . $pos '$' . $pos
); );
}, },
$query $converted_placeholders['original']['query']
); );
// for each ?:DTN: -> replace with $1 ... $n, any remaining :DTN: remove break;
} elseif ($count_numbered && $convert_to == 'pdo') { case 'numbered':
// convert numbered to named
$type = 'numbered';
$matches_return = $numbered_matches;
// only check for $n
$pattern_replace = '/'
. '((?:\'.*?\')?\s*(?:\?\?|' . $query_split . ')\s*)'
. '(\d+|(?:\'.*?\')|(\$[1-9]{1}(?:[0-9]{1,})?))'
. '/s';
// 0: full // 0: full
// 1: pre part // 1: pre part
// 2: keep part UNLESS '3' is set // 2: keep part UNLESS '3' is set
// 3: replace part $numbered // 3: replace part $numbered
$pos = 0; $pos = 0;
$query_new = preg_replace_callback( $query_new = preg_replace_callback(
$pattern_replace, self::REGEX_REPLACE_NUMBERED,
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params) { function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) {
// only count up if $match[3] is not yet in lookup table // only count up if $match[3] is not yet in lookup table
if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) { if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) {
$pos++; $pos++;
$params_lookup[$matches[3]] = ':' . $pos . '_named'; $params_lookup[$matches[3]] = ':' . $pos . '_named';
// skip params setup if param list is empty
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 220
); );
} }
}
// 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 $matches[1] . (
empty($matches[3]) ? empty($matches[3]) ?
@@ -204,24 +312,12 @@ class ConvertPlaceholder
) )
); );
}, },
$query $converted_placeholders['original']['query']
); );
break;
} }
// return, old query is always set
return [ return [
// original
'original' => [
'query' => $query,
'params' => $params,
],
// type found, empty if nothing was done
'type' => $type,
// int: found, not found; -1: problem (set from false)
'found' => (int)$found,
'matches' => $matches_return,
// old to new lookup check
'params_lookup' => $params_lookup, 'params_lookup' => $params_lookup,
// new
'query' => $query_new ?? '', 'query' => $query_new ?? '',
'params' => $params_new, 'params' => $params_new,
]; ];

View File

@@ -190,7 +190,6 @@ class GetTextReader
private function loadTables(): void private function loadTables(): void
{ {
if ( if (
is_array($this->cache_translations) &&
is_array($this->table_originals) && is_array($this->table_originals) &&
is_array($this->table_translations) is_array($this->table_translations)
) { ) {
@@ -318,10 +317,7 @@ class GetTextReader
if ($this->enable_cache) { if ($this->enable_cache) {
// Caching enabled, get translated string from cache // Caching enabled, get translated string from cache
if ( if (array_key_exists($string, $this->cache_translations)) {
is_array($this->cache_translations) &&
array_key_exists($string, $this->cache_translations)
) {
return $this->cache_translations[$string]; return $this->cache_translations[$string];
} else { } else {
return $string; return $string;
@@ -481,7 +477,7 @@ class GetTextReader
$key = $single . chr(0) . $plural; $key = $single . chr(0) . $plural;
if ($this->enable_cache) { if ($this->enable_cache) {
if (is_array($this->cache_translations) && !array_key_exists($key, $this->cache_translations)) { if (!array_key_exists($key, $this->cache_translations)) {
return ($number != 1) ? $plural : $single; return ($number != 1) ? $plural : $single;
} else { } else {
$result = $this->cache_translations[$key]; $result = $this->cache_translations[$key];

View File

@@ -474,7 +474,7 @@ class Generate
$page_name_camel_case $page_name_camel_case
); );
try { try {
/** @var TableArrays\Interface\TableArraysInterface|false $class */ /** @var TableArrays\Interface\TableArraysInterface $class */
$class = new $class_string($this); $class = new $class_string($this);
} catch (\Throwable $t) { } catch (\Throwable $t) {
$this->log->critical('CLASS LOADING: Failed loading: ' . $class_string . ' => ' . $t->getMessage()); $this->log->critical('CLASS LOADING: Failed loading: ' . $class_string . ' => ' . $t->getMessage());
@@ -1757,15 +1757,10 @@ class Generate
$this->dba->setTableArrayEntry($this->dba->getTableArray()[$key]['preset'], $key, 'value'); $this->dba->setTableArrayEntry($this->dba->getTableArray()[$key]['preset'], $key, 'value');
} }
} }
if (is_array($this->reference_array)) {
if (!is_array($this->reference_array)) {
$this->reference_array = [];
}
reset($this->reference_array); reset($this->reference_array);
foreach ($this->reference_array as $key => $value) { foreach ($this->reference_array as $key => $value) {
unset($this->reference_array[$key]['selected']); unset($this->reference_array[$key]['selected']);
} }
}
$this->warning = 1; $this->warning = 1;
$this->msg = $this->l->__('Cleared for new Dataset!'); $this->msg = $this->l->__('Cleared for new Dataset!');
} }
@@ -1787,11 +1782,7 @@ class Generate
$this->dba->unsetTableArrayEntry($key, 'input_value'); $this->dba->unsetTableArrayEntry($key, 'input_value');
} }
if (is_array($this->reference_array)) {
// load each reference_table // load each reference_table
if (!is_array($this->reference_array)) {
$this->reference_array = [];
}
reset($this->reference_array); reset($this->reference_array);
foreach ($this->reference_array as $key => $value) { foreach ($this->reference_array as $key => $value) {
unset($this->reference_array[$key]['selected']); unset($this->reference_array[$key]['selected']);
@@ -1802,7 +1793,6 @@ class Generate
$this->reference_array[$key]['selected'][] = $res[$this->reference_array[$key]['other_table_pk']]; $this->reference_array[$key]['selected'][] = $res[$this->reference_array[$key]['other_table_pk']];
} }
} }
}
$this->warning = 1; $this->warning = 1;
$this->msg = $this->l->__('Dataset has been loaded!<br>'); $this->msg = $this->l->__('Dataset has been loaded!<br>');
} }
@@ -1979,10 +1969,6 @@ class Generate
// write the object // write the object
$this->dba->dbWrite($addslashes, [], true); $this->dba->dbWrite($addslashes, [], true);
// write reference array (s) if necessary // write reference array (s) if necessary
if (is_array($this->reference_array)) {
if (!is_array($this->reference_array)) {
$this->reference_array = [];
}
reset($this->reference_array); reset($this->reference_array);
foreach ($this->reference_array as $reference_array) { foreach ($this->reference_array as $reference_array) {
$q = 'DELETE FROM ' . $reference_array['table_name'] $q = 'DELETE FROM ' . $reference_array['table_name']
@@ -1996,7 +1982,6 @@ class Generate
$this->dba->dbExec($q . $t_q); $this->dba->dbExec($q . $t_q);
} }
} // foreach reference arrays } // foreach reference arrays
} // if reference arrays
// write element list // write element list
if (!empty($this->element_list)) { if (!empty($this->element_list)) {
$type = []; $type = [];
@@ -2230,17 +2215,12 @@ class Generate
public function formDeleteTableArray() public function formDeleteTableArray()
{ {
// remove any reference arrays // remove any reference arrays
if (is_array($this->reference_array)) {
if (!is_array($this->reference_array)) {
$this->reference_array = [];
}
reset($this->reference_array); reset($this->reference_array);
foreach ($this->reference_array as $reference_array) { foreach ($this->reference_array as $reference_array) {
$q = 'DELETE FROM ' . $reference_array['table_name'] $q = 'DELETE FROM ' . $reference_array['table_name']
. ' WHERE ' . $this->int_pk_name . ' = ' . $this->dba->getTableArray()[$this->int_pk_name]['value']; . ' WHERE ' . $this->int_pk_name . ' = ' . $this->dba->getTableArray()[$this->int_pk_name]['value'];
$this->dba->dbExec($q); $this->dba->dbExec($q);
} }
}
// remove any element list references // remove any element list references
if (!empty($this->element_list)) { if (!empty($this->element_list)) {
reset($this->element_list); reset($this->element_list);

View File

@@ -256,8 +256,8 @@ class Image
} }
// check resize parameters // check resize parameters
if ($inc_width > $thumb_width || $inc_height > $thumb_height) { if ($inc_width > $thumb_width || $inc_height > $thumb_height) {
$thumb_width_r = 0; $thumb_width_r = 1;
$thumb_height_r = 0; $thumb_height_r = 1;
// we need to keep the aspect ration on longest side // we need to keep the aspect ration on longest side
if ( if (
($inc_height > $inc_width && ($inc_height > $inc_width &&
@@ -288,6 +288,12 @@ class Image
!file_exists($thumbnail_write_path . $thumbnail) !file_exists($thumbnail_write_path . $thumbnail)
) { ) {
// image, copy source image, offset in image, source x/y, new size, source image size // image, copy source image, offset in image, source x/y, new size, source image size
if ($thumb_width_r < 1) {
$thumb_width_r = 1;
}
if ($thumb_height_r < 1) {
$thumb_height_r = 1;
}
$thumb = imagecreatetruecolor($thumb_width_r, $thumb_height_r); $thumb = imagecreatetruecolor($thumb_width_r, $thumb_height_r);
if ($thumb === false) { if ($thumb === false) {
throw new \RuntimeException( throw new \RuntimeException(
@@ -380,9 +386,7 @@ class Image
} }
} }
// add output path // add output path
if ($thumbnail !== false) {
$thumbnail = $thumbnail_web_path . $thumbnail; $thumbnail = $thumbnail_web_path . $thumbnail;
}
} elseif ($create_dummy === true) { } elseif ($create_dummy === true) {
// create dummy image in the thumbnail size // create dummy image in the thumbnail size
// if one side is missing, use the other side to create a square // if one side is missing, use the other side to create a square
@@ -399,10 +403,10 @@ class Image
!file_exists($thumbnail_write_path . $thumbnail) !file_exists($thumbnail_write_path . $thumbnail)
) { ) {
// if both are unset, set to 250 // if both are unset, set to 250
if ($thumb_height == 0) { if ($thumb_height < 1) {
$thumb_height = 250; $thumb_height = 250;
} }
if ($thumb_width == 0) { if ($thumb_width < 1) {
$thumb_width = 250; $thumb_width = 250;
} }
$thumb = imagecreatetruecolor($thumb_width, $thumb_height); $thumb = imagecreatetruecolor($thumb_width, $thumb_height);

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 (in_array($type, self::CUSTOM_REQUESTS)) { } elseif (!empty($type) && 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
@@ -700,12 +700,12 @@ class Curl implements Interface\RequestsInterface
// if we have a timeout signal // if we have a timeout signal
if (!empty($this->config['timeout'])) { if (!empty($this->config['timeout'])) {
$timeout_requires_no_signal = $this->config['timeout'] < 1; $timeout_requires_no_signal = $this->config['timeout'] < 1;
curl_setopt($handle, CURLOPT_TIMEOUT_MS, $this->config['timeout'] * 1000); curl_setopt($handle, CURLOPT_TIMEOUT_MS, (int)round($this->config['timeout'] * 1000));
} }
if (!empty($this->config['connection_timeout'])) { if (!empty($this->config['connection_timeout'])) {
$timeout_requires_no_signal = $timeout_requires_no_signal || $timeout_requires_no_signal = $timeout_requires_no_signal ||
$this->config['connection_timeout'] < 1; $this->config['connection_timeout'] < 1;
curl_setopt($handle, CURLOPT_CONNECTTIMEOUT_MS, $this->config['connection_timeout'] * 1000); curl_setopt($handle, CURLOPT_CONNECTTIMEOUT_MS, (int)round($this->config['connection_timeout'] * 1000, 1));
} }
if ($timeout_requires_no_signal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') { if ($timeout_requires_no_signal && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
curl_setopt($handle, CURLOPT_NOSIGNAL, true); curl_setopt($handle, CURLOPT_NOSIGNAL, true);

View File

@@ -243,6 +243,8 @@ final class CoreLibsACLLoginTest extends TestCase
[], [],
[ [
'EUID' => 1, 'EUID' => 1,
'ECUID' => 'abc',
'ECUUID' => '1233456-1234-1234-1234-123456789012',
], ],
2, 2,
[], [],
@@ -260,6 +262,8 @@ final class CoreLibsACLLoginTest extends TestCase
[], [],
[ [
'EUID' => 1, 'EUID' => 1,
'ECUID' => 'abc',
'ECUUID' => '1233456-1234-1234-1234-123456789012',
'USER_NAME' => '', 'USER_NAME' => '',
'GROUP_NAME' => '', 'GROUP_NAME' => '',
'ADMIN' => 1, 'ADMIN' => 1,
@@ -1085,9 +1089,9 @@ final class CoreLibsACLLoginTest extends TestCase
/** @var \CoreLibs\Create\Session&MockObject */ /** @var \CoreLibs\Create\Session&MockObject */
$session_mock = $this->createPartialMock( $session_mock = $this->createPartialMock(
\CoreLibs\Create\Session::class, \CoreLibs\Create\Session::class,
['startSession', 'checkActiveSession', 'sessionDestroy'] ['getSessionId', 'checkActiveSession', 'sessionDestroy']
); );
$session_mock->method('startSession')->willReturn('ACLLOGINTEST12'); $session_mock->method('getSessionId')->willReturn('ACLLOGINTEST12');
$session_mock->method('checkActiveSession')->willReturn(true); $session_mock->method('checkActiveSession')->willReturn(true);
$session_mock->method('sessionDestroy')->will( $session_mock->method('sessionDestroy')->will(
$this->returnCallback(function () { $this->returnCallback(function () {
@@ -1788,9 +1792,9 @@ final class CoreLibsACLLoginTest extends TestCase
/** @var \CoreLibs\Create\Session&MockObject */ /** @var \CoreLibs\Create\Session&MockObject */
$session_mock = $this->createPartialMock( $session_mock = $this->createPartialMock(
\CoreLibs\Create\Session::class, \CoreLibs\Create\Session::class,
['startSession', 'checkActiveSession', 'sessionDestroy'] ['getSessionId', 'checkActiveSession', 'sessionDestroy']
); );
$session_mock->method('startSession')->willReturn('ACLLOGINTEST34'); $session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34');
$session_mock->method('checkActiveSession')->willReturn(true); $session_mock->method('checkActiveSession')->willReturn(true);
$session_mock->method('sessionDestroy')->will( $session_mock->method('sessionDestroy')->will(
$this->returnCallback(function () { $this->returnCallback(function () {
@@ -1902,9 +1906,9 @@ final class CoreLibsACLLoginTest extends TestCase
/** @var \CoreLibs\Create\Session&MockObject */ /** @var \CoreLibs\Create\Session&MockObject */
$session_mock = $this->createPartialMock( $session_mock = $this->createPartialMock(
\CoreLibs\Create\Session::class, \CoreLibs\Create\Session::class,
['startSession', 'checkActiveSession', 'sessionDestroy'] ['getSessionId', 'checkActiveSession', 'sessionDestroy']
); );
$session_mock->method('startSession')->willReturn('ACLLOGINTEST34'); $session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34');
$session_mock->method('checkActiveSession')->willReturn(true); $session_mock->method('checkActiveSession')->willReturn(true);
$session_mock->method('sessionDestroy')->will( $session_mock->method('sessionDestroy')->will(
$this->returnCallback(function () { $this->returnCallback(function () {
@@ -1990,9 +1994,9 @@ final class CoreLibsACLLoginTest extends TestCase
/** @var \CoreLibs\Create\Session&MockObject */ /** @var \CoreLibs\Create\Session&MockObject */
$session_mock = $this->createPartialMock( $session_mock = $this->createPartialMock(
\CoreLibs\Create\Session::class, \CoreLibs\Create\Session::class,
['startSession', 'checkActiveSession', 'sessionDestroy'] ['getSessionId', 'checkActiveSession', 'sessionDestroy']
); );
$session_mock->method('startSession')->willReturn('ACLLOGINTEST34'); $session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34');
$session_mock->method('checkActiveSession')->willReturn(true); $session_mock->method('checkActiveSession')->willReturn(true);
$session_mock->method('sessionDestroy')->will( $session_mock->method('sessionDestroy')->will(
$this->returnCallback(function () { $this->returnCallback(function () {
@@ -2086,9 +2090,9 @@ final class CoreLibsACLLoginTest extends TestCase
/** @var \CoreLibs\Create\Session&MockObject */ /** @var \CoreLibs\Create\Session&MockObject */
$session_mock = $this->createPartialMock( $session_mock = $this->createPartialMock(
\CoreLibs\Create\Session::class, \CoreLibs\Create\Session::class,
['startSession', 'checkActiveSession', 'sessionDestroy'] ['getSessionId', 'checkActiveSession', 'sessionDestroy']
); );
$session_mock->method('startSession')->willReturn('ACLLOGINTEST34'); $session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34');
$session_mock->method('checkActiveSession')->willReturn(true); $session_mock->method('checkActiveSession')->willReturn(true);
$session_mock->method('sessionDestroy')->will( $session_mock->method('sessionDestroy')->will(
$this->returnCallback(function () { $this->returnCallback(function () {

View File

@@ -32,6 +32,7 @@ BEGIN
IF TG_OP = 'INSERT' THEN IF TG_OP = 'INSERT' THEN
NEW.date_created := 'now'; NEW.date_created := 'now';
NEW.cuid := random_string(random_length); NEW.cuid := random_string(random_length);
NEW.cuuid := gen_random_uuid();
ELSIF TG_OP = 'UPDATE' THEN ELSIF TG_OP = 'UPDATE' THEN
NEW.date_updated := 'now'; NEW.date_updated := 'now';
END IF; END IF;
@@ -305,6 +306,7 @@ CREATE TABLE temp_files (
-- DROP TABLE edit_generic; -- DROP TABLE edit_generic;
CREATE TABLE edit_generic ( CREATE TABLE edit_generic (
cuid VARCHAR, cuid VARCHAR,
cuuid UUID,
date_created TIMESTAMP WITHOUT TIME ZONE DEFAULT clock_timestamp(), date_created TIMESTAMP WITHOUT TIME ZONE DEFAULT clock_timestamp(),
date_updated TIMESTAMP WITHOUT TIME ZONE date_updated TIMESTAMP WITHOUT TIME ZONE
); );
@@ -652,6 +654,8 @@ COMMENT ON COLUMN edit_user.additional_acl IS 'Additional Access Control List st
CREATE TABLE edit_log ( CREATE TABLE edit_log (
edit_log_id SERIAL PRIMARY KEY, edit_log_id SERIAL PRIMARY KEY,
euid INT, -- this is a foreign key, but I don't nedd to reference to it euid INT, -- this is a foreign key, but I don't nedd to reference to it
ecuid VARCHAR,
ecuuid UUID,
FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL, FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL,
username VARCHAR, username VARCHAR,
password VARCHAR, password VARCHAR,
@@ -664,6 +668,7 @@ CREATE TABLE edit_log (
page VARCHAR, page VARCHAR,
action VARCHAR, action VARCHAR,
action_id VARCHAR, action_id VARCHAR,
action_sub_id VARCHAR,
action_yes VARCHAR, action_yes VARCHAR,
action_flag VARCHAR, action_flag VARCHAR,
action_menu VARCHAR, action_menu VARCHAR,

View File

@@ -319,6 +319,36 @@ final class CoreLibsConvertMathTest extends TestCase
[6, 12, 18], [6, 12, 18],
] ]
], ],
'inblanaced [2x2,3] x [3x2]' => [
'a' => [
[1, 2, 3],
[4, 5]
],
'b' => [
[6, 7],
[8, 9],
[10, 11]
],
'result' => [
[52, 58],
[64, 73],
]
],
'inblanaced [2x3] x [3x1,2]' => [
'a' => [
[1, 2, 3],
[4, 5, 7]
],
'b' => [
[7, 8],
[9, 10],
[11]
],
'result' => [
[58, 28],
[150, 82],
]
],
]; ];
} }

View File

@@ -22,7 +22,6 @@ final class CoreLibsCreateSessionTest extends TestCase
public function sessionProvider(): array public function sessionProvider(): array
{ {
// 0: session name as parameter or for GLOBAL value // 0: session name as parameter or for GLOBAL value
// 1: type p: parameter, g: global, d: php.ini default
// 2: mock data as array // 2: mock data as array
// checkCliStatus: true/false, // checkCliStatus: true/false,
// getSessionStatus: PHP_SESSION_DISABLED for abort, // getSessionStatus: PHP_SESSION_DISABLED for abort,
@@ -31,13 +30,10 @@ final class CoreLibsCreateSessionTest extends TestCase
// checkActiveSession: true/false, [1st call, 2nd call] // checkActiveSession: true/false, [1st call, 2nd call]
// getSessionId: string or false // getSessionId: string or false
// 3: exepcted name (session)] // 3: exepcted name (session)]
// 4: Exception thrown on error // 4: auto write close flag
// 5: exception code, null for none
// 6: expected error string
return [ return [
'session parameter' => [ 'session parameter' => [
'sessionNameParameter', 'sessionNameParameter',
'p',
[ [
'checkCliStatus' => false, 'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_NONE, 'getSessionStatus' => PHP_SESSION_NONE,
@@ -47,12 +43,9 @@ final class CoreLibsCreateSessionTest extends TestCase
], ],
'sessionNameParameter', 'sessionNameParameter',
null, null,
null,
'',
], ],
'session globals' => [ 'session globals' => [
'sessionNameGlobals', 'sessionNameGlobals',
'g',
[ [
'checkCliStatus' => false, 'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_NONE, 'getSessionStatus' => PHP_SESSION_NONE,
@@ -61,13 +54,10 @@ final class CoreLibsCreateSessionTest extends TestCase
'getSessionId' => '1234abcd4567' 'getSessionId' => '1234abcd4567'
], ],
'sessionNameGlobals', 'sessionNameGlobals',
null, false,
null,
'',
], ],
'session name default' => [ 'auto write close' => [
'', 'sessionNameAutoWriteClose',
'd',
[ [
'checkCliStatus' => false, 'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_NONE, 'getSessionStatus' => PHP_SESSION_NONE,
@@ -75,109 +65,8 @@ final class CoreLibsCreateSessionTest extends TestCase
'checkActiveSession' => [false, true], 'checkActiveSession' => [false, true],
'getSessionId' => '1234abcd4567' 'getSessionId' => '1234abcd4567'
], ],
'', 'sessionNameAutoWriteClose',
null, true,
null,
'',
],
// error checks
// 1: we are in cli
'on cli error' => [
'',
'd',
[
'checkCliStatus' => true,
'getSessionStatus' => PHP_SESSION_NONE,
'setSessionName' => true,
'checkActiveSession' => [false, true],
'getSessionId' => '1234abcd4567'
],
'',
'RuntimeException',
1,
'[SESSION] No sessions in php cli'
],
// 2: session disabled
'session disabled error' => [
'',
'd',
[
'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_DISABLED,
'setSessionName' => true,
'checkActiveSession' => [false, true],
'getSessionId' => '1234abcd4567'
],
'',
'RuntimeException',
2,
'[SESSION] Sessions are disabled'
],
// 3: invalid session name: string
'invalid name chars error' => [
'1invalid$session#;',
'p',
[
'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_NONE,
'setSessionName' => false,
'checkActiveSession' => [false, true],
'getSessionId' => '1234abcd4567'
],
'',
'UnexpectedValueException',
3,
'[SESSION] Invalid session name: 1invalid$session#;'
],
// 3: invalid session name: only numbers
'invalid name numbers only error' => [
'123',
'p',
[
'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_NONE,
'setSessionName' => false,
'checkActiveSession' => [false, true],
'getSessionId' => '1234abcd4567'
],
'',
'UnexpectedValueException',
3,
'[SESSION] Invalid session name: 123'
],
// 3: invalid session name: invalid name short
// 3: invalid session name: too long (128)
// 4: failed to start session (2nd false on check active session)
'invalid name numbers only error' => [
'',
'd',
[
'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_NONE,
'setSessionName' => true,
'checkActiveSession' => [false, false],
'getSessionId' => '1234abcd4567'
],
'',
'RuntimeException',
4,
'[SESSION] Failed to activate session'
],
// 5: get session id return false
'invalid name numbers only error' => [
'',
'd',
[
'checkCliStatus' => false,
'getSessionStatus' => PHP_SESSION_NONE,
'setSessionName' => true,
'checkActiveSession' => [false, true],
'getSessionId' => false
],
'',
'UnexpectedValueException',
5,
'[SESSION] getSessionId did not return a session id'
], ],
]; ];
} }
@@ -190,32 +79,23 @@ final class CoreLibsCreateSessionTest extends TestCase
* @testdox startSession $input name for $type will be $expected (error: $expected_error) [$_dataName] * @testdox startSession $input name for $type will be $expected (error: $expected_error) [$_dataName]
* *
* @param string $input * @param string $input
* @param string $type
* @param array<mixed> $mock_data * @param array<mixed> $mock_data
* @param string $expected * @param string $expected
* @param string|null $exception
* @param string $expected_error
* @return void * @return void
*/ */
public function testStartSession( public function testStartSession(
string $input, string $input,
string $type,
array $mock_data, array $mock_data,
string $expected, string $expected,
?string $exception, ?bool $auto_write_close,
?int $exception_code,
string $expected_error
): void { ): void {
// override expected
if ($type == 'd') {
$expected = ini_get('session.name');
}
/** @var \CoreLibs\Create\Session&MockObject $session_mock */ /** @var \CoreLibs\Create\Session&MockObject $session_mock */
$session_mock = $this->createPartialMock( $session_mock = $this->createPartialMock(
\CoreLibs\Create\Session::class, \CoreLibs\Create\Session::class,
[ [
'checkCliStatus', 'getSessionStatus', 'checkActiveSession', 'checkCliStatus',
'setSessionName', 'startSessionCall', 'getSessionId', 'getSessionStatus', 'checkActiveSession',
'getSessionId',
'getSessionName' 'getSessionName'
] ]
); );
@@ -234,12 +114,8 @@ final class CoreLibsCreateSessionTest extends TestCase
$mock_data['checkActiveSession'][0], $mock_data['checkActiveSession'][0],
$mock_data['checkActiveSession'][1], $mock_data['checkActiveSession'][1],
); );
// dummy set for session name
$session_mock->method('setSessionName')->with($input)->willReturn($mock_data['setSessionName']);
// set session name & return bsed on request data // set session name & return bsed on request data
$session_mock->method('getSessionName')->willReturn($expected); $session_mock->method('getSessionName')->willReturn($expected);
// will not return anything
$session_mock->method('startSessionCall');
// in test case only return string // in test case only return string
// false: will return false // false: will return false
$session_mock->method('getSessionId')->willReturn($mock_data['getSessionId']); $session_mock->method('getSessionId')->willReturn($mock_data['getSessionId']);
@@ -247,25 +123,7 @@ final class CoreLibsCreateSessionTest extends TestCase
// regex for session id // regex for session id
$ression_id_regex = "/^\w+$/"; $ression_id_regex = "/^\w+$/";
if ($exception !== null) { $session_id = $session_mock->getSessionId();
$this->expectException($exception);
$this->expectExceptionCode($exception_code);
}
unset($GLOBALS['SET_SESSION_NAME']);
$session_id = '';
switch ($type) {
case 'p':
$session_id = $session_mock->startSession($input);
break;
case 'g':
$GLOBALS['SET_SESSION_NAME'] = $input;
$session_id = $session_mock->startSession();
break;
case 'd':
$session_id = $session_mock->startSession();
break;
}
// asert checks // asert checks
if (!empty($session_id)) { if (!empty($session_id)) {
$this->assertMatchesRegularExpression( $this->assertMatchesRegularExpression(
@@ -284,6 +142,73 @@ final class CoreLibsCreateSessionTest extends TestCase
} }
} }
/**
* Undocumented function
*
* @return array
*/
public function providerSessionException(): array
{
return [
'not cli' => [
'TEST_EXCEPTION',
\RuntimeException::class,
1,
'/^\[SESSION\] No sessions in php cli$/',
],
/* 'session disabled ' => [
'TEST_EXCEPTION',
\RuntimeException::class,
2,
'/^\[SESSION\] Sessions are disabled/'
],
'invalid session name' => [
'--#as^-292p-',
\UnexpectedValueException::class,
3,
'/^\[SESSION\] Invalid session name: /'
],
'failed to activate session' => [
'TEST_EXCEPTION',
\RuntimeException::class,
4,
'/^\[SESSION\] Failed to activate session/'
],
'not a valid session id returned' => [
\UnexpectedValueException::class,
5,
'/^\[SESSION\] getSessionId did not return a session id/'
], */
];
}
/**
* exception checks
*
* @covers ::initSession
* @dataProvider providerSessionException
* @testdox create session $session_name with exception $exception ($exception_code) [$_dataName]
*
* @param string $session_name
* @param string $exception
* @param int $exception_code
* @param string $expected_error
* @return void
*/
public function testSessionException(
string $session_name,
string $exception,
int $exception_code,
string $expected_error,
): void {
//
// throws only on new Object creation
$this->expectException($exception);
$this->expectExceptionCode($exception_code);
$this->expectExceptionMessageMatches($expected_error);
new \CoreLibs\Create\Session($session_name);
}
/** /**
* provider for session name check * provider for session name check
* *
@@ -369,6 +294,8 @@ final class CoreLibsCreateSessionTest extends TestCase
]; ];
} }
// NOTE: with auto start session, we cannot test this in the command line
/** /**
* method call test * method call test
* *
@@ -384,74 +311,32 @@ final class CoreLibsCreateSessionTest extends TestCase
* @param mixed $expected * @param mixed $expected
* @return void * @return void
*/ */
public function testMethodSetGet($name, $input, $expected): void /* public function testMethodSetGet($name, $input, $expected): void
{ {
$session = new \CoreLibs\Create\Session(); $session = new \CoreLibs\Create\Session('TEST_METHOD');
$session->setS($name, $input); $session->set($name, $input);
$this->assertEquals( $this->assertEquals(
$expected, $expected,
$session->getS($name), $session->get($name),
'method set assert' 'method set assert'
); );
// isset true // isset true
$this->assertTrue( $this->assertTrue(
$session->issetS($name), $session->isset($name),
'method isset assert ok' 'method isset assert ok'
); );
$session->unsetS($name); $session->unset($name);
$this->assertEquals( $this->assertEquals(
'', '',
$session->getS($name), $session->get($name),
'method unset assert' 'method unset assert'
); );
// iset false // iset false
$this->assertFalse( $this->assertFalse(
$session->issetS($name), $session->isset($name),
'method isset assert false' 'method isset assert false'
); );
} } */
/**
* magic call test
*
* @covers ::__set
* @covers ::__get
* @covers ::__isset
* @covers ::__unset
* @dataProvider sessionDataProvider
* @testdox __set/__get/__iseet/__unset $name with $input is $expected [$_dataName]
*
* @param string|int $name
* @param mixed $input
* @param mixed $expected
* @return void
*/
public function testMagicSetGet($name, $input, $expected): void
{
$session = new \CoreLibs\Create\Session();
$session->$name = $input;
$this->assertEquals(
$expected,
$session->$name,
'magic set assert'
);
// isset true
$this->assertTrue(
isset($session->$name),
'magic isset assert ok'
);
unset($session->$name);
$this->assertEquals(
'',
$session->$name,
'magic unset assert'
);
// isset true
$this->assertFalse(
isset($session->$name),
'magic isset assert false'
);
}
/** /**
* unset all test * unset all test
@@ -461,33 +346,33 @@ final class CoreLibsCreateSessionTest extends TestCase
* *
* @return void * @return void
*/ */
public function testUnsetAll(): void /* public function testUnsetAll(): void
{ {
$test_values = [ $test_values = [
'foo' => 'abc', 'foo' => 'abc',
'bar' => '123' 'bar' => '123'
]; ];
$session = new \CoreLibs\Create\Session(); $session = new \CoreLibs\Create\Session('TEST_UNSET');
foreach ($test_values as $name => $value) { foreach ($test_values as $name => $value) {
$session->setS($name, $value); $session->set($name, $value);
// confirm set // confirm set
$this->assertEquals( $this->assertEquals(
$value, $value,
$session->getS($name), $session->get($name),
'set assert: ' . $name 'set assert: ' . $name
); );
} }
// unset all // unset all
$session->unsetAllS(); $session->unsetAll();
// check unset // check unset
foreach (array_keys($test_values) as $name) { foreach (array_keys($test_values) as $name) {
$this->assertEquals( $this->assertEquals(
'', '',
$session->getS($name), $session->get($name),
'unsert assert: ' . $name 'unsert assert: ' . $name
); );
} }
} } */
} }
// __END__ // __END__

View File

@@ -121,6 +121,7 @@ final class CoreLibsCreateUidsTest extends TestCase
* must match 7e78fe0d-59b8-4637-af7f-e88d221a7d1e * must match 7e78fe0d-59b8-4637-af7f-e88d221a7d1e
* *
* @covers ::uuidv4 * @covers ::uuidv4
* @covers ::validateUuidv4
* @testdox uuidv4 check that return is matching regex [$_dataName] * @testdox uuidv4 check that return is matching regex [$_dataName]
* *
* @return void * @return void
@@ -129,13 +130,18 @@ final class CoreLibsCreateUidsTest extends TestCase
{ {
$uuid = \CoreLibs\Create\Uids::uuidv4(); $uuid = \CoreLibs\Create\Uids::uuidv4();
$this->assertMatchesRegularExpression( $this->assertMatchesRegularExpression(
'/^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$/', '/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/',
$uuid $uuid,
'Failed regex check'
);
$this->assertTrue(
\CoreLibs\Create\Uids::validateUuuidv4($uuid),
'Failed validate regex method'
);
$this->assertFalse(
\CoreLibs\Create\Uids::validateUuuidv4('not-a-uuidv4'),
'Failed wrong uuid validated as true'
); );
// $this->assertStringMatchesFormat(
// '%4s%4s-%4s-%4s-%4s-%4s%4s%4s',
// $uuid
// );
} }
/** /**

View File

@@ -37,8 +37,9 @@ namespace tests;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
use CoreLibs\Logging\Logger\Level; use CoreLibs\Logging;
use CoreLibs\DB\Options\Convert; use CoreLibs\DB\Options\Convert;
use CoreLibs\DB\Support\ConvertPlaceholder;
/** /**
* Test class for DB\IO + DB\SQL\PgSQL * Test class for DB\IO + DB\SQL\PgSQL
@@ -117,7 +118,7 @@ final class CoreLibsDBIOTest extends TestCase
); );
} }
// define basic connection set valid and one invalid // define basic connection set valid and one invalid
self::$log = new \CoreLibs\Logging\Logging([ self::$log = new Logging\Logging([
// 'log_folder' => __DIR__ . DIRECTORY_SEPARATOR . 'log', // 'log_folder' => __DIR__ . DIRECTORY_SEPARATOR . 'log',
'log_folder' => DIRECTORY_SEPARATOR . 'tmp', 'log_folder' => DIRECTORY_SEPARATOR . 'tmp',
'log_file_id' => 'CoreLibs-DB-IO-Test', 'log_file_id' => 'CoreLibs-DB-IO-Test',
@@ -159,15 +160,12 @@ final class CoreLibsDBIOTest extends TestCase
// create the tables // create the tables
$db->dbExec( $db->dbExec(
// primary key name is table + '_id' // primary key name is table + '_id'
// table_with_primary_key_id SERIAL PRIMARY KEY,
<<<SQL <<<SQL
CREATE TABLE table_with_primary_key ( CREATE TABLE table_with_primary_key (
table_with_primary_key_id SERIAL PRIMARY KEY, table_with_primary_key_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
$base_table $base_table
SQL SQL
/* "CREATE TABLE table_with_primary_key ("
// primary key name is table + '_id'
. "table_with_primary_key_id SERIAL PRIMARY KEY, "
. $base_table */
); );
$db->dbExec( $db->dbExec(
<<<SQL <<<SQL
@@ -570,11 +568,11 @@ final class CoreLibsDBIOTest extends TestCase
); );
$db->dbClose(); $db->dbClose();
// second conenction with log set NOT debug // second conenction with log set NOT debug
$log = new \CoreLibs\Logging\Logging([ $log = new Logging\Logging([
// 'log_folder' => __DIR__ . DIRECTORY_SEPARATOR . 'log', // 'log_folder' => __DIR__ . DIRECTORY_SEPARATOR . 'log',
'log_folder' => DIRECTORY_SEPARATOR . 'tmp', 'log_folder' => DIRECTORY_SEPARATOR . 'tmp',
'log_file_id' => 'CoreLibs-DB-IO-Test', 'log_file_id' => 'CoreLibs-DB-IO-Test',
'log_level' => \CoreLibs\Logging\Logger\Level::Notice, 'log_level' => Logging\Logger\Level::Notice,
]); ]);
$db = new \CoreLibs\DB\IO( $db = new \CoreLibs\DB\IO(
self::$db_config[$connection], self::$db_config[$connection],
@@ -3293,6 +3291,7 @@ final class CoreLibsDBIOTest extends TestCase
'query' => 'INSERT INTO table_with_primary_key (row_int, uid) ' 'query' => 'INSERT INTO table_with_primary_key (row_int, uid) '
. 'VALUES ($1, $2) RETURNING table_with_primary_key_id', . 'VALUES ($1, $2) RETURNING table_with_primary_key_id',
'returning_id' => true, 'returning_id' => true,
'placeholder_converted' => [],
], ],
], ],
// update // update
@@ -3327,6 +3326,7 @@ final class CoreLibsDBIOTest extends TestCase
'query' => 'UPDATE table_with_primary_key SET row_int = $1, ' 'query' => 'UPDATE table_with_primary_key SET row_int = $1, '
. 'row_varchar = $2 WHERE uid = $3', . 'row_varchar = $2 WHERE uid = $3',
'returning_id' => false, 'returning_id' => false,
'placeholder_converted' => [],
], ],
], ],
// select // select
@@ -3356,6 +3356,7 @@ final class CoreLibsDBIOTest extends TestCase
'count' => 1, 'count' => 1,
'query' => 'SELECT row_int, uid FROM table_with_primary_key WHERE uid = $1', 'query' => 'SELECT row_int, uid FROM table_with_primary_key WHERE uid = $1',
'returning_id' => false, 'returning_id' => false,
'placeholder_converted' => [],
], ],
], ],
// any query but with no parameters // any query but with no parameters
@@ -3388,6 +3389,7 @@ final class CoreLibsDBIOTest extends TestCase
'count' => 0, 'count' => 0,
'query' => 'SELECT row_int, uid FROM table_with_primary_key', 'query' => 'SELECT row_int, uid FROM table_with_primary_key',
'returning_id' => false, 'returning_id' => false,
'placeholder_converted' => [],
], ],
], ],
// no statement name (25) // no statement name (25)
@@ -3411,6 +3413,7 @@ final class CoreLibsDBIOTest extends TestCase
'count' => 0, 'count' => 0,
'query' => '', 'query' => '',
'returning_id' => false, 'returning_id' => false,
'placeholder_converted' => [],
], ],
], ],
// no query (prepare 11) // no query (prepare 11)
@@ -3435,6 +3438,7 @@ final class CoreLibsDBIOTest extends TestCase
'count' => 0, 'count' => 0,
'query' => '', 'query' => '',
'returning_id' => false, 'returning_id' => false,
'placeholder_converted' => [],
], ],
], ],
// no db connection (prepare/execute 16) // no db connection (prepare/execute 16)
@@ -3464,6 +3468,7 @@ final class CoreLibsDBIOTest extends TestCase
'count' => 0, 'count' => 0,
'query' => 'SELECT row_int, uid FROM table_with_primary_key', 'query' => 'SELECT row_int, uid FROM table_with_primary_key',
'returning_id' => false, 'returning_id' => false,
'placeholder_converted' => [],
], ],
], ],
// prepare with different statement name // prepare with different statement name
@@ -3489,6 +3494,7 @@ final class CoreLibsDBIOTest extends TestCase
'count' => 0, 'count' => 0,
'query' => 'SELECT row_int, uid FROM table_with_primary_key', 'query' => 'SELECT row_int, uid FROM table_with_primary_key',
'returning_id' => false, 'returning_id' => false,
'placeholder_converted' => [],
], ],
], ],
// insert wrong data count compared to needed (execute 23) // insert wrong data count compared to needed (execute 23)
@@ -3514,10 +3520,12 @@ final class CoreLibsDBIOTest extends TestCase
'query' => 'INSERT INTO table_with_primary_key (row_int, uid) VALUES ' 'query' => 'INSERT INTO table_with_primary_key (row_int, uid) VALUES '
. '($1, $2) RETURNING table_with_primary_key_id', . '($1, $2) RETURNING table_with_primary_key_id',
'returning_id' => true, 'returning_id' => true,
'placeholder_converted' => [],
], ],
], ],
// execute does not return a result (22) // execute does not return a result (22)
// TODO execute does not return a result // TODO execute does not return a result
// TODO prepared statement with placeholder params auto convert
]; ];
} }
@@ -3662,7 +3670,7 @@ final class CoreLibsDBIOTest extends TestCase
} }
// check dbGetPrepareCursorValue // check dbGetPrepareCursorValue
foreach (['pk_name', 'count', 'query', 'returning_id'] as $key) { foreach (['pk_name', 'count', 'query', 'returning_id', 'placeholder_converted'] as $key) {
$this->assertEquals( $this->assertEquals(
$prepare_cursor[$key], $prepare_cursor[$key],
$db->dbGetPrepareCursorValue($stm_name, $key), $db->dbGetPrepareCursorValue($stm_name, $key),
@@ -5031,8 +5039,151 @@ final class CoreLibsDBIOTest extends TestCase
$db->dbClose(); $db->dbClose();
} }
// query placeholder convert // MARK: QUERY PLACEHOLDERS
// test query placeholder detection for all possible sets
// ::dbPrepare
/**
* placeholder sql
*
* @return array
*/
public function providerDbCountQueryParams(): array
{
return [
'one place holder' => [
'query' => 'SELECT row_varchar FROM table_with_primary_key WHERE row_varchar = $1',
'count' => 1,
'convert' => false,
],
'one place holder, json call' => [
'query' => "SELECT row_varchar FROM table_with_primary_key WHERE row_jsonb->>'data' = $1",
'count' => 1,
'convert' => false,
],
'one place holder, <> compare' => [
'query' => "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar <> $1",
'count' => 1,
'convert' => false,
],
'one place holder, named' => [
'query' => "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar <> :row_varchar",
'count' => 1,
'convert' => true,
],
'no replacement' => [
'query' => "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar = '$1'",
'count' => 0,
'convert' => false,
],
'insert' => [
'query' => "INSERT INTO table_with_primary_key (row_varchar, row_jsonb, row_int) VALUES ($1, $2, $3)",
'count' => 3,
'convert' => false,
],
'update' => [
'query' => "UPDATE table_with_primary_key SET row_varchar = $1, row_jsonb = $2, row_int = $3 WHERE row_numeric = $4",
'count' => 4,
'convert' => false,
],
'multiple, multline' => [
'query' => <<<SQL
SELECT
row_varchar
FROM
table_with_primary_key
WHERE
row_varchar = $1 AND row_int = $2
AND row_numeric = ANY($3)
SQL,
'count' => 3,
'convert' => false,
],
'two digit numbers' => [
'query' => <<<SQL
INSERT INTO table_with_primary_key (
row_int, row_numeric, row_varchar, row_varchar_literal, row_json,
row_jsonb, row_bytea, row_timestamp, row_date, row_interval
) VALUES (
$1, $2, $3, $4, $5,
$6, $7, $8, $9, $10
)
SQL,
'count' => 10,
'convert' => false,
],
'things in brackets' => [
'query' => <<<SQL
SELECT row_varchar
FROM table_with_primary_key
WHERE
row_varchar = $1 AND
(row_int = ANY($2) OR row_int = $3)
AND row_varchar_literal = $4
SQL,
'count' => 4,
'convert' => false,
],
'number compare' => [
'query' => <<<SQL
SELECT row_varchar
FROM table_with_primary_key
WHERE
row_int >= $1 OR row_int <= $2 OR
row_int > $3 OR row_int < $4
OR row_int = $5 OR row_int <> $6
SQL,
'count' => 6,
'convert' => false,
]
];
}
/**
* Placeholder check and convert tests
*
* @covers ::dbPrepare
* @covers ::__dbCountQueryParams
* @onvers ::convertPlaceholderInQuery
* @dataProvider providerDbCountQueryParams
* @testdox Query replacement count test [$_dataName]
*
* @param string $query
* @param int $count
* @return void
*/
public function testDbCountQueryParams(string $query, int $count, bool $convert): void
{
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
);
$id = sha1($query);
$db->dbSetConvertPlaceholder($convert);
$db->dbPrepare($id, $query);
// print "\n**\n";
// print "PCount: " . $db->dbGetPrepareCursorValue($id, 'count') . "\n";
// print "\n**\n";
$this->assertEquals(
$count,
$db->dbGetPrepareCursorValue($id, 'count'),
'DB count params'
);
$placeholder = ConvertPlaceholder::convertPlaceholderInQuery($query, null, 'pg');
// print "RES: " . print_r($placeholder, true) . "\n";
$this->assertEquals(
$count,
$placeholder['needed'],
'convert params'
);
}
/**
* query placeholder convert
*
* @return array
*/
public function queryPlaceholderReplaceProvider(): array public function queryPlaceholderReplaceProvider(): array
{ {
// WHERE row_varchar = $1 // WHERE row_varchar = $1
@@ -5076,7 +5227,9 @@ final class CoreLibsDBIOTest extends TestCase
WHERE row_varchar = $1 WHERE row_varchar = $1
SQL, SQL,
'expected_params' => ['string a'], 'expected_params' => ['string a'],
] ],
// TODO: test with multiple entries
// TODO: test with same entry ($1, $1, :var, :var)
]; ];
} }
@@ -5178,6 +5331,8 @@ final class CoreLibsDBIOTest extends TestCase
// - data debug // - data debug
// dbDumpData // dbDumpData
// MARK: ASYNC
// ASYNC at the end because it has 1s timeout // ASYNC at the end because it has 1s timeout
// - asynchronous executions // - asynchronous executions
// dbExecAsync, dbCheckAsync // dbExecAsync, dbCheckAsync