Compare commits

...

14 Commits

16 changed files with 1526 additions and 986 deletions

View File

@@ -1 +1 @@
9.19.0 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) {
@@ -1132,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'],
@@ -1425,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);
} }
/** /**
@@ -1566,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();
@@ -1722,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)
* *
@@ -1731,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 = '',
@@ -1749,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
@@ -1862,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
@@ -1942,6 +2106,8 @@ HTML;
$this->loginSetAcl(); $this->loginSetAcl();
} }
// MARK: setters/getters
/** /**
* Returns current set login_html content * Returns current set login_html content
* *
@@ -2111,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;
} }
@@ -2128,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
@@ -2198,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;
} }
@@ -2503,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 ?? '',

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

@@ -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

@@ -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

@@ -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
@@ -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])));
} }
/** /**
@@ -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

@@ -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

@@ -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