Compare commits

...

12 Commits

Author SHA1 Message Date
Clemens Schwaighofer
38b2ffe82a jsonConvertToArray with JSON_INVALID_UTF8_IGNORE does not work
json_decode JSON_INVALID_UTF8_IGNORE is not honoring any of those flags at the moment
2026-01-22 11:44:11 +09:00
Clemens Schwaighofer
6e547abccb Fix text in check runners 2026-01-22 11:31:01 +09:00
Clemens Schwaighofer
676af5e1a4 Add json validation and update tests
Also add removal of THROW flag for json decode to not throw an exception if if wanted

Add jquery 4.0.0 libs
2026-01-22 11:08:18 +09:00
Clemens Schwaighofer
118aacee28 Bug in RamdomKey::validateRandomKeyData when passing large character sets
The check for stringlength was done on the set key range in the class var and not the variable passed on in the method.
So if we cold call randomKey with a key character list and not the default list it failed with invalid max length
2026-01-14 15:07:38 +09:00
Clemens Schwaighofer
564e23ecd7 Update and fix Strings stringToTime()
Allow parsing of more flexible interval strings including
long names (day, hour, minute, second, millisecond),
negative values, no spaces between components, and
throwing exceptions on invalid input if requested.

The following types are now allowed
- d|day|days
- h|hour|hours
- m|min|mins|minute|minutes
- s|sec|secs|second|seconds
- ms|msec|msecs|msecond|mseconds|millis|millisec|millisecs|millisecond|milliseconds

Also fix the milisecond parsing that was done completly wrong
the milliseoncds where just added after a "." as decimals without converting them at all.
Now the value is divided by 1000 and added to the existing number, and as before only if ms exist

The negative check is now included in the main parse regex, so a second regex check is no longer necessary

Spaces between values, before or anywhere are now more flexible.

Exceptions are thrown if the regex cannot parse anything, or it returns only one master entry and no matches
2026-01-14 13:16:53 +09:00
Clemens Schwaighofer
2889012592 Add no dash character list allowed in Strings parseCharacterRanges function
Same rules with returning unique elements array
2026-01-14 10:49:14 +09:00
Clemens Schwaighofer
cd65604073 Add parseCharacterRanges function to Strings.php and tests
Parses character ranges like A-Z into individual characters and returns as an array
2026-01-14 10:36:09 +09:00
Clemens Schwaighofer
a3cf5f45f9 Fix function name in Create/RandomKey 2026-01-07 17:21:26 +09:00
Clemens Schwaighofer
6f3dacdec0 Remove double color settings entry in Phan config 2026-01-07 13:32:31 +09:00
Clemens Schwaighofer
2ab1ee90ef Add color output in phan 2026-01-07 13:17:54 +09:00
Clemens Schwaighofer
b8c0aff975 Update phive phars with correct version and update scripts to use both
new "-c" switch for all checking scripts to swtich to the composer version from the phive installed version

NOTE: phpstan plugins only work in the composer version.

Default is the phive version
2026-01-06 18:15:50 +09:00
Clemens Schwaighofer
c5fed66237 PHP 8.5 fixes and updates
All tested with PHP 8.4 and PHP 8.3 too

Major changes:
- cube root Math (cbrt) now throws InvalidArgumentException if NAN is returned instead of returning NAN
- Byte convert from string to int will throw errors if value is too large (\LengthException)
- new flag for returning string type but for this bcmath must be installed (\RuntimeException if no bcmath)
- Updated curl class and remove close handler as not needed and deprecated as of PHP 8.5
- Curl phpunit tests: convert string to JSON convert flow for return content check (to avoid per PHP version check)
- image close handler for ImageMagick removed as not needed and deprecated as of PHP 8.5
- updated all check calls too use phive tools if possible (except phpunit) and all scripts can have dynamic php version set
2026-01-06 15:55:47 +09:00
28 changed files with 10878 additions and 198 deletions

View File

@@ -26,8 +26,8 @@
use Phan\Config;
return [
// "target_php_version" => "8.2",
"minimum_target_php_version" => "8.2",
// "target_php_version" => "8.3",
"minimum_target_php_version" => "8.3",
// turn color on (-C)
"color_issue_messages_if_supported" => true,
// If true, missing properties will be created when

View File

@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<phive xmlns="https://phar.io/phive">
<phar name="phpunit" version="^10.3.5" installed="10.5.46" location="./tools/phpunit" copy="false"/>
<phar name="phpcbf" version="^3.7.2" installed="3.13.0" location="./tools/phpcbf" copy="false"/>
<phar name="phpcs" version="^3.10.3" installed="3.13.0" location="./tools/phpcs" copy="false"/>
<phar name="phpstan" version="^2.0" installed="2.1.17" location="./tools/phpstan" copy="false"/>
<phar name="phan" version="^5.4.3" installed="5.4.3" location="./tools/phan" copy="false"/>
<phar name="psalm" version="^5.15.0" installed="5.24.0" location="./tools/psalm" copy="false"/>
<phar name="phpunit" version="~9.6" installed="9.6.31" location="./tools/phpunit" copy="false"/>
<phar name="phpcbf" version="4" installed="4.0.1" location="./tools/phpcbf" copy="false"/>
<phar name="phpcs" version="4" installed="4.0.1" location="./tools/phpcs" copy="false"/>
<phar name="phpstan" version="^2.0" installed="2.1.33" location="./tools/phpstan" copy="false"/>
<phar name="phan" version="^5.4.3" installed="5.5.2" location="./tools/phan" copy="false"/>
<phar name="psalm" version="^5.26.1" installed="5.26.1" location="./tools/psalm" copy="false"/>
<phar name="phpdox" version="^0.12.0" installed="0.12.0" location="./tools/phpdox" copy="false"/>
<phar name="phpdocumentor" version="^3.4.2" installed="3.4.3" location="./tools/phpDocumentor" copy="false"/>
<phar name="php-cs-fixer" version="^3.34.1" installed="3.57.2" location="./tools/php-cs-fixer" copy="false"/>
<phar name="phpdocumentor" version="^3.4.2" installed="3.9.1" location="./tools/phpDocumentor" copy="false"/>
<phar name="php-cs-fixer" version="^3.34.1" installed="3.92.4" location="./tools/php-cs-fixer" copy="false"/>
</phive>

View File

@@ -1,6 +1,100 @@
base=$(pwd)"/";
#!/bin/env bash
function error() {
if [ -t 1 ]; then echo "[MAK] ERROR: $*" >&2; fi; exit 0;
}
usage() {
cat <<EOF
Usage: $(basename "${BASH_SOURCE[0]}") [-h | --help] [-p | --php VERSION] [-c | --composer]
Runs phan static analyzer.
If -p is not set, the default intalled PHP is used.
Available options:
-h, --help Print this help and exit
-p, --php VERSION Chose PHP version in the form of "N.N", if not found will exit
-c, --composer Use composer version and not the default phives bundle
EOF
exit
}
BASE_PATH=$(pwd)"/";
PHP_BIN_PATH=$(which php);
if [ -z "${PHP_BIN_PATH}" ]; then
echo "Cannot find php binary";
exit;
fi;
DEFAULT_PHP_VERSION=$(${PHP_BIN_PATH} -r "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;");
if [ -z "${DEFAULT_PHP_VERSION}" ]; then
echo "Cannot set default PHP version";
exit;
fi;
php_version="";
no_php_version=0;
use_composer=0;
while [ -n "${1-}" ]; do
case "${1}" in
-p | --php)
php_version="${2-}";
shift
;;
-c | --composer)
use_composer=1;
shift
;;
-h | --help)
usage
;;
# invalid option
-?*)
error "[!] Unknown option: '$1'."
;;
esac
shift;
done;
if [ -z "${php_version}" ]; then
php_version="${DEFAULT_PHP_VERSION}";
no_php_version=1;
fi;
php_bin="${PHP_BIN_PATH}${php_version}";
echo "Use PHP Version: ${php_version}";
if [ "${use_composer}" -eq 1 ]; then
echo "Use composer installed";
else
echo "Use phive installed";
fi;
if [ ! -f "${php_bin}" ]; then
echo "Set php ${php_bin} does not exist";
exit;
fi;
# must be run in ${base}
cd $base || exit;
#PHAN_DISABLE_XDEBUG_WARN=1;${base}tools/phan --progress-bar -C --analyze-twice
PHAN_DISABLE_XDEBUG_WARN=1;${base}vendor/bin/phan --progress-bar -C --analyze-twice
cd "$BASE_PATH" || exit;
export PHAN_DISABLE_XDEBUG_WARN=1;
PHAN_CALL=(
"${php_bin}"
);
if [ "${use_composer}" -eq 1 ]; then
PHAN_CALL+=("${BASE_PATH}vendor/bin/phan");
else
PHAN_CALL+=("${BASE_PATH}tools/phan");
fi;
PHAN_CALL+=(
"--progress-bar"
"-C"
"--analyze-twice"
)
"${PHAN_CALL[@]}";
if [ "${no_php_version}" -eq 0 ]; then
echo "*** CALLED WITH PHP ${php_bin} ***";
${php_bin} --version;
else
echo "Default PHP used: $(php --version)";
fi;
cd ~ || exit;
# __END__

