Compare commits

...

11 Commits

Author SHA1 Message Date
Clemens Schwaighofer
a66cc09095 Fix phpstan problems in test db encryption file 2025-04-15 17:46:41 +09:00
Clemens Schwaighofer
1cfdc45107 Fix edit user missing error example for login user id field 2025-04-15 17:40:54 +09:00
Clemens Schwaighofer
07e46c91ab Add test decryption for pg crypto columns 2025-04-14 09:19:58 +09:00
Clemens Schwaighofer
8aee448c59 Update DB IO for query hash storage and parameter count
The parameter count methods in the PgSQL class have changed
- the function returns a unique list of $ parameters

The count is now done in the DB IO part where it counts over the unique array

Query hash is stored like the query for the current run one (reset on dbExec call).
The method to create the hash is renamed to dbBuildQueryHash instead of "Get".
The dbGetQueryHash function now just returns the last set query hash. There is a matching dbResetQueryHash for unsetting the query hash.
2025-04-09 11:35:02 +09:00
Clemens Schwaighofer
37367db878 Fix regex for $$ PostgresSQL string in convert placeholder 2025-04-07 19:44:18 +09:00
Clemens Schwaighofer
2d30d1d160 Rewrite DB param lookup
* Correct wrong comment lookup
* simplify regex by excluding comment and string blocks before
* simpler lookup for each type
* update checks for more tests for various special cases

In DB IO
* add a function to return all placeholders found in a query
* only numbered parameters are looked up
2025-04-07 17:30:30 +09:00
Clemens Schwaighofer
531229e8b7 Add DB Encryption tests 2025-04-07 12:05:06 +09:00
Clemens Schwaighofer
d09c20ff9d hash test page update 2025-04-07 09:09:45 +09:00
Clemens Schwaighofer
f4ddc5a5fc Add hash hmac to the Create Hash class 2025-04-07 09:05:37 +09:00
Clemens Schwaighofer
1791ec3908 phan and phpstan fixes for hash uses in CoreLibs 2025-04-04 15:17:42 +09:00
Clemens Schwaighofer
3d13f55c35 Update Hash Class
Add new constant: STANDARD_HASH for sha256
Deprecate DEFAULT_HASH is now STANDARD_HASH_SHORT

Deprecated
__sha1Short:
replace with __crc32b with the default parameter use_sha false
replace with sha1Short if use_sha is true

__hash:
replace with hashShort if default hash type
replace with hash for all others with new default STANDARD_HASH

__hashLong:
replace with hashLong

New:
hashShort: returns STANDARD_HASH_SHORT which is __hash default type
hashStd: returns STANDARD_HASH sha256
hash: switches to STANDARD_HASH as default type
2025-04-04 15:08:58 +09:00
16 changed files with 859 additions and 189 deletions

View File

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

View File

