Compare commits
14 Commits
v9.37.0
...
developmen
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b3ef52c5d | ||
|
|
f2271313fc | ||
|
|
a7f81766bf | ||
|
|
7a4c905ae3 | ||
|
|
0531b29749 | ||
|
|
5cf06f5b51 | ||
|
|
bb23e760d0 | ||
|
|
45487b0039 | ||
|
|
bfc82e12a5 | ||
|
|
8bd14b4385 | ||
|
|
cfb6f14c09 | ||
|
|
969aa684d9 | ||
|
|
74999e6f6b | ||
|
|
787468e67c |
@@ -56,6 +56,8 @@ return [
|
|||||||
// Automatically inferred from composer.json requirement for "php" of ">=8.2"
|
// Automatically inferred from composer.json requirement for "php" of ">=8.2"
|
||||||
'target_php_version' => '8.2',
|
'target_php_version' => '8.2',
|
||||||
"minimum_target_php_version" => "8.2",
|
"minimum_target_php_version" => "8.2",
|
||||||
|
// turn color on (-C)
|
||||||
|
"color_issue_messages_if_supported" => true,
|
||||||
|
|
||||||
// If enabled, missing properties will be created when
|
// If enabled, missing properties will be created when
|
||||||
// they are first seen. If false, we'll report an
|
// they are first seen. If false, we'll report an
|
||||||
|
|||||||
@@ -4,6 +4,6 @@
|
|||||||
<phar name="phpcs" version="^4.0.0" installed="4.0.1" location="./tools/phpcs" copy="false"/>
|
<phar name="phpcs" version="^4.0.0" installed="4.0.1" location="./tools/phpcs" copy="false"/>
|
||||||
<phar name="phpcbf" version="^4.0.0" installed="4.0.1" location="./tools/phpcbf" copy="false"/>
|
<phar name="phpcbf" version="^4.0.0" installed="4.0.1" location="./tools/phpcbf" copy="false"/>
|
||||||
<phar name="psalm" version="^5.15.0" installed="5.26.1" location="./tools/psalm" copy="false"/>
|
<phar name="psalm" version="^5.15.0" installed="5.26.1" location="./tools/psalm" copy="false"/>
|
||||||
<phar name="phpstan" version="^1.10.37" installed="1.12.32" location="./tools/phpstan" copy="false"/>
|
<phar name="phpstan" version="^2.0.0" installed="2.1.33" location="./tools/phpstan" copy="false"/>
|
||||||
<phar name="phan" version="^5.4.2" installed="5.5.2" location="./tools/phan" copy="false"/>
|
<phar name="phan" version="^5.4.2" installed="5.5.2" location="./tools/phan" copy="false"/>
|
||||||
</phive>
|
</phive>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
9.36.0
|
9.40.0
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
BASE_FOLDER=$(dirname "$(readlink -f "$0")")"/";
|
BASE_FOLDER=$(dirname "$(readlink -f "$0")")"/";
|
||||||
PACKAGE_DOWNLOAD="${BASE_FOLDER}package-download/";
|
PACKAGE_DOWNLOAD="${BASE_FOLDER}package-download/";
|
||||||
if [ ! -d "${PACKAGE_DOWNLOAD}" ]; then
|
if [ ! -d "${PACKAGE_DOWNLOAD}" ]; then
|
||||||
mkdir "${PACKAGE_DOWNLOAD}";
|
mkdir "${PACKAGE_DOWNLOAD}";
|
||||||
fi;
|
fi;
|
||||||
VERSION=$(git tag --list | sort -V | tail -n1 | sed -e "s/^v//");
|
VERSION=$(git tag --list | sort -V | tail -n1 | sed -e "s/^v//");
|
||||||
file_last_published="${BASE_FOLDER}last.published";
|
file_last_published="${BASE_FOLDER}last.published";
|
||||||
@@ -11,84 +11,84 @@ go_flag="$1";
|
|||||||
|
|
||||||
function gitea_publish
|
function gitea_publish
|
||||||
{
|
{
|
||||||
_GITEA_PUBLISH="${1}"
|
_GITEA_PUBLISH="${1}"
|
||||||
_GITEA_UPLOAD_FILENAME="${2}"
|
_GITEA_UPLOAD_FILENAME="${2}"
|
||||||
_GITEA_URL_DL="${3}"
|
_GITEA_URL_DL="${3}"
|
||||||
_GITEA_URL_PUSH="${4}"
|
_GITEA_URL_PUSH="${4}"
|
||||||
_GITEA_USER="${5}"
|
_GITEA_USER="${5}"
|
||||||
_GITEA_TOKEN="${6}"
|
_GITEA_TOKEN="${6}"
|
||||||
_PACKAGE_DOWNLOAD="${7}"
|
_PACKAGE_DOWNLOAD="${7}"
|
||||||
_VERSION="${8}"
|
_VERSION="${8}"
|
||||||
_file_last_published="${9}"
|
_file_last_published="${9}"
|
||||||
|
|
||||||
if [ -z "${_GITEA_PUBLISH}" ]; then
|
if [ -z "${_GITEA_PUBLISH}" ]; then
|
||||||
return
|
return
|
||||||
fi;
|
fi;
|
||||||
if [ -n "${_GITEA_UPLOAD_FILENAME}" ] &&
|
if [ -n "${_GITEA_UPLOAD_FILENAME}" ] &&
|
||||||
[ -n "${_GITEA_URL_DL}" ] && [ -n "${_GITEA_URL_PUSH}" ] &&
|
[ -n "${_GITEA_URL_DL}" ] && [ -n "${_GITEA_URL_PUSH}" ] &&
|
||||||
[ -n "${_GITEA_USER}" ] && [ -n "${_GITEA_TOKEN}" ]; then
|
[ -n "${_GITEA_USER}" ] && [ -n "${_GITEA_TOKEN}" ]; then
|
||||||
echo "> Publish ${_GITEA_UPLOAD_FILENAME} with ${_VERSION} to: ${_GITEA_URL_PUSH}";
|
echo "> Publish ${_GITEA_UPLOAD_FILENAME} with ${_VERSION} to: ${_GITEA_URL_PUSH}";
|
||||||
if [ ! -f "${_PACKAGE_DOWNLOAD}${_GITEA_UPLOAD_FILENAME}-v${_VERSION}.zip" ]; then
|
if [ ! -f "${_PACKAGE_DOWNLOAD}${_GITEA_UPLOAD_FILENAME}-v${_VERSION}.zip" ]; then
|
||||||
echo "> Download: ${_GITEA_UPLOAD_FILENAME}-v${_VERSION}.zip";
|
echo "> Download: ${_GITEA_UPLOAD_FILENAME}-v${_VERSION}.zip";
|
||||||
curl -LJO \
|
curl -LJO \
|
||||||
--output-dir "${_PACKAGE_DOWNLOAD}" \
|
--output-dir "${_PACKAGE_DOWNLOAD}" \
|
||||||
"${_GITEA_URL_DL}"/v"${_VERSION}".zip;
|
"${_GITEA_URL_DL}"/v"${_VERSION}".zip;
|
||||||
fi;
|
fi;
|
||||||
if [ ! -f "${_PACKAGE_DOWNLOAD}${_GITEA_UPLOAD_FILENAME}-v${_VERSION}.zip" ]; then
|
if [ ! -f "${_PACKAGE_DOWNLOAD}${_GITEA_UPLOAD_FILENAME}-v${_VERSION}.zip" ]; then
|
||||||
echo "[!] Package file does not exist for version: ${_VERSION}";
|
echo "[!] Package file does not exist for version: ${_VERSION}";
|
||||||
else
|
else
|
||||||
response=$(curl --user "${_GITEA_USER}":"${_GITEA_TOKEN}" \
|
response=$(curl --user "${_GITEA_USER}":"${_GITEA_TOKEN}" \
|
||||||
--upload-file "${_PACKAGE_DOWNLOAD}${_GITEA_UPLOAD_FILENAME}-v${_VERSION}.zip" \
|
--upload-file "${_PACKAGE_DOWNLOAD}${_GITEA_UPLOAD_FILENAME}-v${_VERSION}.zip" \
|
||||||
"${_GITEA_URL_PUSH}"?version="${_VERSION}");
|
"${_GITEA_URL_PUSH}"?version="${_VERSION}");
|
||||||
status=$(echo "${response}" | jq .errors[].status);
|
status=$(echo "${response}" | jq .errors[].status);
|
||||||
message=$(echo "${response}" | jq .errors[].message);
|
message=$(echo "${response}" | jq .errors[].message);
|
||||||
if [ -n "${status}" ]; then
|
if [ -n "${status}" ]; then
|
||||||
echo "[!] Error ${status}: ${message}";
|
echo "[!] Error ${status}: ${message}";
|
||||||
else
|
else
|
||||||
echo "> Publish completed";
|
echo "> Publish completed";
|
||||||
fi;
|
fi;
|
||||||
echo "${_VERSION}" > "${_file_last_published}";
|
echo "${_VERSION}" > "${_file_last_published}";
|
||||||
fi;
|
fi;
|
||||||
else
|
else
|
||||||
echo "[!] Missing either GITEA_UPLOAD_FILENAME, GITEA_URL_DL, GITEA_URL_PUSH, GITEA_USER or GITEA_TOKEN environment variable";
|
echo "[!] Missing either GITEA_UPLOAD_FILENAME, GITEA_URL_DL, GITEA_URL_PUSH, GITEA_USER or GITEA_TOKEN environment variable";
|
||||||
fi;
|
fi;
|
||||||
}
|
}
|
||||||
|
|
||||||
function gitlab_publish
|
function gitlab_publish
|
||||||
{
|
{
|
||||||
_GITLAB_PUBLISH="${1}";
|
_GITLAB_PUBLISH="${1}";
|
||||||
_GITLAB_URL="${2}";
|
_GITLAB_URL="${2}";
|
||||||
_GITLAB_DEPLOY_TOKEN="${3}";
|
_GITLAB_DEPLOY_TOKEN="${3}";
|
||||||
_PACKAGE_DOWNLOAD="${4}"
|
_PACKAGE_DOWNLOAD="${4}"
|
||||||
_VERSION="${5}"
|
_VERSION="${5}"
|
||||||
_file_last_published="${6}"
|
_file_last_published="${6}"
|
||||||
if [ -z "${GITLAB_PUBLISH}" ]; then
|
if [ -z "${_GITLAB_PUBLISH}" ]; then
|
||||||
return;
|
return;
|
||||||
fi;
|
fi;
|
||||||
if [ -n "${_GITLAB_URL}" ] && [ -n "${_GITLAB_DEPLOY_TOKEN}" ]; then
|
if [ -n "${_GITLAB_URL}" ] && [ -n "${_GITLAB_DEPLOY_TOKEN}" ]; then
|
||||||
curl --data tag=v"${_VERSION}" \
|
curl --data tag=v"${_VERSION}" \
|
||||||
--header "Deploy-Token: ${_GITLAB_DEPLOY_TOKEN}" \
|
--header "Deploy-Token: ${_GITLAB_DEPLOY_TOKEN}" \
|
||||||
"${_GITLAB_URL}";
|
"${_GITLAB_URL}";
|
||||||
curl --data branch=master \
|
curl --data branch=master \
|
||||||
--header "Deploy-Token: ${_GITLAB_DEPLOY_TOKEN}" \
|
--header "Deploy-Token: ${_GITLAB_DEPLOY_TOKEN}" \
|
||||||
"${_GITLAB_URL}";
|
"${_GITLAB_URL}";
|
||||||
echo "${_VERSION}" > "${_file_last_published}";
|
echo "${_VERSION}" > "${_file_last_published}";
|
||||||
else
|
else
|
||||||
echo "[!] Missing GITLAB_URL or GITLAB_DEPLOY_TOKEN environment variable";
|
echo "[!] Missing GITLAB_URL or GITLAB_DEPLOY_TOKEN environment variable";
|
||||||
fi;
|
fi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if [ -z "${VERSION}" ]; then
|
if [ -z "${VERSION}" ]; then
|
||||||
echo "[!] Version must be set in the form x.y.z without any leading characters";
|
echo "[!] Version must be set in the form x.y.z without any leading characters";
|
||||||
exit;
|
exit;
|
||||||
fi;
|
fi;
|
||||||
# compare version, if different or newer, deploy
|
# compare version, if different or newer, deploy
|
||||||
if [ -f "${file_last_published}" ]; then
|
if [ -f "${file_last_published}" ]; then
|
||||||
LAST_PUBLISHED_VERSION=$(cat "${file_last_published}");
|
LAST_PUBLISHED_VERSION=$(cat "${file_last_published}");
|
||||||
if dpkg --compare-versions "${VERSION}" le "${LAST_PUBLISHED_VERSION}"; then
|
if dpkg --compare-versions "${VERSION}" le "${LAST_PUBLISHED_VERSION}"; then
|
||||||
echo "[!] git tag version ${VERSION} is not newer than previous published version ${LAST_PUBLISHED_VERSION}";
|
echo "[!] git tag version ${VERSION} is not newer than previous published version ${LAST_PUBLISHED_VERSION}";
|
||||||
fi;
|
fi;
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
# read in the .env.deploy file and we must have
|
# read in the .env.deploy file and we must have
|
||||||
@@ -105,8 +105,8 @@ fi;
|
|||||||
# GITLAB_TOKEN
|
# GITLAB_TOKEN
|
||||||
# GITLAB_URL
|
# GITLAB_URL
|
||||||
if [ ! -f "${BASE_FOLDER}.env.deploy" ]; then
|
if [ ! -f "${BASE_FOLDER}.env.deploy" ]; then
|
||||||
echo "[!] Deploy enviroment file .env.deploy is missing";
|
echo "[!] Deploy enviroment file .env.deploy is missing";
|
||||||
exit;
|
exit;
|
||||||
fi;
|
fi;
|
||||||
set -o allexport;
|
set -o allexport;
|
||||||
cd "${BASE_FOLDER}" || exit
|
cd "${BASE_FOLDER}" || exit
|
||||||
@@ -116,10 +116,10 @@ cd - >/dev/null 2>&1 || exit;
|
|||||||
set +o allexport;
|
set +o allexport;
|
||||||
|
|
||||||
if [ "${go_flag}" != "go" ]; then
|
if [ "${go_flag}" != "go" ]; then
|
||||||
echo "[!] No go flag given";
|
echo "[!] No go flag given";
|
||||||
echo "> Would publish ${VERSION}";
|
echo "> Would publish ${VERSION}";
|
||||||
echo "[END]";
|
echo "[END]";
|
||||||
exit;
|
exit;
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
echo "[START]";
|
echo "[START]";
|
||||||
|
|||||||
@@ -395,39 +395,68 @@ class DateTime
|
|||||||
* does a reverse of the timeStringFormat and converts the string from
|
* does a reverse of the timeStringFormat and converts the string from
|
||||||
* xd xh xm xs xms to a timestamp.microtime format
|
* xd xh xm xs xms to a timestamp.microtime format
|
||||||
*
|
*
|
||||||
* @param string|int|float $timestring formatted interval
|
* @param string|int|float $timestring formatted interval
|
||||||
* @return string|int|float converted float interval, or string as is
|
* @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;
|
$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 = [];
|
$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
|
// preg match: 0: full string
|
||||||
// 2, 4, 6, 8 are the to need values
|
// 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 .
|
// multiply the returned matches and sum them up. the last one (ms) is added with .
|
||||||
foreach ($timegroups as $i => $time_multiply) {
|
foreach ($timegroups as $i => $time_multiply) {
|
||||||
if (isset($matches[$i]) && is_numeric($matches[$i])) {
|
if (isset($matches[$i]) && is_numeric($matches[$i])) {
|
||||||
$timestamp += (float)$matches[$i] * $time_multiply;
|
$timestamp += (float)$matches[$i] * $time_multiply;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isset($matches[10]) && is_numeric($matches[10])) {
|
if (isset($matches[11]) && is_numeric($matches[11])) {
|
||||||
$timestamp .= '.' . $matches[10];
|
// for milliseconds, we need to divide by 1000 and add them
|
||||||
|
$timestamp += (float)($matches[11] / 1000);
|
||||||
}
|
}
|
||||||
if ($negative) {
|
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;
|
$timestamp = (float)$timestamp * -1;
|
||||||
}
|
}
|
||||||
return $timestamp;
|
return $timestamp;
|
||||||
|
|||||||
@@ -27,10 +27,14 @@ class Json
|
|||||||
* set original value as array
|
* set original value as array
|
||||||
* @return array<mixed> returns an array from the json values
|
* @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) {
|
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 (self::$json_last_error = json_last_error()) {
|
||||||
if ($override == true) {
|
if ($override == true) {
|
||||||
// init return as array with original as element
|
// init return as array with original as element
|
||||||
@@ -65,6 +69,21 @@ class Json
|
|||||||
return (string)$json_string;
|
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
|
* returns human readable string for json errors thrown in jsonConvertToArray
|
||||||
* Source: https://www.php.net/manual/en/function.json-last-error.php
|
* Source: https://www.php.net/manual/en/function.json-last-error.php
|
||||||
|
|||||||
@@ -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
|
* Check if a regex is valid. Does not return the detail regex parser error
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class RandomKey
|
|||||||
/** @var int character count in they key character range */
|
/** @var int character count in they key character range */
|
||||||
private static int $key_character_range_length = 0;
|
private static int $key_character_range_length = 0;
|
||||||
/** @var int default key lenghth */
|
/** @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;
|
private static int $key_length = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,7 +49,7 @@ class RandomKey
|
|||||||
private static function validateRandomKeyData(array ...$key_range): string
|
private static function validateRandomKeyData(array ...$key_range): string
|
||||||
{
|
{
|
||||||
$key_character_range = Strings::buildCharStringFromLists(...$key_range);
|
$key_character_range = Strings::buildCharStringFromLists(...$key_range);
|
||||||
if (strlen(self::$key_character_range) <= 1) {
|
if (strlen($key_character_range) <= 1) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return $key_character_range;
|
return $key_character_range;
|
||||||
@@ -107,7 +107,7 @@ class RandomKey
|
|||||||
* @param int $key_length key length
|
* @param int $key_length key length
|
||||||
* @return bool true for valid, false for invalid 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 (
|
if (
|
||||||
$key_length > 0 &&
|
$key_length > 0 &&
|
||||||
@@ -125,12 +125,12 @@ class RandomKey
|
|||||||
*
|
*
|
||||||
* @param int $key_length key length
|
* @param int $key_length key length
|
||||||
* @return bool true/false for set status
|
* @return bool true/false for set status
|
||||||
* @deprecated This function does no longer set the key length, the randomKeyGen parameter has to b used
|
* @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
|
public static function setRandomKeyLength(int $key_length): bool
|
||||||
{
|
{
|
||||||
// only if valid int key with valid length
|
// 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;
|
self::$key_length = $key_length;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@@ -176,7 +176,7 @@ class RandomKey
|
|||||||
$key_character_range_length = self::getRandomKeyDataLength();
|
$key_character_range_length = self::getRandomKeyDataLength();
|
||||||
}
|
}
|
||||||
// if not valid key length, fallback to default
|
// if not valid key length, fallback to default
|
||||||
if (!self::validateRandomKeyLenght($key_length)) {
|
if (!self::validateRandomKeyLength($key_length)) {
|
||||||
$key_length = self::KEY_LENGTH_DEFAULT;
|
$key_length = self::KEY_LENGTH_DEFAULT;
|
||||||
}
|
}
|
||||||
// create random string
|
// create random string
|
||||||
|
|||||||
@@ -490,11 +490,11 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
|
|||||||
],
|
],
|
||||||
'micro interval with microtime' => [
|
'micro interval with microtime' => [
|
||||||
'18999d 0h 38m 10s 1235ms',
|
'18999d 0h 38m 10s 1235ms',
|
||||||
1641515890.1235,
|
1641515891.235,
|
||||||
],
|
],
|
||||||
'micro interval with microtime' => [
|
'micro interval with microtime' => [
|
||||||
'18999d 0h 38m 10s 1234567890ms',
|
'18999d 0h 38m 10s 1234567890ms',
|
||||||
1641515890.1234567,
|
1642750457.89,
|
||||||
],
|
],
|
||||||
'negative interval no microtime' => [
|
'negative interval no microtime' => [
|
||||||
'-18999d 0h 38m 10s',
|
'-18999d 0h 38m 10s',
|
||||||
@@ -503,23 +503,246 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
|
|||||||
// short for mini tests
|
// short for mini tests
|
||||||
'microtime only' => [
|
'microtime only' => [
|
||||||
'0s 1235ms',
|
'0s 1235ms',
|
||||||
0.1235,
|
1.235,
|
||||||
],
|
],
|
||||||
'seconds only' => [
|
'seconds only' => [
|
||||||
'30s 1235ms',
|
'30s 1235ms',
|
||||||
30.1235,
|
31.235,
|
||||||
],
|
],
|
||||||
'minutes only' => [
|
'minutes only' => [
|
||||||
'1m 30s 1235ms',
|
'1m 30s 1235ms',
|
||||||
90.1235,
|
91.235,
|
||||||
],
|
],
|
||||||
'hours only' => [
|
'hours only' => [
|
||||||
'1h 1m 30s 1235ms',
|
'1h 1m 30s 1235ms',
|
||||||
3690.1235,
|
3691.235,
|
||||||
],
|
],
|
||||||
'days only' => [
|
'days only' => [
|
||||||
'1d 1h 1m 30s 1235ms',
|
'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' => [
|
'already set' => [
|
||||||
1641515890,
|
1641515890,
|
||||||
@@ -529,10 +752,18 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
|
|||||||
'xyz',
|
'xyz',
|
||||||
'xyz',
|
'xyz',
|
||||||
],
|
],
|
||||||
|
'empty data' => [
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
],
|
||||||
'out of bound data' => [
|
'out of bound data' => [
|
||||||
'99999999999999999999d',
|
'99999999999999999999d',
|
||||||
8.64E+24
|
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
|
* Undocumented function
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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
|
* 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
|
* Undocumented function
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -698,6 +698,89 @@ final class CoreLibsConvertStringsTest extends TestCase
|
|||||||
'Cannot match last preg error string'
|
'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__
|
// __END__
|
||||||
|
|||||||
Reference in New Issue
Block a user