View File

@@ -1,5 +1,92 @@
base=$(pwd)"/";
# must be run in ${base}
cd $base || exit;
${base}tools/phpstan;
#!/bin/env bash
function error() {
if [ -t 1 ]; then echo "[MAK] ERROR: $*" >&2; fi; exit 0;
}
usage() {
cat <<EOF
Usage: $(basename "${BASH_SOURCE[0]}") [-h] [-h | --help] [-p | --php VERSION] [-c | --composer]
Runs phan static analyzer.
If -p is not set, the default intalled PHP is used.
Available options:
-h, --help Print this help and exit
-p, --php VERSION Chose PHP version in the form of "N.N", if not found will exit
-c, --composer Use composer version and not the default phives bundle
EOF
exit
}
BASE_PATH=$(pwd)"/";
PHP_BIN_PATH=$(which php);
if [ -z "${PHP_BIN_PATH}" ]; then
echo "Cannot find php binary";
exit;
fi;
DEFAULT_PHP_VERSION=$(${PHP_BIN_PATH} -r "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;");
if [ -z "${DEFAULT_PHP_VERSION}" ]; then
echo "Cannot set default PHP version";
exit;
fi;
php_version="";
no_php_version=0;
use_composer=0;
while [ -n "${1-}" ]; do
case "${1}" in
-p | --php)
php_version="${2-}";
shift
;;
-c | --composer)
use_composer=1;
shift
;;
-h | --help)
usage
;;
# invalid option
-?*)
error "[!] Unknown option: '$1'."
;;
esac
shift;
done;
if [ -z "${php_version}" ]; then
php_version="${DEFAULT_PHP_VERSION}";
no_php_version=1;
fi;
php_bin="${PHP_BIN_PATH}${php_version}";
echo "Use PHP Version: ${php_version}";
if [ "${use_composer}" -eq 1 ]; then
echo "Use composer installed";
else
echo "Use phive installed";
fi;
if [ ! -f "${php_bin}" ]; then
echo "Set php ${php_bin} does not exist";
exit;
fi;
BASE_PATH=$(pwd)"/";
cd "$BASE_PATH" || exit;
PHPSTAN_CALL=(
"${php_bin}"
);
if [ "${use_composer}" -eq 1 ]; then
PHPSTAN_CALL+=("${BASE_PATH}vendor/bin/phpstan");
else
PHPSTAN_CALL+=("${BASE_PATH}tools/phpstan");
fi;
"${PHPSTAN_CALL[@]}";
if [ "${no_php_version}" -eq 0 ]; then
echo "*** CALLED WITH PHP ${php_bin} ***";
${php_bin} --version;
else
echo "Default PHP used: $(php --version)";
fi;
cd ~ || exit;

View File

@@ -6,7 +6,7 @@ function error() {
usage() {
cat <<EOF
Usage: $(basename "${BASH_SOURCE[0]}") [-h] [-t] [-v] [-p VERSION]
Usage: $(basename "${BASH_SOURCE[0]}") [-h | --help] [-p | --php VERSION] [-c | --composer] [-t | --testdox] [-v | --verbose]
Runs all the PHP unit tests.
@@ -15,8 +15,9 @@ If -p is not set, the default intalled PHP is used.
Available options:
-h, --help Print this help and exit
-t, --testdox Enable testdox output for phpunit
-t, --testdox Enable testdox output for PHPunit
-v, --verbose Enable verbose output for PHPunit
-c, --composer Use composer version and not the default phives bundle
-p, --php VERSION Chose PHP version in the form of "N.N", if not found will exit
EOF
exit
@@ -45,6 +46,7 @@ opt_testdox="";
opt_verbose="";
php_version="";
no_php_version=0;
use_composer=0;
while [ -n "${1-}" ]; do
case "${1}" in
-t | --testdox)
@@ -53,6 +55,10 @@ while [ -n "${1-}" ]; do
-v | --verbose)
opt_verbose="--verbose";
;;
-c | --composer)
use_composer=1;
shift
;;
-p | --php)
php_version="${2-}";
shift
@@ -74,21 +80,38 @@ if [ -z "${php_version}" ]; then
fi;
php_bin="${PHP_BIN_PATH}${php_version}";
echo "Use PHP Version: ${php_version}";
if [ "${use_composer}" -eq 1 ]; then
echo "Use composer installed";
else
echo "Use phive installed";
fi;
if [ ! -f "${php_bin}" ]; then
echo "Set php ${php_bin} does not exist";
exit;
fi;
php_bin="${php_bin} ";
# Note 4dev/tests/bootstrap.php has to be set as bootstrap file in phpunit.xml
phpunit_call="${php_bin}${BASE_PATH}vendor/bin/phpunit ${opt_testdox} ${opt_verbose} -c ${PHPUNIT_CONFIG} ${BASE_PATH}4dev/tests/";
${phpunit_call};
PHPUNIT_CALL=(
"${php_bin}"
);
if [ "${use_composer}" -eq 1 ]; then
PHPUNIT_CALL+=("${BASE_PATH}vendor/bin/phpunit");
else
PHPUNIT_CALL+=("${BASE_PATH}tools/phpunit");
fi;
PHPUNIT_CALL+=(
"${opt_testdox}"
"${opt_verbose}"
"-c" "${PHPUNIT_CONFIG}"
"${BASE_PATH}4dev/tests/"
);
"${PHPUNIT_CALL[@]}" || exit;
echo -e "\nPHPUnit Config: ${PHPUNIT_CONFIG}";
if [ "${no_php_version}" -eq 0 ]; then
echo "CALLED WITH PHP: ${php_bin}$(${php_bin} --version)";
echo "*** CALLED WITH PHP ${php_bin} ***";
${php_bin} --version;
else
echo "Default PHP used: $(php --version)";
fi;

View File