@@ -135,6 +135,7 @@ final class CoreLibsDBIOTest extends TestCase
} }
// check if they already exist, drop them // check if they already exist, drop them
if ($db->dbShowTableMetaData('table_with_primary_key') !== false) { if ($db->dbShowTableMetaData('table_with_primary_key') !== false) {
$db->dbExec("CREATE EXTENSION IF NOT EXISTS pgcrypto");
$db->dbExec("DROP TABLE table_with_primary_key"); $db->dbExec("DROP TABLE table_with_primary_key");
$db->dbExec("DROP TABLE table_without_primary_key"); $db->dbExec("DROP TABLE table_without_primary_key");
$db->dbExec("DROP TABLE test_meta"); $db->dbExec("DROP TABLE test_meta");
@@ -4744,7 +4745,7 @@ final class CoreLibsDBIOTest extends TestCase
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']); $res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
// all hast to be string // all hast to be string
foreach ($res as $key => $value) { foreach ($res as $key => $value) {
$this->assertIsString($value, 'Aseert string for column: ' . $key); $this->assertIsString($value, 'Assert string for column: ' . $key);
} }
// convert base only // convert base only
$db->dbSetConvertFlag(Convert::on); $db->dbSetConvertFlag(Convert::on);
@@ -4757,10 +4758,10 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -4774,13 +4775,13 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
case 'float': case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -4794,17 +4795,17 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
case 'float': case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break; break;
case 'json': case 'json':
case 'jsonb': case 'jsonb':
$this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name); $this->assertIsArray($value, 'Assert array for column: ' . $key . '/' . $name);
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -4818,25 +4819,25 @@ final class CoreLibsDBIOTest extends TestCase
} }
switch ($type_layout[$name]) { switch ($type_layout[$name]) {
case 'int': case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name); $this->assertIsInt($value, 'Assert int for column: ' . $key . '/' . $name);
break; break;
case 'float': case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name); $this->assertIsFloat($value, 'Assert float for column: ' . $key . '/' . $name);
break; break;
case 'json': case 'json':
case 'jsonb': case 'jsonb':
$this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name); $this->assertIsArray($value, 'Assert array for column: ' . $key . '/' . $name);
break; break;
case 'bytea': case 'bytea':
// for hex types it must not start with \x // for hex types it must not start with \x
$this->assertStringStartsNotWith( $this->assertStringStartsNotWith(
'\x', '\x',
$value, $value,
'Aseert bytes not starts with \x for column: ' . $key . '/' . $name 'Assert bytes not starts with \x for column: ' . $key . '/' . $name
); );
break; break;
default: default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name); $this->assertIsString($value, 'Assert string for column: ' . $key . '/' . $name);
break; break;
} }
} }
@@ -5008,8 +5009,8 @@ final class CoreLibsDBIOTest extends TestCase
) )
), ),
($params === null ? ($params === null ?
$db->dbGetQueryHash($query) : $db->dbBuildQueryHash($query) :
$db->dbGetQueryHash($query, $params) $db->dbBuildQueryHash($query, $params)
), ),
'Failed assertdbGetQueryHash ' 'Failed assertdbGetQueryHash '
); );
@@ -5235,6 +5236,9 @@ final class CoreLibsDBIOTest extends TestCase
$3 $3
-- comment 3 -- comment 3
, $4 , $4
-- ignore $5, $6
-- $7, $8
-- digest($9, 10)
) )
SQL, SQL,
'count' => 4, 'count' => 4,
@@ -5305,8 +5309,57 @@ final class CoreLibsDBIOTest extends TestCase
SQL, SQL,
'count' => 2, 'count' => 2,
'convert' => false, 'convert' => false,
],
// special $$ string case
'text string, with $ placehoders that could be seen as $$ string' => [
'query' => <<<SQL
SELECT row_int
FROM table_with_primary_key
WHERE
row_bytea = digest($3::VARCHAR, $4) OR
row_varchar = encode(digest($3, $4), 'hex') OR
row_bytea = hmac($3, $5, $4) OR
row_varchar = encode(hmac($3, $5, $4), 'hex') OR
row_bytea = pgp_sym_encrypt($3, $6) OR
row_varchar = encode(pgp_sym_encrypt($1, $6), 'hex') OR
row_varchar = CASE WHEN row_int = 1 THEN $1 ELSE $2 END
SQL,
'count' => 6,
'convert' => false,
],
// NOTE, in SQL heredoc we cannot write $$ strings parts
'text string, with $ placehoders are in $$ strings' => [
'query' => '
SELECT row_int
FROM table_with_primary_key
WHERE
row_varchar = $$some string$$ OR
row_varchar = $tag$some string$tag$ OR
row_varchar = $btag$some $1 string$btag$ OR
row_varchar = $btag$some $1 $subtag$ something $subtag$string$btag$ OR
row_varchar = $1
',
'count' => 1,
'convert' => false,
],
// a text string with escaped quite
'text string, with escaped quote' => [
'query' => <<<SQL
SELECT row_int
FROM table_with_primary_key
WHERE
row_varchar = 'foo bar bar baz $5' OR
row_varchar = 'foo bar '' barbar $6' OR
row_varchar = E'foo bar \' barbar $7' OR
row_varchar = CASE WHEN row_int = 1 THEN $1 ELSE $2 END
SQL,
'count' => 2,
'convert' => false,
] ]
]; ];
$string = <<<SQL
'''
SQL;
} }
/** /**

View File

@@ -21,6 +21,7 @@ ob_end_flush();
use CoreLibs\Debug\Support; use CoreLibs\Debug\Support;
use CoreLibs\DB\Support\ConvertPlaceholder; use CoreLibs\DB\Support\ConvertPlaceholder;
use CoreLibs\Convert\Html;
$log = new CoreLibs\Logging\Logging([ $log = new CoreLibs\Logging\Logging([
'log_folder' => BASE . LOG, 'log_folder' => BASE . LOG,
@@ -38,10 +39,12 @@ print '<div><h1>' . $PAGE_NAME . '</h1></div>';
print "LOGFILE NAME: " . $log->getLogFile() . "<br>"; print "LOGFILE NAME: " . $log->getLogFile() . "<br>";
print "LOGFILE ID: " . $log->getLogFileId() . "<br>"; print "LOGFILE ID: " . $log->getLogFileId() . "<br>";
print "Lookup Regex: <pre>" . ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS . "</pre>"; print "Lookup Regex: <pre>" . Html::htmlent(ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS) . "</pre>";
print "Replace Named Regex: <pre>" . ConvertPlaceholder::REGEX_REPLACE_NAMED . "</pre>"; print "Lookup Numbered Regex: <pre>" . Html::htmlent(ConvertPlaceholder::REGEX_LOOKUP_NUMBERED) . "</pre>";
print "Replace Named Regex: <pre>" . ConvertPlaceholder::REGEX_REPLACE_QUESTION_MARK . "</pre>"; print "Replace Named Regex: <pre>" . Html::htmlent(ConvertPlaceholder::REGEX_REPLACE_NAMED) . "</pre>";
print "Replace Named Regex: <pre>" . ConvertPlaceholder::REGEX_REPLACE_NUMBERED . "</pre>"; print "Replace Question Mark Regex: <pre>"
. Html::htmlent(ConvertPlaceholder::REGEX_REPLACE_QUESTION_MARK) . "</pre>";
print "Replace Numbered Regex: <pre>" . Html::htmlent(ConvertPlaceholder::REGEX_REPLACE_NUMBERED) . "</pre>";
$uniqid = \CoreLibs\Create\Uids::uniqIdShort(); $uniqid = \CoreLibs\Create\Uids::uniqIdShort();
// $binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: ''); // $binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: '');
@@ -91,40 +94,63 @@ RETURNING
some_binary some_binary
SQL; SQL;
print "[ALL] Convert: " print "<b>[ALL] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
$query = "SELECT foo FROM bar WHERE baz = :baz AND buz = :baz AND biz = :biz AND boz = :bez"; $query = "SELECT foo FROM bar WHERE baz = :baz AND buz = :baz AND biz = :biz AND boz = :bez";
$params = [':baz' => 'SETBAZ', ':bez' => 'SETBEZ', ':biz' => 'SETBIZ']; $params = [':baz' => 'SETBAZ', ':bez' => 'SETBEZ', ':biz' => 'SETBIZ'];
print "[NO PARAMS] Convert: " print "<b>[NO PARAMS] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
$query = "SELECT foo FROM bar WHERE baz = :baz AND buz = :baz AND biz = :biz AND boz = :bez"; $query = "SELECT foo FROM bar WHERE baz = :baz AND buz = :baz AND biz = :biz AND boz = :bez";
$params = null; $params = null;
print "[NO PARAMS] Convert: " print "<b>[NO PARAMS] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
$query = "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar <> :row_varchar"; $query = "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar <> :row_varchar";
$params = null; $params = null;
print "[NO PARAMS] Convert: " print "<b>[NO PARAMS] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
$query = "SELECT row_varchar, row_varchar_literal, row_int, row_date FROM table_with_primary_key"; $query = "SELECT row_varchar, row_varchar_literal, row_int, row_date FROM table_with_primary_key";
$params = null; $params = null;
print "[NO PARAMS] TEST: " print "<b>[NO PARAMS] TEST</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";
print "[P-CONV]: " $query = <<<SQL
UPDATE table_with_primary_key SET
row_int = $1::INT, row_numeric = $1::NUMERIC, row_varchar = $1
WHERE
row_varchar = $1
SQL;
$params = [1];
print "<b>[All the same params] TEST</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>";
echo "<hr>";
$query = <<<SQL
SELECT row_varchar, row_varchar_literal, row_int, row_date
FROM table_with_primary_key
WHERE row_varchar = :row_varchar
SQL;
$params = [':row_varchar' => 1];
print "<b>[: param] TEST</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params))
. "<br>";
echo "<hr>";
print "<b>[P-CONV]</b>: "
. Support::printAr( . Support::printAr(
ConvertPlaceholder::updateParamList([ ConvertPlaceholder::updateParamList([
'original' => [ 'original' => [
@@ -186,6 +212,13 @@ SQL,
'params' => [\CoreLibs\Create\Uids::uniqIdShort(), 'string A-1', 1234], 'params' => [\CoreLibs\Create\Uids::uniqIdShort(), 'string A-1', 1234],
'direction' => 'pg', 'direction' => 'pg',
], ],
'b?' => [
'query' => <<<SQL
SELECT test FROM test_foo = ?
SQL,
'params' => [1234],
'direction' => 'pg',
],
'b:' => [ 'b:' => [
'query' => <<<SQL 'query' => <<<SQL
INSERT INTO test_foo ( INSERT INTO test_foo (
@@ -220,7 +253,7 @@ foreach ($test_queries as $info => $data) {
$query = $data['query']; $query = $data['query'];
$params = $data['params']; $params = $data['params'];
$direction = $data['direction']; $direction = $data['direction'];
print "[$info] Convert: " print "<b>[$info] Convert</b>: "
. Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params, $direction)) . Support::printAr(ConvertPlaceholder::convertPlaceholderInQuery($query, $params, $direction))
. "<br>"; . "<br>";
echo "<hr>"; echo "<hr>";

View File

@@ -0,0 +1,166 @@
<?php // phpcs:ignore warning
/**
* @phan-file-suppress PhanTypeSuspiciousStringExpression
*/
declare(strict_types=1);
// turn on all error reporting
error_reporting(E_ALL | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR);
ob_start();
// basic class test file
define('USE_DATABASE', true);
// sample config
require 'config.php';
// for testing encryption compare
use OpenPGP\OpenPGP;
// define log file id
$LOG_FILE_ID = 'classTest-db-query-encryption';
ob_end_flush();
// use CoreLibs\Debug\Support;
use CoreLibs\Security\SymmetricEncryption;
use CoreLibs\Security\CreateKey;
use CoreLibs\Create\Hash;
use CoreLibs\Debug\Support;
$log = new CoreLibs\Logging\Logging([
'log_folder' => BASE . LOG,
'log_file_id' => $LOG_FILE_ID,
'log_per_date' => true,
]);
// db connection and attach logger
$db = new CoreLibs\DB\IO(DB_CONFIG, $log);
$db->log->debug('START', '=============================>');
$PAGE_NAME = 'TEST CLASS: DB QUERY ENCRYPTION';
print "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title></head>";
print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>';
// encryption key
$key_new = CreateKey::generateRandomKey();
print "Secret Key NEW: " . $key_new . "<br>";
// for reproducable test results
$key = 'e475c19b9a3c8363feb06b51f5b73f1dc9b6f20757d4ab89509bf5cc70ed30ec';
print "Secret Key: " . $key . "<br>";
// test text
$text_string = "I a some deep secret";
$text_string = "I a some deep secret ABC";
//
$crypt = new SymmetricEncryption($key);
$encrypted = $crypt->encrypt($text_string);
$string_hashed = Hash::hashStd($text_string);
$string_hmac = Hash::hashHmac($text_string, $key);
$decrypted = $crypt->decrypt($encrypted);
print "String: " . $text_string . "<br>";
print "Encrypted: " . $encrypted . "<br>";
print "Hashed: " . $string_hashed . "<br>";
print "Hmac: " . $string_hmac . "<br>";
$db->dbExecParams(
<<<SQL
INSERT INTO test_encryption (
-- for compare
plain_text,
-- via php encryption
hash_text, hmac_text, crypt_text,
-- -- in DB encryption
pg_digest_bytea, pg_digest_text,
pg_hmac_bytea, pg_hmac_text,
pg_crypt_bytea, pg_crypt_text
) VALUES (
$1,
$2, $3, $4,
digest($1::VARCHAR, $5),
encode(digest($1, $5), 'hex'),
hmac($1, $6, $5),
encode(hmac($1, $6, $5), 'hex'),
pgp_sym_encrypt($1, $7),
encode(pgp_sym_encrypt($1, $7), 'hex')
) RETURNING cuuid
SQL,
[
// 1: original string
$text_string,
// 2: hashed, 3: hmac, 4: encrypted
$string_hashed, $string_hmac, $encrypted,
// 5: hash type, 6: hmac secret, 7: pgp secret
'sha256', $key, $key
]
);
$cuuid = $db->dbGetReturningExt('cuuid');
print "INSERTED: " . print_r($cuuid, true) . "<br>";
print "LAST ERROR: " . $db->dbGetLastError(true) . "<br>";
// read back
$res = $db->dbReturnRowParams(
<<<SQL
SELECT
-- for compare
plain_text,
-- via php encryption
hash_text, hmac_text, crypt_text,
-- in DB encryption
pg_digest_bytea, pg_digest_text,
pg_hmac_bytea, pg_hmac_text,
pg_crypt_bytea, pg_crypt_text,
encode(pg_crypt_bytea, 'hex') AS pg_crypt_bytea_hex,
pgp_sym_decrypt(pg_crypt_bytea, $2) AS from_pg_crypt_bytea,
pgp_sym_decrypt(decode(pg_crypt_text, 'hex'), $2) AS from_pg_crypt_text
FROM
test_encryption
WHERE
cuuid = $1
SQL,
[
$cuuid, $key
]
);
print "RES: <pre>" . Support::prAr($res) . "</pre><br>";
if ($res === false) {
echo "Failed to run query<br>";
} else {
if (hash_equals($string_hashed, $res['pg_digest_text'])) {
print "libsodium and pgcrypto hash match<br>";
}
if (hash_equals($string_hmac, $res['pg_hmac_text'])) {
print "libsodium and pgcrypto hash hmac match<br>";
}
// do compare for PHP and pgcrypto settings
$encryptedMessage_template = <<<TEXT
-----BEGIN PGP MESSAGE-----
{BASE64}
-----END PGP MESSAGE-----
TEXT;
$base64_string = base64_encode(hex2bin($res['pg_crypt_text']) ?: '');
$encryptedMessage = str_replace(
'{BASE64}',
$base64_string,
$encryptedMessage_template
);
try {
$literalMessage = OpenPGP::decryptMessage($encryptedMessage, passwords: [$key]);
$decrypted = $literalMessage->getLiteralData()->getData();
print "Pg decrypted PHP: " . $decrypted . "<br>";
if ($decrypted == $text_string) {
print "Decryption worked<br>";
}
} catch (\Exception $e) {
print "Error decrypting message: " . $e->getMessage() . "<br>";
}
}
print "</body></html>";
// __END__

View File

@@ -54,7 +54,7 @@ if (($dbh = $db->dbGetDbh()) instanceof \PgSql\Connection) {
print "NO DB HANDLER<br>"; print "NO DB HANDLER<br>";
} }
// REGEX for placeholder count // REGEX for placeholder count
print "Placeholder regex: <pre>" . CoreLibs\DB\Support\ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS . "</pre>"; print "Placeholder lookup regex: <pre>" . CoreLibs\DB\Support\ConvertPlaceholder::REGEX_LOOKUP_NUMBERED . "</pre>";
// turn on debug replace for placeholders // turn on debug replace for placeholders
$db->dbSetDebugReplacePlaceholder(true); $db->dbSetDebugReplacePlaceholder(true);
@@ -148,6 +148,7 @@ RETURNING
bigint_a, number_real, number_double, numeric_3, bigint_a, number_real, number_double, numeric_3,
uuid_var uuid_var
SQL; SQL;
print "Placeholders: <pre>" . print_r($db->dbGetQueryParamPlaceholders($query_insert), true) . "<pre>";
$status = $db->dbExecParams($query_insert, $query_params); $status = $db->dbExecParams($query_insert, $query_params);
echo "<b>*</b><br>"; echo "<b>*</b><br>";
echo "INSERT ALL COLUMN TYPES: " echo "INSERT ALL COLUMN TYPES: "
@@ -326,6 +327,7 @@ SQL,
) { ) {
print "RES: " . Support::prAr($res) . "<br>"; print "RES: " . Support::prAr($res) . "<br>";
} }
print "PL: " . Support::PrAr($db->dbGetPlaceholderConverted()) . "<br>";
print "ERROR: " . $db->dbGetLastError(true) . "<br>"; print "ERROR: " . $db->dbGetLastError(true) . "<br>";
print "</body></html>"; print "</body></html>";

View File

@@ -19,6 +19,7 @@ $LOG_FILE_ID = 'classTest-hash';
ob_end_flush(); ob_end_flush();
use CoreLibs\Create\Hash; use CoreLibs\Create\Hash;
use CoreLibs\Security\CreateKey;
$log = new CoreLibs\Logging\Logging([ $log = new CoreLibs\Logging\Logging([
'log_folder' => BASE . LOG, 'log_folder' => BASE . LOG,
@@ -38,28 +39,66 @@ print '<div><h1>' . $PAGE_NAME . '</h1></div>';
$to_crc = 'Some text block'; $to_crc = 'Some text block';
// static // static
print "S::__CRC32B: $to_crc: " . $hash_class::__crc32b($to_crc) . "<br>"; print "S::__CRC32B: $to_crc: " . Hash::__crc32b($to_crc) . "<br>";
print "S::__SHA1SHORT(off): $to_crc: " . $hash_class::__sha1short($to_crc) . "<br>"; // print "S::__SHA1SHORT(off): $to_crc: " . Hash::__sha1short($to_crc) . "<br>";
print "S::__SHA1SHORT(on): $to_crc: " . $hash_class::__sha1short($to_crc, true) . "<br>"; print "S::hashShort(__sha1Short replace): $to_crc: " . Hash::hashShort($to_crc) . "<br>";
print "S::__hash(d): " . $to_crc . "/" // print "S::__SHA1SHORT(on): $to_crc: " . Hash::__sha1short($to_crc, true) . "<br>";
. Hash::STANDARD_HASH_SHORT . ": " . $hash_class::__hash($to_crc) . "<br>"; print "S::sha1Short(__sha1Short replace): $to_crc: " . Hash::sha1Short($to_crc) . "<br>";
foreach (['adler32', 'fnv132', 'fnv1a32', 'joaat', 'sha512'] as $__hash_c) { // print "S::__hash(d): " . $to_crc . "/"
print "S::__hash($__hash_c): $to_crc: " . $hash_class::__hash($to_crc, $__hash_c) . "<br>"; // . Hash::STANDARD_HASH_SHORT . ": " . $hash_class::__hash($to_crc) . "<br>";
$to_crc_list = [
'Some text block',
'Some String Text',
'any string',
];
foreach ($to_crc_list as $__to_crc) {
foreach (['adler32', 'fnv132', 'fnv1a32', 'joaat', 'ripemd160', 'sha256', 'sha512'] as $__hash_c) {
print "Hash::hash($__hash_c): $__to_crc: " . Hash::hash($to_crc, $__hash_c) . "<br>";
}
} }
// static use // static use
print "U-S::__CRC32B: $to_crc: " . Hash::__crc32b($to_crc) . "<br>"; print "U-S::__CRC32B: $to_crc: " . Hash::__crc32b($to_crc) . "<br>";
echo "<hr>"; echo "<hr>";
$text = 'Some String Text'; $text = 'Some String Text';
// $text = 'any string';
$type = 'crc32b'; $type = 'crc32b';
print "Hash: " . $type . ": " . hash($type, $text) . "<br>"; print "Hash: " . $type . ": " . hash($type, $text) . "<br>";
print "Class: " . $type . ": " . Hash::__hash($text, $type) . "<br>"; // print "Class (old): " . $type . ": " . Hash::__hash($text, $type) . "<br>";
print "Class (new): " . $type . ": " . Hash::hash($text, $type) . "<br>";
echo "<hr>"; echo "<hr>";
print "<br>CURRENT STANDARD_HASH_SHORT: " . Hash::STANDARD_HASH_SHORT . "<br>"; print "CURRENT STANDARD_HASH_SHORT: " . Hash::STANDARD_HASH_SHORT . "<br>";
print "<br>CURRENT STANDARD_HASH_LONG: " . Hash::STANDARD_HASH_LONG . "<br>"; print "CURRENT STANDARD_HASH_LONG: " . Hash::STANDARD_HASH_LONG . "<br>";
print "HASH SHORT: " . $to_crc . ": " . Hash::__hash($to_crc) . "<br>"; print "CURRENT STANDARD_HASH: " . Hash::STANDARD_HASH . "<br>";
print "HASH LONG: " . $to_crc . ": " . Hash::__hashLong($to_crc) . "<br>"; print "HASH SHORT: " . $to_crc . ": " . Hash::hashShort($to_crc) . "<br>";
print "HASH LONG: " . $to_crc . ": " . Hash::hashLong($to_crc) . "<br>";
print "HASH DEFAULT: " . $to_crc . ": " . Hash::hashStd($to_crc) . "<br>";
echo "<hr>";
$key = CreateKey::generateRandomKey();
$key = "FIX KEY";
print "Secret Key: " . $key . "<br>";
print "HASHMAC DEFAULT (fix): " . $to_crc . ": " . Hash::hashHmac($to_crc, $key) . "<br>";
$key = CreateKey::generateRandomKey();
print "Secret Key: " . $key . "<br>";
print "HASHMAC DEFAULT (random): " . $to_crc . ": " . Hash::hashHmac($to_crc, $key) . "<br>";
echo "<hr>";
$hash_types = ['crc32b', 'sha256', 'invalid'];
foreach ($hash_types as $hash_type) {
echo "<b>Checking $hash_type:</b><br>";
if (Hash::isValidHashType($hash_type)) {
echo "hash type: $hash_type is valid<br>";
} else {
echo "hash type: $hash_type is INVALID<br>";
}
if (Hash::isValidHashHmacType($hash_type)) {
echo "hash hmac type: $hash_type is valid<br>";
} else {
echo "hash hmac type: $hash_type is INVALID<br>";
}
}
// print "UNIQU ID SHORT : " . Hash::__uniqId() . "<br>"; // print "UNIQU ID SHORT : " . Hash::__uniqId() . "<br>";
// print "UNIQU ID LONG : " . Hash::__uniqIdLong() . "<br>"; // print "UNIQU ID LONG : " . Hash::__uniqIdLong() . "<br>";

View File

@@ -95,6 +95,7 @@ $test_files = [
'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn', 'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn',
'class_test.db.single.php' => 'Class Test: DB single query tests', 'class_test.db.single.php' => 'Class Test: DB single query tests',
'class_test.db.convert-placeholder.php' => 'Class Test: DB convert placeholder', 'class_test.db.convert-placeholder.php' => 'Class Test: DB convert placeholder',
'class_test.db.encryption.php' => 'Class Test: DB pgcrypto',
'class_test.convert.colors.php' => 'Class Test: CONVERT COLORS', 'class_test.convert.colors.php' => 'Class Test: CONVERT COLORS',
'class_test.check.colors.php' => 'Class Test: CHECK COLORS', 'class_test.check.colors.php' => 'Class Test: CHECK COLORS',
'class_test.mime.php' => 'Class Test: MIME', 'class_test.mime.php' => 'Class Test: MIME',

View File

@@ -24,6 +24,7 @@
"egrajp/smarty-extended": "^5.4", "egrajp/smarty-extended": "^5.4",
"php": ">=8.1", "php": ">=8.1",
"gullevek/dotenv": "^2.0", "gullevek/dotenv": "^2.0",
"psr/log": "^2.0 || ^3.0" "psr/log": "^2.0 || ^3.0",
"php-privacy/openpgp": "^2.1"
} }
} }

View File

@@ -1024,8 +1024,12 @@ class Basic
*/ */
public function __sha1Short(string $string, bool $use_sha = false): string public function __sha1Short(string $string, bool $use_sha = false): string
{ {
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\Hash::__sha1Short()', E_USER_DEPRECATED); trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\Hash::sha1Short() or ::__crc32b()', E_USER_DEPRECATED);
return \CoreLibs\Create\Hash::__sha1Short($string, $use_sha); if ($use_sha) {
return \CoreLibs\Create\Hash::sha1Short($string);
} else {
return \CoreLibs\Create\Hash::__crc32b($string);
}
} }
/** /**
@@ -1040,8 +1044,8 @@ class Basic
*/ */
public function __hash(string $string, string $hash_type = 'adler32'): string public function __hash(string $string, string $hash_type = 'adler32'): string
{ {
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\Hash::__hash()', E_USER_DEPRECATED); trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Create\Hash::hash()', E_USER_DEPRECATED);
return \CoreLibs\Create\Hash::__hash($string, $hash_type); return \CoreLibs\Create\Hash::hash($string, $hash_type);
} }
// *** HASH FUNCTIONS END // *** HASH FUNCTIONS END

View File

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

View File

@@ -303,6 +303,8 @@ class IO
private string $query = ''; private string $query = '';
/** @var array<mixed> current params for query */ /** @var array<mixed> current params for query */
private array $params = []; private array $params = [];
/** @var string current hash build from query and params */
private string $query_hash = '';
// if we do have a convert call, store the convert data in here, else it will be empty // if we do have a convert call, store the convert data in here, else it will be empty
/** @var array{}|array{original:array{query:string,params:array<mixed>},type:''|'named'|'numbered'|'question_mark',found:int,matches:array<string>,params_lookup:array<mixed>,query:string,params:array<mixed>} */ /** @var array{}|array{original:array{query:string,params:array<mixed>},type:''|'named'|'numbered'|'question_mark',found:int,matches:array<string>,params_lookup:array<mixed>,query:string,params:array<mixed>} */
private array $placeholder_converted = []; private array $placeholder_converted = [];
@@ -1319,7 +1321,7 @@ class IO
*/ */
private function __dbCountQueryParams(string $query): int private function __dbCountQueryParams(string $query): int
{ {
return $this->db_functions->__dbCountQueryParams($query); return count($this->db_functions->__dbGetQueryParams($query));
} }
/** /**
@@ -1382,6 +1384,8 @@ class IO
$this->query = $query; $this->query = $query;
// current params // current params
$this->params = $params; $this->params = $params;
// empty on new
$this->query_hash = '';
// no query set // no query set
if (empty($this->query)) { if (empty($this->query)) {
$this->__dbError(11); $this->__dbError(11);
@@ -1441,7 +1445,7 @@ class IO
$this->returning_id = true; $this->returning_id = true;
} }
// import protection, hash needed // import protection, hash needed
$query_hash = $this->dbGetQueryHash($this->query, $this->params); $query_hash = $this->dbBuildQueryHash($this->query, $this->params);
// QUERY PARAMS: run query params check and rewrite // QUERY PARAMS: run query params check and rewrite
if ($this->dbGetConvertPlaceholder() === true) { if ($this->dbGetConvertPlaceholder() === true) {
try { try {
@@ -1475,7 +1479,8 @@ class IO
return false; return false;
} }
} }
// set query hash
$this->query_hash = $query_hash;
// $this->debug('DB IO', 'Q: ' . $this->query . ', RETURN: ' . $this->returning_id); // $this->debug('DB IO', 'Q: ' . $this->query . ', RETURN: ' . $this->returning_id);
// for DEBUG, only on first time ;) // for DEBUG, only on first time ;)
$this->__dbDebug( $this->__dbDebug(
@@ -1959,7 +1964,7 @@ class IO
{ {
// set start array // set start array
if ($query) { if ($query) {
$array = $this->cursor_ext[$this->dbGetQueryHash($query)] ?? []; $array = $this->cursor_ext[$this->dbBuildQueryHash($query)] ?? [];
} else { } else {
$array = $this->cursor_ext; $array = $this->cursor_ext;
} }
@@ -2361,7 +2366,7 @@ class IO
return false; return false;
} }
// create hash from query ... // create hash from query ...
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
// pre declare array // pre declare array
if (!isset($this->cursor_ext[$query_hash])) { if (!isset($this->cursor_ext[$query_hash])) {
$this->cursor_ext[$query_hash] = [ $this->cursor_ext[$query_hash] = [
@@ -2940,7 +2945,7 @@ class IO
public function dbCacheReset(string $query, array $params = []): bool public function dbCacheReset(string $query, array $params = []): bool
{ {
$this->__dbErrorReset(); $this->__dbErrorReset();
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
// clears cache for this query // clears cache for this query
if (empty($this->cursor_ext[$query_hash]['query'])) { if (empty($this->cursor_ext[$query_hash]['query'])) {
$this->__dbWarning(18, context: [ $this->__dbWarning(18, context: [
@@ -2982,7 +2987,7 @@ class IO
if ($query === null) { if ($query === null) {
return $this->cursor_ext; return $this->cursor_ext;
} }
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
if ( if (
!empty($this->cursor_ext) && !empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash]) isset($this->cursor_ext[$query_hash])
@@ -3012,7 +3017,7 @@ class IO
$this->__dbError(11); $this->__dbError(11);
return false; return false;
} }
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
if ( if (
!empty($this->cursor_ext) && !empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash]) isset($this->cursor_ext[$query_hash])
@@ -3038,7 +3043,7 @@ class IO
$this->__dbError(11); $this->__dbError(11);
return false; return false;
} }
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
if ( if (
!empty($this->cursor_ext) && !empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash]) isset($this->cursor_ext[$query_hash])
@@ -3064,7 +3069,7 @@ class IO
*/ */
public function dbResetQueryCalled(string $query, array $params = []): void public function dbResetQueryCalled(string $query, array $params = []): void
{ {
$this->query_called[$this->dbGetQueryHash($query, $params)] = 0; $this->query_called[$this->dbBuildQueryHash($query, $params)] = 0;
} }
/** /**
@@ -3077,7 +3082,7 @@ class IO
*/ */
public function dbGetQueryCalled(string $query, array $params = []): int public function dbGetQueryCalled(string $query, array $params = []): int
{ {
$query_hash = $this->dbGetQueryHash($query, $params); $query_hash = $this->dbBuildQueryHash($query, $params);
if (!empty($this->query_called[$query_hash])) { if (!empty($this->query_called[$query_hash])) {
return $this->query_called[$query_hash]; return $this->query_called[$query_hash];
} else { } else {
@@ -4046,7 +4051,7 @@ class IO
} }
/** /**
* Returns hash for query * Creates hash for query and parameters
* Hash is used in all internal storage systems for return data * Hash is used in all internal storage systems for return data
* *
* @param string $query The query to create the hash from * @param string $query The query to create the hash from
@@ -4054,9 +4059,9 @@ class IO
* data to create a unique call one, optional * data to create a unique call one, optional
* @return string Hash, as set by hash long * @return string Hash, as set by hash long
*/ */
public function dbGetQueryHash(string $query, array $params = []): string public function dbBuildQueryHash(string $query, array $params = []): string
{ {
return Hash::__hashLong( return Hash::hashLong(
$query . ( $query . (
$params !== [] ? $params !== [] ?
'#' . json_encode($params) : '' '#' . json_encode($params) : ''
@@ -4104,6 +4109,26 @@ class IO
$this->params = []; $this->params = [];
} }
/**
* get the current set query hash
*
* @return string Current Query hash
*/
public function dbGetQueryHash(): string
{
return $this->query_hash;
}
/**
* reset query hash
*
* @return void
*/
public function dbResetQueryHash(): void
{
$this->query_hash = '';
}
/** /**
* Returns the placeholder convert set or empty * Returns the placeholder convert set or empty
* *
@@ -4283,6 +4308,17 @@ class IO
return $this->field_names[$pos] ?? false; return $this->field_names[$pos] ?? false;
} }
/**
* get all the $ placeholders
*
* @param string $query
* @return array<string>
*/
public function dbGetQueryParamPlaceholders(string $query): array
{
return $this->db_functions->__dbGetQueryParams($query);
}
/** /**
* Return a field type for a field name or pos, * Return a field type for a field name or pos,
* will return false if field is not found in list * will return false if field is not found in list

View File

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

View File

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

View File

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

View File

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

View File

@@ -182,6 +182,7 @@ class EditUsers implements Interface\TableArraysInterface
'type' => 'text', 'type' => 'text',
'error_check' => 'unique|custom', 'error_check' => 'unique|custom',
'error_regex' => "/^[A-Za-z0-9]+$/", 'error_regex' => "/^[A-Za-z0-9]+$/",
'error_example' => "ABCdef123",
'emptynull' => 1,'min_edit_acl' => '100', 'emptynull' => 1,'min_edit_acl' => '100',
'min_show_acl' => '100', 'min_show_acl' => '100',
], ],