@@ -490,11 +490,11 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
],
'micro interval with microtime' => [
'18999d 0h 38m 10s 1235ms',
1641515890.1235,
1641515891.235,
],
'micro interval with microtime' => [
'18999d 0h 38m 10s 1234567890ms',
1641515890.1234567,
1642750457.89,
],
'negative interval no microtime' => [
'-18999d 0h 38m 10s',
@@ -503,23 +503,246 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
// short for mini tests
'microtime only' => [
'0s 1235ms',
0.1235,
1.235,
],
'seconds only' => [
'30s 1235ms',
30.1235,
31.235,
],
'minutes only' => [
'1m 30s 1235ms',
90.1235,
91.235,
],
'hours only' => [
'1h 1m 30s 1235ms',
3690.1235,
3691.235,
],
'days only' => [
'1d 1h 1m 30s 1235ms',
90090.1235,
90091.235,
],
'days only with long name' => [
'1day 1hour 1min 30second 1235millisecond',
90091.235,
],
// Test day variations
'day singular' => [
'5day',
432000,
],
'days plural' => [
'3days',
259200,
],
'days with space' => [
'2days 5h',
190800,
],
'day without space' => [
'1day1h',
90000,
],
// Test hour variations
'hour singular' => [
'2hour',
7200,
],
'hours plural' => [
'4hours',
14400,
],
'hours with space' => [
'3hours 30m',
12600,
],
'hour without space' => [
'1hour30m',
5400,
],
// Test minute variations
'min short' => [
'45min',
2700,
],
'minute singular' => [
'1minute',
60,
],
'minutes plural' => [
'10minutes',
600,
],
'minutes with space' => [
'5minutes 20s',
320,
],
'min without space' => [
'2min30s',
150,
],
// Test second variations
'sec short' => [
'30sec',
30,
],
'second singular' => [
'1second',
1,
],
'seconds plural' => [
'45seconds',
45,
],
'seconds with space' => [
'15seconds 500ms',
15.5,
],
'sec without space' => [
'10sec250ms',
10.25,
],
// Test millisecond variations
'ms short' => [
'500ms',
0.5,
],
'millis short' => [
'250millis',
0.25,
],
'millisec medium singular' => [
'250millisec',
0.25,
],
'millisecs medium plural' => [
'250millisecs',
0.25,
],
'misec medium singular' => [
'250millisec',
0.25,
],
'msecs medium plural' => [
'250millisecs',
0.25,
],
'millisecond long singular' => [
'1millisecond',
0.001,
],
'milliseconds long plural' => [
'999milliseconds',
0.999,
],
// Test negative values
'negative days' => [
'-5d',
-432000,
],
'negative hours' => [
'-3h',
-10800,
],
'negative minutes' => [
'-45m',
-2700,
],
'negative seconds' => [
'-30s',
-30,
],
'negative milliseconds' => [
'-500ms',
-0.5,
],
'negative complex' => [
'-2days 3hours 15minutes 30seconds 250milliseconds',
-184530.25,
],
// Test combined formats
'all components short' => [
'1d 2h 3m 4s 5ms',
93784.005,
],
'all components long' => [
'2days 3hours 4minutes 5seconds 678milliseconds',
183845.678,
],
'mixed short and long' => [
'1day 2h 3minutes 4sec 100ms',
93784.1,
],
'no spaces between components' => [
'1d2h3m4s5ms',
93784.005,
],
'only days and milliseconds' => [
'5d 123ms',
432000.123,
],
'only hours and seconds' => [
'2h 45s',
7245,
],
'only minutes and milliseconds' => [
'30m 500ms',
1800.5,
],
// Test zero values
'zero seconds' => [
'0s',
0,
],
'zero with milliseconds' => [
'0s 123ms',
0.123,
],
// Test large values
'large days' => [
'365days',
31536000,
],
'large hours' => [
'48hours',
172800,
],
'large minutes' => [
'1440minutes',
86400,
],
'large seconds' => [
'86400seconds',
86400,
],
// Test edge cases with spaces
'extra spaces' => [
'1d 2h 3m 4s 5ms',
93784.005,
],
'mixed spaces and no spaces' => [
'1d 2h3m 4s5ms',
93784.005,
],
// Test single component each
'only days short' => [
'7d',
604800,
],
'only hours short' => [
'12h',
43200,
],
'only minutes short' => [
'90m',
5400,
],
'only seconds short' => [
'120s',
120,
],
'only milliseconds short' => [
'1500ms',
1.5,
],
'already set' => [
1641515890,
@@ -529,10 +752,18 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
'xyz',
'xyz',
],
'empty data' => [
' ',
' ',
],
'out of bound data' => [
'99999999999999999999d',
8.64E+24
],
'spaces inbetween' => [
' - 9 d 2h 58minutes 35 seconds 123 ms ',
-788315.123,
]
];
}
@@ -555,6 +786,36 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
);
}
/**
* Undocumented function
*
* @covers ::stringToTime
* @testdox stringToTime invalid input will throw exception if requested
*
* @return void
*/
public function testStringToTimeException(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessageMatches("/^Invalid time string format, cannot parse: /");
\CoreLibs\Combined\DateTime::stringToTime('1x 2y 3z', true);
}
/**
* Undocumented function
*
* @covers ::stringToTime
* @testdox stringToTime empty input will throw exception if requested
*
* @return void
*/
public function testStringToTimeExceptionEmpty(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessageMatches("/^Invalid time string format, no interval value found: /");
\CoreLibs\Combined\DateTime::stringToTime(' ', true);
}
/**
* Undocumented function
*

View File

@@ -123,47 +123,6 @@ final class CoreLibsConvertByteTest extends TestCase
];
}
/**
* Undocumented function
*
* @return array
*/
public function byteStringProvider(): array
{
return [
'negative number' => [
0 => '-117.42 MB',
1 => -123123794,
2 => -117420000,
],
'megabyte' => [
0 => '242.98 MB',
1 => 254782996,
2 => 242980000
],
'megabyte si' => [
0 => '254.78 MiB',
1 => 267156193,
2 => 254780000
],
'petabyte' => [
0 => '1 EiB',
1 => 1152921504606846976,
2 => 1000000000000000000,
],
'max int' => [
0 => '8 EB',
1 => -9223372036854775807 - 1,
2 => 8000000000000000000,
],
'exabyte, overflow' => [
0 => '867.36EB',
1 => 3873816255479021568,
2 => 363028535651074048,
]
];
}
/**
* Undocumented function
*
@@ -180,7 +139,7 @@ final class CoreLibsConvertByteTest extends TestCase
* @return void
*/
public function testHumanReadableByteFormat(
$input,
string|int|float $input,
string $expected,
string $expected_si,
string $expected_no_space,
@@ -217,6 +176,73 @@ final class CoreLibsConvertByteTest extends TestCase
);
}
/**
* Undocumented function
*
* @return array
*/
public function byteStringProvider(): array
{
return [
'negative number' => [
0 => '-117.42 MB',
1 => -123123794,
2 => -117420000,
3 => "-123123793",
4 => "-117420000",
5 => null,
],
'megabyte' => [
0 => '242.98 MB',
1 => 254782996,
2 => 242980000,
3 => "254782996",
4 => "242980000",
5 => null,
],
'megabyte si' => [
0 => '254.78 MiB',
1 => 267156193,
2 => 254780000,
3 => "267156193",
4 => "254780000",
5 => null,
],
'petabyte' => [
0 => '1 EiB',
1 => 1152921504606846976,
2 => 1000000000000000000,
3 => "1152921504606846976",
4 => "1000000000000000000",
5 => null,
],
'max int' => [
0 => '8 EB',
1 => 0,
2 => 0,
3 => "9223372036854775808",
4 => "8000000000000000000",
5 => \LengthException::class,
],
'exabyte, overflow' => [
0 => '867.36EB',
1 => 0,
2 => 0,
3 => "999997996235794808832",
4 => "867360000000000000000",
5 => \LengthException::class,
],
'huge exabyte, overflow' => [
0 => '1000EB',
1 => 0,
2 => 0,
3 => "1152921504606846976000",
4 => "1000000000000000000000",
5 => \LengthException::class,
],
];
}
/**
* Undocumented function
*
@@ -227,10 +253,22 @@ final class CoreLibsConvertByteTest extends TestCase
* @param string|int|float $input
* @param string|int|float $expected
* @param string|int|float $expected_si
* @param string|int|float $expected_string
* @param string|int|float $expected_string_si
* @param ?string $exception
* @return void
*/
public function testStringByteFormat($input, $expected, $expected_si): void
{
public function testStringByteFormat(
string|int|float $input,
string|int|float $expected,
string|int|float $expected_si,
string|int|float $expected_string,
string|int|float $expected_string_si,
?string $exception
): void {
if ($exception !== null) {
$this->expectException($exception);
}
$this->assertEquals(
$expected,
\CoreLibs\Convert\Byte::stringByteFormat($input)
@@ -239,6 +277,17 @@ final class CoreLibsConvertByteTest extends TestCase
$expected_si,
\CoreLibs\Convert\Byte::stringByteFormat($input, \CoreLibs\Convert\Byte::BYTE_FORMAT_SI)
);
$this->assertEquals(
$expected_string,
\CoreLibs\Convert\Byte::stringByteFormat($input, \CoreLibs\Convert\Byte::RETURN_AS_STRING)
);
$this->assertEquals(
$expected_string_si,
\CoreLibs\Convert\Byte::stringByteFormat(
$input,
\CoreLibs\Convert\Byte::BYTE_FORMAT_SI | \CoreLibs\Convert\Byte::RETURN_AS_STRING
)
);
}
/**

View File

@@ -164,6 +164,51 @@ final class CoreLibsConvertJsonTest extends TestCase
);
}
/**
* test with flags
*
* @covers ::jsonConvertToArray
* @testdox jsonConvertToArray flag test, if flag is used
*
* @return void
*/
public function testJsonConvertToArrayWithFlags(): void
{
$input = '{"valid":"json","invalid":"\xB1\x31"}';
/* $expected_without_flag = [
'valid' => 'json'
];
$expected_with_flag = [
'valid' => 'json',
'invalid' => "\xB1\x31"
]; */
// no idea why in both it throws an erro
$expected_without_flag = [];
$expected_with_flag = [];
$this->assertEquals(
$expected_without_flag,
\CoreLibs\Convert\Json::jsonConvertToArray($input)
);
$this->assertEquals(
$expected_with_flag,
\CoreLibs\Convert\Json::jsonConvertToArray($input, flags:JSON_INVALID_UTF8_IGNORE)
);
}
public function testJsonConvertToArrayRemoveThrowFlag(): void
{
$input = '{"valid":"json","invalid":"\xB1\x31"}';
// show NOT throw an exception
try {
$this->assertEquals(
[],
\CoreLibs\Convert\Json::jsonConvertToArray($input, flags:JSON_THROW_ON_ERROR)
);
} catch (\Exception $e) {
$this->fail('Exception was thrown despite flag removal');
}
}
/**
* test json error states
*
@@ -189,6 +234,49 @@ final class CoreLibsConvertJsonTest extends TestCase
);
}
/**
* test json error states
*
* @covers ::jsonValidate
* @dataProvider jsonErrorProvider
* @testdox jsonValidate $input will be $expected_i/$expected_s [$_dataName]
*
* @param string|null $input
* @param int $expected_i
* @param string $expected_s
* @return void
*/
public function testJsonValidateGetLastError(?string $input, int $expected_i, string $expected_s): void
{
\CoreLibs\Convert\Json::jsonValidate($input);
$this->assertEquals(
$expected_i,
\CoreLibs\Convert\Json::jsonGetLastError()
);
$this->assertEquals(
$expected_s,
\CoreLibs\Convert\Json::jsonGetLastError(true)
);
}
/**
* test json validation
*
* @covers ::jsonValidate
* @testdox jsonValidate test valid and invalid json
*
* @return void
*/
public function testJsonValidate(): void
{
$this->assertTrue(
\CoreLibs\Convert\Json::jsonValidate('{"valid": "json"}')
);
$this->assertFalse(
\CoreLibs\Convert\Json::jsonValidate('not valid json')
);
}
/**
* Undocumented function
*

View File

@@ -122,9 +122,9 @@ final class CoreLibsConvertMathTest extends TestCase
public function providerCbrt(): array
{
return [
'cube root of 2' => [2, 1.25992, 5],
'cube root of 3' => [3, 1.44225, 5],
'cube root of -1' => [-1, 'NAN', 0],
'cube root of 2' => [2, 1.25992, 5, null],
'cube root of 3' => [3, 1.44225, 5, null],
'cube root of -1' => [-1, 'NAN', 0, \InvalidArgumentException::class],
];
}
@@ -138,10 +138,14 @@ final class CoreLibsConvertMathTest extends TestCase
* @param float|int $number
* @param float $expected
* @param int $round_to
* @param ?string $exception
* @return void
*/
public function testCbrt(float|int $number, float|string $expected, int $round_to): void
public function testCbrt(float|int $number, float|string $expected, int $round_to, ?string $exception): void
{
if ($exception !== null) {
$this->expectException($exception);
}
$this->assertEquals(
$expected,
round(\CoreLibs\Convert\Math::cbrt($number), $round_to)

View File

@@ -698,6 +698,89 @@ final class CoreLibsConvertStringsTest extends TestCase
'Cannot match last preg error string'
);
}
/**
* Undocumented function
*
* @return array
*/
public function parseCharacterRangesProvider(): array
{
return [
'simple a-z' => [
['a-z'],
implode('', range('a', 'z')),
null,
],
'simple A-Z' => [
['A-Z'],
implode('', range('A', 'Z')),
null,
],
'simple 0-9' => [
['0-9'],
implode('', range('0', '9')),
null,
],
'mixed ranges' => [
['a-c', 'X-Z', '3-5'],
'abcXYZ345',
null,
],
'reverse ranges' => [
['z-a'],
'abcdefghijklmnopqrstuvwxyz',
null,
],
'overlapping ranges' => [
['a-f', 'd-j'],
'abcdefghij',
null,
],
'mixed valid and overlap ranges' => [
['a-f', 'z-a', '0-3'],
'abcdefghijklmnopqrstuvwxyz0123',
null,
],
'range without dashes' => [
['abcddfff'],
'abcdf',
null,
],
'invalid ranges' => [
['a-あ', 'A-あ', '0-あ'],
'',
\InvalidArgumentException::class,
],
];
}
/**
* Undocumented function
*
* @covers ::parseCharacterRanges
* @dataProvider parseCharacterRangesProvider
* @testdox parseCharacterRanges $input to $expected [$_dataName]
*
* @param array $input
* @param string $expected
* @param string|null $expected_exception
* @return void
*/
public function testParseCharacterRanges(
array $input,
string $expected,
?string $expected_exception
): void {
if ($expected_exception !== null) {
$this->expectException($expected_exception);
}
$this->assertEquals(
$expected,
implode('', \CoreLibs\Convert\Strings::parseCharacterRanges(implode('', $input)))
);
}
}
// __END__

View File

@@ -59,8 +59,6 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase
continue;
}
$this->url_basic = $url;
// split out the last / part for url set test
curl_close($handle);
// print "Open: $url\n";
break;
}
@@ -969,76 +967,77 @@ final class CoreLibsUrlRequestsCurlTest extends TestCase
"query" => ["foo-get" => "bar"]
]);
$this->assertEquals("200", $response["code"], "multi call: get response code not matching");
if (PHP_VERSION_ID >= 80400) {
$this->assertEquals(
'{"HEADERS":{"HTTP_HOST":"soba.egplusww.jp",'
. '"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1","HTTP_FIRST_CALL":"get",'
. '"HTTP_ACCEPT":"*\/*"},"REQUEST_TYPE":"GET","PARAMS":{"foo-get":"bar"},"BODY":null}',
$response['content'],
'multi call: get content not matching'
);
} else {
$this->assertEquals(
'{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",'
. '"HTTP_FIRST_CALL":"get","HTTP_ACCEPT":"*\/*",'
. '"HTTP_HOST":"soba.egplusww.jp"},'
. '"REQUEST_TYPE":"GET",'
. '"PARAMS":{"foo-get":"bar"},"BODY":null}',
$response['content'],
'multi call: get content not matching'
);
}
$request_expected = json_decode(
<<<JSON
{
"HEADERS":{
"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",
"HTTP_FIRST_CALL":"get","HTTP_ACCEPT":"*\/*",
"HTTP_HOST":"soba.egplusww.jp"
},
"REQUEST_TYPE":"GET",
"PARAMS":{"foo-get":"bar"},"BODY":null
}
JSON,
true
);
$this->assertEquals(
$request_expected,
json_decode($response['content'], true),
'multi call: get content not matching'
);
// post
$response = $curl->post($this->url_basic, [
"headers" => ["second-call" => "post"],
"body" => ["foo-post" => "baz"]
]);
$this->assertEquals("200", $response["code"], "multi call: post response code not matching");
if (PHP_VERSION_ID >= 80400) {
$this->assertEquals(
'{"HEADERS":{"HTTP_HOST":"soba.egplusww.jp",'
. '"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",'
. '"HTTP_SECOND_CALL":"post","HTTP_ACCEPT":"*\/*"},'
. '"REQUEST_TYPE":"POST","PARAMS":[],"BODY":{"foo-post":"baz"}}',
$response['content'],
'multi call: post content not matching'
);
} else {
$this->assertEquals(
'{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",'
. '"HTTP_SECOND_CALL":"post","HTTP_ACCEPT":"*\/*",'
. '"HTTP_HOST":"soba.egplusww.jp"},'
. '"REQUEST_TYPE":"POST",'
. '"PARAMS":[],"BODY":{"foo-post":"baz"}}',
$response['content'],
'multi call: post content not matching'
);
}
$request_expected = json_decode(
<<<JSON
{
"HEADERS":{
"HTTP_HOST":"soba.egplusww.jp",
"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",
"HTTP_SECOND_CALL":"post",
"HTTP_ACCEPT":"*\/*"
},
"REQUEST_TYPE":"POST",
"PARAMS":[],
"BODY":{"foo-post":"baz"}
}
JSON,
true
);
$this->assertEquals(
$request_expected,
json_decode($response['content'], true),
'multi call: post content not matching'
);
// delete
$response = $curl->delete($this->url_basic, [
"headers" => ["third-call" => "delete"],
]);
$this->assertEquals("200", $response["code"], "multi call: delete response code not matching");
if (PHP_VERSION_ID >= 80400) {
$this->assertEquals(
'{"HEADERS":{"HTTP_HOST":"soba.egplusww.jp",'
. '"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",'
. '"HTTP_THIRD_CALL":"delete","HTTP_ACCEPT":"*\/*"},'
. '"REQUEST_TYPE":"DELETE","PARAMS":[],"BODY":[]}',
$response['content'],
'multi call: delete content not matching'
);
} else {
$this->assertEquals(
'{"HEADERS":{"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",'
. '"HTTP_THIRD_CALL":"delete","HTTP_ACCEPT":"*\/*",'
. '"HTTP_HOST":"soba.egplusww.jp"},'
. '"REQUEST_TYPE":"DELETE",'
. '"PARAMS":[],"BODY":[]}',
$response['content'],
'multi call: delete content not matching'
);
}
$request_expected = json_decode(
<<<JSON
{
"HEADERS":{
"HTTP_HOST":"soba.egplusww.jp",
"HTTP_USER_AGENT":"CoreLibsUrlRequestCurl\/1",
"HTTP_THIRD_CALL":"delete","HTTP_ACCEPT":"*\/*"
},
"REQUEST_TYPE":"DELETE",
"PARAMS":[],
"BODY":[]
}
JSON,
true
);
$this->assertEquals(
$request_expected,
json_decode($response['content'], true),
'multi call: delete content not matching'
);
}
// MARK: auth header set via config

View File

@@ -74,9 +74,21 @@ foreach ($bytes as $byte) {
print '<div style="width: 35%; text-align: right; padding-right: 2px;">';
print "(" . number_format($byte) . "/" . $byte . ") bytes :";
$_bytes = Byte::humanReadableByteFormat($byte);
print '</div><div style="width: 10%;">' . $_bytes;
print '</div><div style="width: 10%;">';
print Byte::stringByteFormat($_bytes);
print '</div>';
print '<div style="width: 10%;">' . $_bytes . '</div>';
print '<div style="width: 40%;">';
try {
print Byte::stringByteFormat($_bytes);
} catch (\LengthException $e) {
print "LengthException 1: " . $e->getMessage();
try {
print "<br>S: " . Byte::stringByteFormat($_bytes, Byte::RETURN_AS_STRING);
} catch (\LengthException $e) {
print "LengthException 2: " . $e->getMessage();
} catch (\RuntimeException $e) {
print "RuntimeException 1: " . $e->getMessage();
}
}
print "</div>";
//
print "</div>";
@@ -87,13 +99,85 @@ foreach ($bytes as $byte) {
print "bytes [si]:";
$_bytes = Byte::humanReadableByteFormat($byte, Byte::BYTE_FORMAT_SI);
print '</div><div style="width: 10%;">' . $_bytes;
print '</div><div style="width: 10%;">';
print Byte::stringByteFormat($_bytes);
print '</div><div style="width: 40%;">';
try {
print Byte::stringByteFormat($_bytes);
} catch (\LengthException $e) {
print "LengthException A: " . $e->getMessage();
try {
print "<br>Ssi: " . Byte::stringByteFormat($_bytes, Byte::RETURN_AS_STRING | Byte::BYTE_FORMAT_SI);
} catch (\LengthException $e) {
print "LengthException B: " . $e->getMessage();
} catch (\RuntimeException $e) {
print "RuntimeException A: " . $e->getMessage();
}
}
print "</div>";
//
print "</div>";
}
$string_bytes = [
'-117.42 MB',
'242.98 MB',
'254.78 MiB',
'1 EiB',
'8 EB',
'867.36EB',
'1000EB',
'10000EB',
];
print "<b>BYTE STRING TO BYTES TESTS</b><br>";
foreach ($string_bytes as $string) {
print '<div style="display: flex; border-bottom: 1px dashed gray;">';
//
print '<div style="width: 35%; text-align: right; padding-right: 2px;">';
print "string byte ($string) to bytes :";
try {
$_bytes = Byte::stringByteFormat($string);
} catch (\LengthException $e) {
print "<br>LengthException A: " . $e->getMessage();
$_bytes = 0;
}
try {
$_bytes_string = Byte::stringByteFormat($string, Byte::RETURN_AS_STRING);
} catch (\LengthException $e) {
print "<br>LengthException B: " . $e->getMessage();
$_bytes_string = '';
} catch (\RuntimeException $e) {
print "<br>RuntimeException: " . $e->getMessage();
$_bytes_string = '';
}
try {
$_bytes_si = Byte::stringByteFormat($string, Byte::BYTE_FORMAT_SI);
} catch (\LengthException $e) {
print "<br>LengthException A: " . $e->getMessage();
$_bytes_si = 0;
}
try {
$_bytes_string_si = Byte::stringByteFormat($string, Byte::RETURN_AS_STRING | Byte::BYTE_FORMAT_SI);
} catch (\LengthException $e) {
print "<br>LengthException B: " . $e->getMessage();
$_bytes_string_si = '';
} catch (\RuntimeException $e) {
print "<br>RuntimeException: " . $e->getMessage();
$_bytes_string_si = '';
}
print '</div>';
print '<div style="width: 20%;">'
. "F:" . number_format((int)$_bytes)
. '<br>B: ' . $_bytes
. '<br>S: ' . $_bytes_string
. "<br>Fsi:" . number_format((int)$_bytes_si)
. '<br>Bsi: ' . $_bytes_si
. '<br>Ssi: ' . $_bytes_string_si;
print '</div>';
print '<div style="width: 10%;">';
print "B: " . Byte::humanReadableByteFormat($_bytes) . "<br>";
print "Bsi: " . Byte::humanReadableByteFormat($_bytes_si, Byte::BYTE_FORMAT_SI);
print "</div>";
print "</div>";
}
print "</body></html>";
// __END__

View File

@@ -225,6 +225,36 @@ foreach ($intervals as $interval) {
print "STRINGTOTIME: $reverse_interval: " . DateTime::stringToTime($reverse_interval) . "<br>";
}
print "<hr>";
$interval_strings = [
'10d 5h 30m 15s 123456ms',
'18999d 0h 38m 10s 1235ms',
'18999 d 0 h 38 m 10s 1235ms',
'-2h 15m 5s',
'45s 500ms',
'0s',
'0ms',
'1s 5ms',
'1s 50ms',
'1s 500ms',
'1s 5000ms',
'10day 5hour 30min 15sec 123456millis',
'10day 5hour 30min 15sec 123456millisec',
'10day 5hour 30min 15sec 123456msec',
'-2days 3hours 15minutes 30seconds 250milliseconds',
'',
' ',
'invalid',
];
foreach ($interval_strings as $interval_string) {
print "STRINGTOTIME: $interval_string: " . DateTime::stringToTime($interval_string) . "<br>";
try {
// test exception
DateTime::stringToTime($interval_string, throw_exception:true);
} catch (\InvalidArgumentException $e) {
print "ERROR: " . $e->getMessage() . "<br><pre>" . $e . "</pre><br>";
}
}
print "<hr>";
$check_dates = [
'2021-05-01',
'2021-05-40'

View File

@@ -46,6 +46,7 @@ $json = '["f: {b"""ar}]';
$output = Json::jsonConvertToArray($json);
print "S::E-JSON: $json: " . DgS::printAr($output) . "<br>";
print "S::E-JSON ERROR: " . Json::jsonGetLastError() . ": " . Json::jsonGetLastError(true) . "<br>";
print "S::E Validate: " . Json::jsonValidate($json) . ": " . Json::jsonGetLastError(true) . "<br>";
// direct
$json = '{"direct": "static function call"}';
@@ -58,6 +59,22 @@ $output = $json_class::jsonConvertToArray($json);
print "J/S::E-JSON: $json: " . DgS::printAr($output) . "<br>";
print "J/S::E-JSON ERROR: " . $json_class::jsonGetLastError() . ": " . $json_class::jsonGetLastError(true) . "<br>";
$json = '{"valid":"json","invalid":"\xB1\x31"}';
$json = '{"valid":"json","invalid":"abc\x80def"}';
$output_no_flag = Json::jsonConvertToArray($json);
print "No Flag JSON: $json: " . DgS::printAr($output_no_flag) . "<br>";
print "No Flag JSON ERROR: " . Json::jsonGetLastError() . ": " . Json::jsonGetLastError(true) . "<br>";
$output_flag = Json::jsonConvertToArray($json, flags:JSON_INVALID_UTF8_IGNORE);
print "No Flag JSON: $json: " . DgS::printAr($output_flag) . "<br>";
print "No Flag JSON ERROR: " . Json::jsonGetLastError() . ": " . Json::jsonGetLastError(true) . "<br>";
$output_raw = json_decode($json, true, flags:JSON_INVALID_UTF8_IGNORE);
print "No Flag JSON RAW (F-1): $json: " . DgS::printAr($output_raw) . "<br>";
$output_raw = json_decode($json, true, flags:JSON_INVALID_UTF8_SUBSTITUTE);
print "No Flag JSON RAW (F-2): $json: " . DgS::printAr($output_raw) . "<br>";
$output_raw = json_decode($json, true);
print "No Flag JSON RAW: $json: " . DgS::printAr($output_raw) . "<br>";
// $json = '{"foo": "bar"}';
// $output = Jason::jsonConvertToArray($json);
// print "S::JSON: $json: " . DgS::printAr($output) . "<br>";

View File

@@ -34,6 +34,20 @@ print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>';
$output = RandomKey::randomKeyGen(
12,
[
0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G', 7 => 'H', 8 => 'I', 9 => 'J', 10 => 'K',
11 => 'L', 12 => 'M', 13 => 'N', 14 => 'O', 15 => 'P', 16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U',
21 => 'V', 22 => 'W', 23 => 'X', 24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b', 28 => 'c', 29 => 'd', 30 => 'e',
31 => 'f', 32 => 'g', 33 => 'h', 34 => 'i', 35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n', 40 => 'o',
41 => 'p', 42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v', 48 => 'w', 49 => 'x', 50 => 'y',
51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3', 56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8',
61 => '9'
]
);
print "CUSTOM OUTPUT: " . $output . "<br>";
$key_length = 10;
$key_length_b = 5;
$key_lenght_long = 64;
@@ -54,6 +68,7 @@ print "===<Br>";
$_array = new CoreLibs\Create\RandomKey(['A', 'F', 'B'], ['1', '5', '9']);
print "C->RANDOMKEYGEN(pre set): " . $_array->randomKeyGen() . "<br>";
print "</body></html>";
// __END__

View File

@@ -122,6 +122,16 @@ print "Combined strings A: "
. Strings::buildCharStringFromLists(['A', 'B', 'C'], ['0', '1', '2']) . "<br>";
print "Combined strings B: "
. Strings::buildCharStringFromLists([['F'], ['G'], 'H'], [['5', ['6']], ['0'], '1', '2']) . "<br>";
print "Combined strings C: "
. Strings::buildCharStringFromLists([
0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D', 4 => 'E', 5 => 'F', 6 => 'G', 7 => 'H', 8 => 'I', 9 => 'J', 10 => 'K',
11 => 'L', 12 => 'M', 13 => 'N', 14 => 'O', 15 => 'P', 16 => 'Q', 17 => 'R', 18 => 'S', 19 => 'T', 20 => 'U',
21 => 'V', 22 => 'W', 23 => 'X', 24 => 'Y', 25 => 'Z', 26 => 'a', 27 => 'b', 28 => 'c', 29 => 'd', 30 => 'e',
31 => 'f', 32 => 'g', 33 => 'h', 34 => 'i', 35 => 'j', 36 => 'k', 37 => 'l', 38 => 'm', 39 => 'n', 40 => 'o',
41 => 'p', 42 => 'q', 43 => 'r', 44 => 's', 45 => 't', 46 => 'u', 47 => 'v', 48 => 'w', 49 => 'x', 50 => 'y',
51 => 'z', 52 => '0', 53 => '1', 54 => '2', 55 => '3', 56 => '4', 57 => '5', 58 => '6', 59 => '7', 60 => '8',
61 => '9'
]) . "<br>";
$input_string = "AaBbCc";
print "Unique: " . Strings::removeDuplicates($input_string) . "<br>";
@@ -140,6 +150,28 @@ $preg_error = Strings::isValidRegex($regex_string);
print "[B] LAST PREGE ERROR: " . preg_last_error() . " -> "
. Strings::getLastRegexErrorString() . " -> " . preg_last_error_msg() . "<br>";
$base_strings = [
'abcddfff',
'A-Z',
'a-z',
'A-Za-z',
'A-Df-g',
'A-D0-9',
'D-A7-0',
'A-FB-G',
'0-9',
'あ-お',
'ア-オ',
];
foreach ($base_strings as $string) {
try {
$parsed = Strings::parseCharacterRanges($string);
print "Parsed ranges for '$string': " . DgS::printAr($parsed) . "<br>";
} catch (\InvalidArgumentException $e) {
print "Error parsing ranges for '$string': " . $e->getMessage() . "<br>";
}
}
print "</body></html>";
// __END__

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -114,7 +114,7 @@ class Encoding
(($char != $r_char && (!self::$mb_error_char ||
in_array(self::$mb_error_char, ['none', 'long', 'entity']))) ||
($char != $r_char && $r_char == self::$mb_error_char && self::$mb_error_char)) &&
ord($char) != 194
ord($char[0]) != 194
) {
$failed[] = $char;
}

View File

@@ -395,39 +395,68 @@ class DateTime
* does a reverse of the timeStringFormat and converts the string from
* xd xh xm xs xms to a timestamp.microtime format
*
* @param string|int|float $timestring formatted interval
* @return string|int|float converted float interval, or string as is
* @param string|int|float $timestring formatted interval
* @param bool $throw_exception [default=false] if set to true will throw exception
* instead of returning input value as is
* @return string|int|float converted float interval, or string as is
*/
public static function stringToTime(string|int|float $timestring): string|int|float
{
public static function stringToTime(
string|int|float $timestring,
bool $throw_exception = false
): string|int|float {
$timestamp = 0;
if (!preg_match("/(d|h|m|s|ms)/", (string)$timestring)) {
return $timestring;
}
$timestring = (string)$timestring;
// pos for preg match read + multiply factor
$timegroups = [2 => 86400, 4 => 3600, 6 => 60, 8 => 1];
$matches = [];
// if start with -, strip and set negative
$negative = false;
if (preg_match("/^-/", $timestring)) {
$negative = true;
$timestring = substr($timestring, 1);
}
// preg match: 0: full string
// 2, 4, 6, 8 are the to need values
preg_match("/^((\d+)d ?)?((\d+)h ?)?((\d+)m ?)?((\d+)s ?)?((\d+)ms)?$/", $timestring, $matches);
if (
!preg_match(
"/^\s*(-)?\s*"
. "((\d+)\s*d(?:ay(?:s)?)?)?\s*"
. "((\d+)\s*h(?:our(?:s)?)?)?\s*"
. "((\d+)\s*m(?:in(?:ute)?(?:s)?)?)?\s*"
. "((\d+)\s*s(?:ec(?:ond)?(?:s)?)?)?\s*"
. "((\d+)\s*m(?:illi)?s(?:ec(?:ond)?(?:s)?)?)?\s*"
. "$/",
(string)$timestring,
$matches
)
) {
if ($throw_exception) {
throw new \InvalidArgumentException(
'Invalid time string format, cannot parse: "' . (string)$timestring . '"',
1
);
}
return $timestring;
}
if (count($matches) < 2) {
if ($throw_exception) {
throw new \InvalidArgumentException(
'Invalid time string format, no interval value found: "' . (string)$timestring . '"',
2
);
}
return $timestring;
}
// pos for preg match read + multiply factor
$timegroups = [3 => 86400, 5 => 3600, 7 => 60, 9 => 1];
// if start with -, strip and set negative
$negative = false;
if (!empty($matches[1])) {
$negative = true;
}
// multiply the returned matches and sum them up. the last one (ms) is added with .
foreach ($timegroups as $i => $time_multiply) {
if (isset($matches[$i]) && is_numeric($matches[$i])) {
$timestamp += (float)$matches[$i] * $time_multiply;
}
}
if (isset($matches[10]) && is_numeric($matches[10])) {
$timestamp .= '.' . $matches[10];
if (isset($matches[11]) && is_numeric($matches[11])) {
// for milliseconds, we need to divide by 1000 and add them
$timestamp += (float)($matches[11] / 1000);
}
if ($negative) {
// cast to flaot so we can do a negative multiplication
// cast to float so we can do a negative multiplication
$timestamp = (float)$timestamp * -1;
}
return $timestamp;

View File

@@ -14,6 +14,7 @@ class Byte
public const BYTE_FORMAT_NOSPACE = 1;
public const BYTE_FORMAT_ADJUST = 2;
public const BYTE_FORMAT_SI = 4;
public const RETURN_AS_STRING = 8;
/**
* This function replaces the old byteStringFormat
@@ -119,7 +120,9 @@ class Byte
* @param int $flags bitwise flag with use space turned on
* BYTE_FORMAT_SI: use 1000 instead of 1024
* @return string|int|float converted value or original value
* @throws \InvalidArgumentException 1: no valid flag set
* @throws \InvalidArgumentException no valid flag set
* @throws \LengthException number too large to convert to int
* @throws \RuntimeException BCMath extension not loaded if flag is set to string
*/
public static function stringByteFormat(string|int|float $number, int $flags = 0): string|int|float
{
@@ -129,7 +132,12 @@ class Byte
} else {
$si = false;
}
if ($flags != 0 && $flags != 4) {
if ($flags & self::RETURN_AS_STRING) {
$return_as_string = true;
} else {
$return_as_string = false;
}
if ($flags != 0 && $flags != 4 && $flags != 8 && $flags != 12) {
throw new \InvalidArgumentException("Invalid flags parameter: $flags", 1);
}
// matches in regex
@@ -142,6 +150,10 @@ class Byte
strtolower((string)$number),
$matches
);
$number_negative = false;
if (!empty($matches[1])) {
$number_negative = true;
}
if (isset($matches[2]) && isset($matches[3])) {
// remove all non valid characters from the number
$number = preg_replace('/[^0-9\.]/', '', $matches[2]);
@@ -152,12 +164,48 @@ class Byte
if ($unit) {
$number = $number * pow($si ? 1000 : 1024, stripos($valid_units_, $unit[0]) ?: 0);
}
// if the number is too large, we cannot convert to int directly
if ($number <= PHP_INT_MIN || $number >= PHP_INT_MAX) {
// if we do not want to convert to string
if (!$return_as_string) {
throw new \LengthException(
'Number too large be converted to int: ' . (string)$number
);
}
// for string, check if bcmath is loaded, if not this will not work
if (!extension_loaded('bcmath')) {
throw new \RuntimeException(
'Number too large be converted to int and BCMath extension not loaded: ' . (string)$number
);
}
}
// string return
if ($return_as_string) {
// return as string to avoid overflow
// $number = (string)round($number);
$number = bcmul(number_format(
$number,
12,
'.',
''
), "1");
if ($number_negative) {
$number = '-' . $number;
}
return $number;
}
// convert to INT to avoid +E output
$number = (int)round($number);
// if negative input, keep nnegative
if (!empty($matches[1])) {
if ($number_negative) {
$number *= -1;
}
// check if number is negative but should be, this is Lenght overflow
if (!$number_negative && $number < 0) {
throw new \LengthException(
'Number too large be converted to int: ' . (string)$number
);
}
}
// if not matching return as is
return $number;

View File

@@ -27,10 +27,14 @@ class Json
* set original value as array
* @return array<mixed> returns an array from the json values
*/
public static function jsonConvertToArray(?string $json, bool $override = false): array
public static function jsonConvertToArray(?string $json, bool $override = false, int $flags = 0): array
{
if ($json !== null) {
$_json = json_decode($json, true);
// if flags has JSON_THROW_ON_ERROR remove it
if ($flags & JSON_THROW_ON_ERROR) {
$flags = $flags & ~JSON_THROW_ON_ERROR;
}
$_json = json_decode($json, true, flags:$flags);
if (self::$json_last_error = json_last_error()) {
if ($override == true) {
// init return as array with original as element
@@ -65,6 +69,21 @@ class Json
return (string)$json_string;
}
/**
* Validate if a json string could be decoded.
* Weill set the internval last error state and info can be read with jsonGetLastError
*
* @param string $json
* @param int $flags only JSON_INVALID_UTF8_IGNORE is currently allowed
* @return bool
*/
public static function jsonValidate(string $json, int $flags = 0): bool
{
$json_valid = json_validate($json, flags:$flags);
self::$json_last_error = json_last_error();
return $json_valid;
}
/**
* returns human readable string for json errors thrown in jsonConvertToArray
* Source: https://www.php.net/manual/en/function.json-last-error.php

View File

@@ -62,10 +62,15 @@ class Math
*
* @param float $number Number to cubic root
* @return float Calculated value
* @throws \InvalidArgumentException if $number is negative
*/
public static function cbrt(float|int $number): float
{
return pow((float)$number, 1.0 / 3);
$value = pow((float)$number, 1.0 / 3);
if (is_nan($value)) {
throw new \InvalidArgumentException('cube root from this number is not supported: ' . $number);
}
return $value;
}
/**

View File

@@ -268,6 +268,55 @@ class Strings
));
}
/**
* Split up character ranges in format A-Z, a-z, 0-9
*
* @param string $input
* @return string[]
*/
public static function parseCharacterRanges(string $input): array
{
// if not alphanumeric, throw value error
if (!preg_match("/^[A-Za-z0-9\-\s]+$/u", $input)) {
throw new \InvalidArgumentException(
"The input string contains invalid characters, "
. "only alphanumeric, dash (-), space and 'or' are allowed: "
. $input
);
}
// Remove all spaces
$input = str_replace(' ', '', $input);
$result = [];
// if there is no - inside, return unique characters as array
if (strpos($input, '-') === false) {
return array_unique(mb_str_split($input));
}
// Find all patterns like "A-Z" (character-dash-character)
preg_match_all('/(.)-(.)/u', $input, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$start = $match[1];
$end = $match[2];
// Get ASCII/Unicode values
$startOrd = ord($start[0]);
$endOrd = ord($end[0]);
// make sure start is before end
if ($startOrd > $endOrd) {
[$startOrd, $endOrd] = [$endOrd, $startOrd];
}
// Generate range of characters
for ($i = $startOrd; $i <= $endOrd; $i++) {
$char = chr($i);
if (!in_array($char, $result)) {
$result[] = $char;
}
}
}
// make the result unique
$result = array_unique($result);
return $result;
}
/**
* Check if a regex is valid. Does not return the detail regex parser error
*

View File

@@ -27,7 +27,7 @@ class RandomKey
/** @var int character count in they key character range */
private static int $key_character_range_length = 0;
/** @var int default key lenghth */
/** @deprecated Will be removed */
/** @deprecated Will be removed, as setting has moved to randomKeyGen */
private static int $key_length = 4;
/**
@@ -49,7 +49,7 @@ class RandomKey
private static function validateRandomKeyData(array ...$key_range): string
{
$key_character_range = Strings::buildCharStringFromLists(...$key_range);
if (strlen(self::$key_character_range) <= 1) {
if (strlen($key_character_range) <= 1) {
return '';
}
return $key_character_range;
@@ -107,7 +107,7 @@ class RandomKey
* @param int $key_length key length
* @return bool true for valid, false for invalid length
*/
private static function validateRandomKeyLenght(int $key_length): bool
private static function validateRandomKeyLength(int $key_length): bool
{
if (
$key_length > 0 &&
@@ -125,12 +125,12 @@ class RandomKey
*
* @param int $key_length key length
* @return bool true/false for set status
* @deprecated This function does no longer set the key length, the randomKeyGen parameter has to b used
* @deprecated This function does no longer set the key length, the randomKeyGen parameter has to be used
*/
public static function setRandomKeyLength(int $key_length): bool
{
// only if valid int key with valid length
if (self::validateRandomKeyLenght($key_length) === true) {
if (self::validateRandomKeyLength($key_length) === true) {
self::$key_length = $key_length;
return true;
} else {
@@ -176,7 +176,7 @@ class RandomKey
$key_character_range_length = self::getRandomKeyDataLength();
}
// if not valid key length, fallback to default
if (!self::validateRandomKeyLenght($key_length)) {
if (!self::validateRandomKeyLength($key_length)) {
$key_length = self::KEY_LENGTH_DEFAULT;
}
// create random string

View File

@@ -291,7 +291,7 @@ class ErrorMessage
*/
public function getLastErrorMsg(): array
{
return $this->error_str[array_key_last($this->error_str)] ?? [
return $this->error_str[array_key_last($this->error_str) ?? -1] ?? [
'level' => '',
'str' => '',
'id' => '',

View File

@@ -365,9 +365,6 @@ class Image
imagepng($thumb, $thumbnail_write_path . $thumbnail);
break;
}
// free up resources (in case we are called in a loop)
imagedestroy($source);
imagedestroy($thumb);
} else {
throw new \RuntimeException(
'Invalid source image file. Only JPEG/PNG are allowed: ' . $filename,
@@ -543,8 +540,6 @@ class Image
imagepng($img, $filename);
break;
}
// clean up image if we have an image
imagedestroy($img);
}
}

View File

@@ -614,8 +614,6 @@ class Curl implements Interface\RequestsInterface
// print "CURLINFO_HEADER_OUT: <pre>" . curl_getinfo($handle, CURLINFO_HEADER_OUT) . "</pre>";
// get response code and bail on not authorized
$http_response = $this->handleCurlResponse($handle, $http_result, $options['http_errors']);
// close handler
$this->handleCurlClose($handle);
// return response and result
return [
'code' => (string)$http_response,
@@ -838,17 +836,6 @@ class Curl implements Interface\RequestsInterface
);
}
/**
* close the current curl handle
*
* @param \CurlHandle $handle
* @return void
*/
private function handleCurlClose(\CurlHandle $handle): void
{
curl_close($handle);
}
// *********************************************************************
// MARK: PUBLIC METHODS
// *********************************************************************