Compare commits
94 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e286d7f913 | |||
| e148a39902 | |||
| b7d5a79c3a | |||
| 9f8a86b4b0 | |||
| 50e593789e | |||
| 4ee141f8df | |||
| 9ee8f43478 | |||
| 2c75dbdf6c | |||
| 5fe61388fc | |||
| a03c7e7319 | |||
| 7e01152bb4 | |||
| fbea8f4aca | |||
| 346cdaad72 | |||
| 6887f17e15 | |||
| 5b1ca4241c | |||
| c8d6263c0f | |||
| bd1972d894 | |||
| fa29477c80 | |||
| 20ee958db9 | |||
| 157616582f | |||
| 0f7bf0ab44 | |||
| 10dc56c7cb | |||
| d19842007c | |||
| c5bd16de74 | |||
| e580e5430c | |||
| da9717265c | |||
| 6c6b33cacc | |||
| 5509d1c92e | |||
| 16d789f061 | |||
| 3450b6263b | |||
| bcc1e833c1 | |||
| fea4c315f3 | |||
| 3ac57e05b0 | |||
| b0230e7050 | |||
| 70f78a5f9c | |||
| 814ec50d3d | |||
| e60ce3f0dc | |||
| 6140bd8671 | |||
| e318a4fb9a | |||
| 9818410889 | |||
| 62e1e3b97c | |||
| 1bb4d5f426 | |||
| a65485e56a | |||
| 5e6ba85639 | |||
| 3cf891a7d2 | |||
| 6e19f30ff5 | |||
| 95079885c5 | |||
| 14dab54f2c | |||
| 180fba596a | |||
| 69e2503a36 | |||
| c06ae55919 | |||
| 6098d1091a | |||
| 2a8038835f | |||
| 984dec37e2 | |||
| d91fbd5a46 | |||
| 668954c1c4 | |||
| adbae19fca | |||
| 39de680b66 | |||
| 2e81a4da82 | |||
| 61782be11c | |||
| e7fe4655d1 | |||
| 561be4bce6 | |||
| 51fef30364 | |||
| 89a4b4cf3e | |||
| 3eb1229590 | |||
| f174e9ec34 | |||
| 928369cff7 | |||
| 29f4800e1a | |||
| e706a67427 | |||
| 672de694ee | |||
| c69eac3258 | |||
| eca62e53c9 | |||
| 62cd3badfe | |||
| 4bd2568d94 | |||
| b3c7947c67 | |||
| 6710f44646 | |||
| f89be6bd19 | |||
| 9696336abc | |||
| fec1c5ad57 | |||
| a6e5e86c9e | |||
| 1357b98883 | |||
| fa18a6f422 | |||
| ada130329f | |||
| 4c489adf9b | |||
| c8a2ad4d48 | |||
| d54a6cbdf5 | |||
| e8f4c82f59 | |||
| 24eb2c2bee | |||
| 94edb7f556 | |||
| 3f5b3f02ad | |||
| a270922b4b | |||
| 8c607ae610 | |||
| d3d4cf512f | |||
| efae352c35 |
33
.github/workflows-disabled/ci.yml
vendored
Normal file
33
.github/workflows-disabled/ci.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: CI
|
||||
run-name: ${{ github.actor}} runs CI
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
phpstan:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
# - uses: php-actions/composer@v6
|
||||
# env:
|
||||
# COMPOSER_ROOT_VERSION: dev-master
|
||||
- name: "Restore result cache"
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ./tmp
|
||||
key: "result-cache-v1-${{ matrix.php-version }}-${{ github.run_id }}"
|
||||
restore-keys: |
|
||||
result-cache-v1-${{ matrix.php-version }}-
|
||||
- name: PHPStan Static Analysis
|
||||
uses: php-actions/phpstan@v3
|
||||
with:
|
||||
path: src/
|
||||
- name: "Save result cache"
|
||||
uses: actions/cache/save@v4
|
||||
if: always()
|
||||
with:
|
||||
path: ./tmp
|
||||
key: "result-cache-v1-${{ matrix.php-version }}-${{ github.run_id }}"
|
||||
# - name: PHPunit Tests
|
||||
# run: |
|
||||
# vendor/bin/phpunit
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
vendor
|
||||
vendor/
|
||||
composer.lock
|
||||
tools/
|
||||
|
||||
@@ -54,7 +54,8 @@ return [
|
||||
// Note that the **only** effect of choosing `'5.6'` is to infer that functions removed in php 7.0 exist.
|
||||
// (See `backward_compatibility_checks` for additional options)
|
||||
// Automatically inferred from composer.json requirement for "php" of ">=8.2"
|
||||
'target_php_version' => '8.1',
|
||||
'target_php_version' => '8.2',
|
||||
"minimum_target_php_version" => "8.2",
|
||||
|
||||
// If enabled, missing properties will be created when
|
||||
// they are first seen. If false, we'll report an
|
||||
@@ -370,6 +371,7 @@ return [
|
||||
'file_list' => [
|
||||
"./test/configs/config.php",
|
||||
"./test/configs/config.other.php",
|
||||
"./test/configs/config.path.php",
|
||||
"./test/configs/config.master.php",
|
||||
],
|
||||
];
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phive xmlns="https://phar.io/phive">
|
||||
<phar name="phpunit" version="^9.6" installed="9.6.13" location="./tools/phpunit" copy="false"/>
|
||||
<phar name="phpcs" version="^3.7.2" installed="3.7.2" location="./tools/phpcs" copy="false"/>
|
||||
<phar name="phpcbf" version="^3.7.2" installed="3.7.2" location="./tools/phpcbf" copy="false"/>
|
||||
<phar name="psalm" version="^5.15.0" installed="5.16.0" location="./tools/psalm" copy="false"/>
|
||||
<phar name="phpstan" version="^1.10.37" installed="1.10.46" location="./tools/phpstan" copy="false"/>
|
||||
<phar name="phan" version="^5.4.2" installed="5.4.2" location="./tools/phan" copy="false"/>
|
||||
<phar name="phpunit" version="^9.6" installed="9.6.19" location="./tools/phpunit" copy="false"/>
|
||||
<phar name="phpcs" version="^3.7.2" installed="3.10.0" location="./tools/phpcs" copy="false"/>
|
||||
<phar name="phpcbf" version="^3.7.2" installed="3.10.0" location="./tools/phpcbf" copy="false"/>
|
||||
<phar name="psalm" version="^5.15.0" installed="5.24.0" location="./tools/psalm" copy="false"/>
|
||||
<phar name="phpstan" version="^1.10.37" installed="1.11.1" location="./tools/phpstan" copy="false"/>
|
||||
<phar name="phan" version="^5.4.2" installed="5.4.3" location="./tools/phan" copy="false"/>
|
||||
</phive>
|
||||
|
||||
2
.shellcheckrc
Normal file
2
.shellcheckrc
Normal file
@@ -0,0 +1,2 @@
|
||||
shell=bash
|
||||
external-sources=true
|
||||
39
ReadMe.md
39
ReadMe.md
@@ -9,20 +9,45 @@ For local install only
|
||||
- Template\SmartyExtended
|
||||
- Admin\EditBase
|
||||
|
||||
## Setup from central composer
|
||||
## Publish to gitea or gitlab server
|
||||
|
||||
Setup from gitea internal servers
|
||||
Currently there are only gitea and gitlab supported, github does not have support for composer packages
|
||||
|
||||
```sh
|
||||
composer config repositories.git.egplusww.jp.Composer composer https://git.egplusww.jp/api/packages/Composer/composer
|
||||
`publish\publish.sh go` will run the publish script
|
||||
|
||||
All the configuration is done in the `publish\.env.deploy` file
|
||||
|
||||
```ini
|
||||
# downlaod file name is "Repository name" "-" "version" where
|
||||
# version is "vN.N.N"
|
||||
GITEA_PUBLISH=1
|
||||
GITEA_UPLOAD_FILENAME="Upload-File-Name";
|
||||
GITEA_USER=gitea-user
|
||||
GITEA_TOKEN=gitea-tokek
|
||||
GITEA_URL_DL=https://[gitea.hostname]/[to/package/folder]/archive
|
||||
GITEA_URL_PUSH=https://[gitea.hostname]/api/packages/[organization]/composer
|
||||
|
||||
GITLAB_PUBLISH=1
|
||||
GITLAB_URL=gitlab URl to repository
|
||||
GITLAB_DEPLOY_TOKEN=gitlab-token
|
||||
```
|
||||
|
||||
Alternative setup composer local zip file repot:
|
||||
`composer config repositories.composer.egplusww.jp composer http://composer.egplusww.jp`
|
||||
At the moment there is only one gitea or gitlab target setable
|
||||
|
||||
## Setup from central composer
|
||||
|
||||
Setup from gitea servers
|
||||
|
||||
[hostname] is the hostname for your gitea server (or wherever this is published)
|
||||
[OrgName] is the organization name where the composer packages are hosted
|
||||
|
||||
```sh
|
||||
composer config repositories.[hostname].Composer composer https://[hostname]/api/packages/[OrgName]/composer
|
||||
```
|
||||
|
||||
## Install package
|
||||
|
||||
`composer require egrajp/corelibs-composer-all:^8.0`
|
||||
`composer require egrajp/corelibs-composer-all:^9.0`
|
||||
|
||||
## Tests
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"description": "CoreLibs in a composer package",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"keywords": ["corelib", "logging", "database", "templating", "tools"],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"CoreLibs\\": "src/"
|
||||
@@ -20,8 +21,13 @@
|
||||
"psr/log": "^3.0@dev"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^2.0",
|
||||
"phpstan/phpdoc-parser": "^2.0",
|
||||
"phpstan/phpstan-deprecation-rules": "^2.0",
|
||||
"phan/phan": "^5.4",
|
||||
"egrajp/smarty-extended": "^4.3",
|
||||
"gullevek/dotenv": "dev-master"
|
||||
"gullevek/dotenv": "dev-master",
|
||||
"phpunit/phpunit": "^9"
|
||||
},
|
||||
"repositories": {
|
||||
"git.egplusww.jp.Composer": {
|
||||
|
||||
18
phpcs.xml
Normal file
18
phpcs.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0"?>
|
||||
<ruleset name="MyStandard">
|
||||
<description>PSR12 override rules (strict, standard). Switch spaces indent to tab.</description>
|
||||
<arg name="tab-width" value="4"/>
|
||||
<rule ref="PSR1"/>
|
||||
<rule ref="PSR12">
|
||||
<!-- turn off white space check for tab -->
|
||||
<exclude name="Generic.WhiteSpace.DisallowTabIndent"/>
|
||||
</rule>
|
||||
<!-- no space indent, must be tab, 4 is tab iwdth -->
|
||||
<rule ref="Generic.WhiteSpace.DisallowSpaceIndent"/>
|
||||
<rule ref="Generic.WhiteSpace.ScopeIndent">
|
||||
<properties>
|
||||
<property name="indent" value="4"/>
|
||||
<property name="tabIndent" value="true"/>
|
||||
</properties>
|
||||
</rule>
|
||||
</ruleset>
|
||||
@@ -8,5 +8,6 @@ $_SERVER['HTTP_HOST'] = 'soba.tokyo.tequila.jp';
|
||||
// for whatever reason it does not load that from the confing.master.php
|
||||
// for includes/admin_header.php
|
||||
define('BASE_NAME', '');
|
||||
define('CONTENT_PATH', '');
|
||||
|
||||
// __END__
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
includes:
|
||||
- phpstan-conditional.php
|
||||
parameters:
|
||||
tmpDir: /tmp/phpstan-corelibs-composer
|
||||
tmpDir: %currentWorkingDirectory%/tmp/phpstan-corelibs-composer
|
||||
level: 8 # max is now 9
|
||||
checkMissingCallableSignature: true
|
||||
treatPhpDocTypesAsCertain: false
|
||||
|
||||
@@ -4,4 +4,9 @@
|
||||
verbose="true"
|
||||
bootstrap="test/phpunit/bootstrap.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="deploy">
|
||||
<directory>test/phpunit</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
||||
|
||||
@@ -1 +1 @@
|
||||
9.11.0
|
||||
9.25.2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
BASE_FOLDER=$(dirname $(readlink -f $0))"/";
|
||||
BASE_FOLDER=$(dirname "$(readlink -f "$0")")"/";
|
||||
PACKAGE_DOWNLOAD="${BASE_FOLDER}package-download/";
|
||||
if [ ! -d "${PACKAGE_DOWNLOAD}" ]; then
|
||||
mkdir "${PACKAGE_DOWNLOAD}";
|
||||
@@ -15,30 +15,35 @@ if [ -z "${VERSION}" ]; then
|
||||
fi;
|
||||
# compare version, if different or newer, deploy
|
||||
if [ -f "${file_last_published}" ]; then
|
||||
LAST_PUBLISHED_VERSION=$(cat ${file_last_published});
|
||||
if $(dpkg --compare-versions "${VERSION}" le "${LAST_PUBLISHED_VERSION}"); then
|
||||
LAST_PUBLISHED_VERSION=$(cat "${file_last_published}");
|
||||
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}";
|
||||
exit;
|
||||
fi;
|
||||
fi;
|
||||
|
||||
# read in the .env.deploy file and we must have
|
||||
# for gitea
|
||||
# GITEA_PUBLISH: must be set with a value to trigger publish run
|
||||
# GITEA_UPLOAD_FILENAME
|
||||
# GITLAB_USER
|
||||
# GITLAB_TOKEN
|
||||
# GITLAB_URL
|
||||
# GITEA_USER
|
||||
# GITEA_DEPLOY_TOKEN
|
||||
# GITEA_URL_DL
|
||||
# GITEA_URL_PUSH
|
||||
# for gitlab
|
||||
# GITLAB_PUBLISH: must be set with a value to trigger publish run
|
||||
# GITLAB_USER
|
||||
# GITLAB_TOKEN
|
||||
# GITLAB_URL
|
||||
if [ ! -f "${BASE_FOLDER}.env.deploy" ]; then
|
||||
echo "Deploy enviroment file .env.deploy is missing";
|
||||
exit;
|
||||
fi;
|
||||
set -o allexport;
|
||||
cd ${BASE_FOLDER};
|
||||
cd "${BASE_FOLDER}" || exit;
|
||||
# shellcheck source=.env.deploy
|
||||
source .env.deploy;
|
||||
cd -;
|
||||
cd - || exit;
|
||||
set +o allexport;
|
||||
|
||||
if [ "${go_flag}" != "go" ]; then
|
||||
@@ -50,23 +55,33 @@ fi;
|
||||
|
||||
echo "[START]";
|
||||
# gitea
|
||||
if [ ! -z "${GITEA_UPLOAD_FILENAME}" ] &&
|
||||
[ ! -z "${GITEA_URL_DL}" ] && [ ! -z "${GITEA_URL_PUSH}" ] &&
|
||||
[ ! -z "${GITEA_USER}" ] && [ ! -z "${GITEA_TOKEN}" ]; then
|
||||
# skip iof
|
||||
if [ -n "${GITEA_PUBLISH}" ]; then
|
||||
if [ -n "${GITEA_UPLOAD_FILENAME}" ] &&
|
||||
[ -n "${GITEA_URL_DL}" ] && [ -n "${GITEA_URL_PUSH}" ] &&
|
||||
[ -n "${GITEA_USER}" ] && [ -n "${GITEA_TOKEN}" ]; then
|
||||
if [ ! -f "${PACKAGE_DOWNLOAD}${GITEA_UPLOAD_FILENAME}-v${VERSION}.zip" ]; then
|
||||
curl -LJO \
|
||||
--output-dir "${PACKAGE_DOWNLOAD}" \
|
||||
${GITEA_URL_DL}/v${VERSION}.zip;
|
||||
curl --user ${GITEA_USER}:${GITEA_TOKEN} \
|
||||
"${GITEA_URL_DL}"/v"${VERSION}".zip;
|
||||
fi;
|
||||
if [ ! -f "${PACKAGE_DOWNLOAD}${GITEA_UPLOAD_FILENAME}-v${VERSION}.zip" ]; then
|
||||
echo "Version file does not exist for ${VERSION}";
|
||||
else
|
||||
curl --user "${GITEA_USER}":"${GITEA_TOKEN}" \
|
||||
--upload-file "${PACKAGE_DOWNLOAD}${GITEA_UPLOAD_FILENAME}-v${VERSION}.zip" \
|
||||
${GITEA_URL_PUSH}?version=${VERSION};
|
||||
"${GITEA_URL_PUSH}"?version="${VERSION}";
|
||||
echo "${VERSION}" > "${file_last_published}";
|
||||
fi;
|
||||
else
|
||||
echo "Missing either GITEA_UPLOAD_FILENAME, GITEA_URL_DL, GITEA_URL_PUSH, GITEA_USER or GITEA_TOKEN environment variable";
|
||||
fi;
|
||||
fi;
|
||||
|
||||
# gitlab
|
||||
if [ ! -z "${GITLAB_URL}" ] && [ ! -z "${GITLAB_DEPLOY_TOKEN}" ]; then
|
||||
curl --data tag=v${VERSION} \
|
||||
if [ -n "${GITLAB_PUBLISH}" ]; then
|
||||
if [ -n "${GITLAB_URL}" ] && [ -n "${GITLAB_DEPLOY_TOKEN}" ]; then
|
||||
curl --data tag=v"${VERSION}" \
|
||||
--header "Deploy-Token: ${GITLAB_DEPLOY_TOKEN}" \
|
||||
"${GITLAB_URL}";
|
||||
curl --data branch=master \
|
||||
@@ -74,7 +89,8 @@ if [ ! -z "${GITLAB_URL}" ] && [ ! -z "${GITLAB_DEPLOY_TOKEN}" ]; then
|
||||
"${GITLAB_URL}";
|
||||
echo "${VERSION}" > "${file_last_published}";
|
||||
else
|
||||
echo "Missing GITLAB_DEPLOY_TOKEN environment variable";
|
||||
echo "Missing GITLAB_URL or GITLAB_DEPLOY_TOKEN environment variable";
|
||||
fi;
|
||||
fi;
|
||||
echo "";
|
||||
echo "[DONE]";
|
||||
|
||||
2121
src/ACL/Login.php
2121
src/ACL/Login.php
File diff suppressed because it is too large
Load Diff
68
src/ACL/LoginUserStatus.php
Normal file
68
src/ACL/LoginUserStatus.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2024/12/12
|
||||
* DESCRIPTION:
|
||||
* ACL Login user status bitmap list
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\ACL;
|
||||
|
||||
final class LoginUserStatus
|
||||
{
|
||||
// lock status bitmap (smallint, 256)
|
||||
/** @var int enabled flag */
|
||||
public const ENABLED = 1;
|
||||
/** @var int deleted flag */
|
||||
public const DELETED = 2;
|
||||
/** @var int locked flag */
|
||||
public const LOCKED = 4;
|
||||
/** @var int banned/suspened flag [not implemented] */
|
||||
public const BANNED = 8;
|
||||
/** @var int password reset in progress [not implemented] */
|
||||
public const RESET = 16;
|
||||
/** @var int confirm/paending, eg waiting for confirm of email [not implemented] */
|
||||
public const CONFIRM = 32;
|
||||
/** @var int strict, on error lock */
|
||||
public const STRICT = 64;
|
||||
/** @var int proected, cannot delete */
|
||||
public const PROTECTED = 128;
|
||||
/** @var int master admin flag */
|
||||
public const ADMIN = 256;
|
||||
|
||||
/**
|
||||
* Returns an array mapping the numerical role values to their descriptive names
|
||||
*
|
||||
* @return array<int|string,string>
|
||||
*/
|
||||
public static function getMap()
|
||||
{
|
||||
return array_flip((new \ReflectionClass(static::class))->getConstants());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the descriptive role names
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getNames()
|
||||
{
|
||||
|
||||
return array_keys((new \ReflectionClass(static::class))->getConstants());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the numerical role values
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public static function getValues()
|
||||
{
|
||||
return array_values((new \ReflectionClass(static::class))->getConstants());
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
@@ -31,6 +31,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Admin;
|
||||
|
||||
use CoreLibs\Create\Uids;
|
||||
use CoreLibs\Convert\Json;
|
||||
|
||||
class Backend
|
||||
{
|
||||
// page name
|
||||
@@ -42,7 +45,7 @@ class Backend
|
||||
/** @var array<string> */
|
||||
public array $action_list = [
|
||||
'action', 'action_id', 'action_sub_id', 'action_yes', 'action_flag',
|
||||
'action_menu', 'action_value', 'action_error', 'action_loaded'
|
||||
'action_menu', 'action_value', 'action_type', 'action_error', 'action_loaded'
|
||||
];
|
||||
/** @var string */
|
||||
public string $action;
|
||||
@@ -61,20 +64,31 @@ class Backend
|
||||
/** @var string */
|
||||
public string $action_value;
|
||||
/** @var string */
|
||||
public string $action_type;
|
||||
/** @var string */
|
||||
public string $action_error;
|
||||
|
||||
// ACL array variable if we want to set acl data from outisde
|
||||
/** @var array<mixed> */
|
||||
public array $acl = [];
|
||||
/** @var int */
|
||||
public int $default_acl;
|
||||
|
||||
// queue key
|
||||
/** @var string */
|
||||
public string $queue_key;
|
||||
|
||||
/** @var array<string> list of allowed types for edit log write */
|
||||
private const WRITE_TYPES = ['BINARY', 'BZIP2', 'LZIP', 'STRING', 'SERIAL', 'JSON'];
|
||||
/** @var array<string> list of available write types for log */
|
||||
private array $write_types_available = [];
|
||||
|
||||
// the current active edit access id
|
||||
/** @var int|null */
|
||||
public int|null $edit_access_id;
|
||||
/** @var string */
|
||||
public string $page_name;
|
||||
|
||||
// error/warning/info messages
|
||||
/** @var array<mixed> */
|
||||
public array $messages = [];
|
||||
@@ -84,6 +98,7 @@ class Backend
|
||||
public bool $warning = false;
|
||||
/** @var bool */
|
||||
public bool $info = false;
|
||||
|
||||
// internal lang & encoding vars
|
||||
/** @var string */
|
||||
public string $lang_dir = '';
|
||||
@@ -95,6 +110,7 @@ class Backend
|
||||
public string $domain;
|
||||
/** @var string */
|
||||
public string $encoding;
|
||||
|
||||
/** @var \CoreLibs\Logging\Logging logger */
|
||||
public \CoreLibs\Logging\Logging $log;
|
||||
/** @var \CoreLibs\DB\IO database */
|
||||
@@ -103,6 +119,7 @@ class Backend
|
||||
public \CoreLibs\Language\L10n $l;
|
||||
/** @var \CoreLibs\Create\Session session class */
|
||||
public \CoreLibs\Create\Session $session;
|
||||
|
||||
// smarty publics [end processing in smarty class]
|
||||
/** @var array<mixed> */
|
||||
public array $DATA = [];
|
||||
@@ -121,14 +138,16 @@ class Backend
|
||||
* @param \CoreLibs\Logging\Logging $log Logging class
|
||||
* @param \CoreLibs\Create\Session $session Session interface class
|
||||
* @param \CoreLibs\Language\L10n $l10n l10n language class
|
||||
* @param int|null $set_default_acl_level Default ACL level
|
||||
* @param int|null $set_default_acl_level [default=null] Default ACL level
|
||||
* @param bool $init_action_vars [default=true] If the action vars should be set
|
||||
*/
|
||||
public function __construct(
|
||||
\CoreLibs\DB\IO $db,
|
||||
\CoreLibs\Logging\Logging $log,
|
||||
\CoreLibs\Create\Session $session,
|
||||
\CoreLibs\Language\L10n $l10n,
|
||||
?int $set_default_acl_level = null
|
||||
?int $set_default_acl_level = null,
|
||||
bool $init_action_vars = true
|
||||
) {
|
||||
// attach db class
|
||||
$this->db = $db;
|
||||
@@ -151,9 +170,9 @@ class Backend
|
||||
// set the page name
|
||||
$this->page_name = \CoreLibs\Get\System::getPageName();
|
||||
|
||||
// set the action ids
|
||||
foreach ($this->action_list as $_action) {
|
||||
$this->$_action = $_POST[$_action] ?? '';
|
||||
// NOTE: if any of the "action" vars are used somewhere, it is recommended to NOT set them here
|
||||
if ($init_action_vars) {
|
||||
$this->adbSetActionVars();
|
||||
}
|
||||
|
||||
if ($set_default_acl_level === null) {
|
||||
@@ -170,9 +189,12 @@ class Backend
|
||||
}
|
||||
|
||||
// queue key
|
||||
if (preg_match("/^(add|save|delete|remove|move|up|down|push_live)$/", $this->action)) {
|
||||
if (preg_match("/^(add|save|delete|remove|move|up|down|push_live)$/", $this->action ?? '')) {
|
||||
$this->queue_key = \CoreLibs\Create\RandomKey::randomKeyGen(3);
|
||||
}
|
||||
|
||||
// check what edit log data write types are allowed
|
||||
$this->adbSetEditLogWriteTypeAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +205,26 @@ class Backend
|
||||
// NO OP
|
||||
}
|
||||
|
||||
// PUBLIC METHODS |=================================================>
|
||||
// MARK: PRIVATE METHODS
|
||||
|
||||
/**
|
||||
* set the write types that are allowed
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function adbSetEditLogWriteTypeAvailable()
|
||||
{
|
||||
// check what edit log data write types are allowed
|
||||
$this->write_types_available = self::WRITE_TYPES;
|
||||
if (!function_exists('bzcompress')) {
|
||||
$this->write_types_available = array_diff($this->write_types_available, ['BINARY', 'BZIP']);
|
||||
}
|
||||
if (!function_exists('gzcompress')) {
|
||||
$this->write_types_available = array_diff($this->write_types_available, ['LZIP']);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: PUBLIC METHODS |=================================================>
|
||||
|
||||
/**
|
||||
* set internal ACL from login ACL
|
||||
@@ -195,30 +236,117 @@ class Backend
|
||||
$this->acl = $acl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current set ACL
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function adbGetAcl(): array
|
||||
{
|
||||
return $this->acl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set _POST action vars if needed
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function adbSetActionVars()
|
||||
{
|
||||
// set the action ids
|
||||
foreach ($this->action_list as $_action) {
|
||||
$this->$_action = $_POST[$_action] ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return all the action data, if not set, sets entry to null
|
||||
*
|
||||
* @return array{action:?string,action_id:null|string|int,action_sub_id:null|string|int,action_yes:null|string|int|bool,action_flag:?string,action_menu:?string,action_loaded:?string,action_value:?string,action_type:?string,action_error:?string}
|
||||
*/
|
||||
public function adbGetActionSet(): array
|
||||
{
|
||||
return [
|
||||
'action' => $this->action ?? null,
|
||||
'action_id' => $this->action_id ?? null,
|
||||
'action_sub_id' => $this->action_sub_id ?? null,
|
||||
'action_yes' => $this->action_yes ?? null,
|
||||
'action_flag' => $this->action_flag ?? null,
|
||||
'action_menu' => $this->action_menu ?? null,
|
||||
'action_loaded' => $this->action_loaded ?? null,
|
||||
'action_value' => $this->action_value ?? null,
|
||||
'action_type' => $this->action_type ?? null,
|
||||
'action_error' => $this->action_error ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* writes all action vars plus other info into edit_log table
|
||||
*
|
||||
* @param string $event any kind of event description,
|
||||
* @param string|array<mixed> $data any kind of data related to that event
|
||||
* @param string $write_type write type can bei STRING or BINARY
|
||||
* @param string|null $db_schema override target schema
|
||||
* @param string $event [default=''] any kind of event description,
|
||||
* @param string|array<mixed> $data [default=''] any kind of data related to that event
|
||||
* @param string $write_type [default=JSON] write type can be
|
||||
* JSON, STRING/SERIEAL, BINARY/BZIP or ZLIB
|
||||
* @param string|null $db_schema [default=null] override target schema
|
||||
* @return void
|
||||
* @deprecated Use $login->writeLog() and set action_set from ->adbGetActionSet()
|
||||
*/
|
||||
public function adbEditLog(
|
||||
string $event = '',
|
||||
string|array $data = '',
|
||||
string $write_type = 'STRING',
|
||||
string $write_type = 'JSON',
|
||||
?string $db_schema = null
|
||||
): void {
|
||||
$data_binary = '';
|
||||
$data_write = '';
|
||||
if ($write_type == 'BINARY') {
|
||||
$data_binary = $this->db->dbEscapeBytea((string)bzcompress(serialize($data)));
|
||||
$data_write = 'see bzip compressed data_binary field';
|
||||
// check if write type is valid, if not fallback to JSON
|
||||
if (!in_array($write_type, $this->write_types_available)) {
|
||||
$this->log->warning('Write type not in allowed array, fallback to JSON', context:[
|
||||
"write_type" => $write_type,
|
||||
"write_list" => $this->write_types_available,
|
||||
]);
|
||||
$write_type = 'JSON';
|
||||
}
|
||||
if ($write_type == 'STRING') {
|
||||
$data_binary = '';
|
||||
$data_write = $this->db->dbEscapeString(serialize($data));
|
||||
switch ($write_type) {
|
||||
case 'BINARY':
|
||||
case 'BZIP':
|
||||
$data_binary = $this->db->dbEscapeBytea((string)bzcompress(serialize($data)));
|
||||
$data_write = Json::jsonConvertArrayTo([
|
||||
'type' => 'BZIP',
|
||||
'message' => 'see bzip compressed data_binary field'
|
||||
]);
|
||||
break;
|
||||
case 'ZLIB':
|
||||
$data_binary = $this->db->dbEscapeBytea((string)gzcompress(serialize($data)));
|
||||
$data_write = Json::jsonConvertArrayTo([
|
||||
'type' => 'ZLIB',
|
||||
'message' => 'see zlib compressed data_binary field'
|
||||
]);
|
||||
break;
|
||||
case 'STRING':
|
||||
case 'SERIAL':
|
||||
$data_binary = $this->db->dbEscapeBytea(Json::jsonConvertArrayTo([
|
||||
'type' => 'SERIAL',
|
||||
'message' => 'see serial string data field'
|
||||
]));
|
||||
$data_write = serialize($data);
|
||||
break;
|
||||
case 'JSON':
|
||||
$data_binary = $this->db->dbEscapeBytea(Json::jsonConvertArrayTo([
|
||||
'type' => 'JSON',
|
||||
'message' => 'see json string data field'
|
||||
]));
|
||||
// must be converted to array
|
||||
if (!is_array($data)) {
|
||||
$data = ["data" => $data];
|
||||
}
|
||||
$data_write = Json::jsonConvertArrayTo($data);
|
||||
break;
|
||||
default:
|
||||
$this->log->alert('Invalid type for data compression was set', context:[
|
||||
"write_type" => $write_type
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
/** @var string $DB_SCHEMA check schema */
|
||||
@@ -228,44 +356,69 @@ class Backend
|
||||
} elseif (!empty($this->db->dbGetSchema())) {
|
||||
$DB_SCHEMA = $this->db->dbGetSchema();
|
||||
}
|
||||
$q = "INSERT INTO " . $DB_SCHEMA . ".edit_log "
|
||||
. "(euid, event_date, event, data, data_binary, page, "
|
||||
. "ip, user_agent, referer, script_name, query_string, server_name, http_host, "
|
||||
. "http_accept, http_accept_charset, http_accept_encoding, session_id, "
|
||||
. "action, action_id, action_yes, action_flag, action_menu, action_loaded, action_value, action_error) "
|
||||
. "VALUES "
|
||||
. "(" . $this->db->dbEscapeString(isset($_SESSION['EUID']) && is_numeric($_SESSION['EUID']) ?
|
||||
$_SESSION['EUID'] :
|
||||
'NULL')
|
||||
. ", "
|
||||
. "NOW(), "
|
||||
. "'" . $this->db->dbEscapeString((string)$event) . "', "
|
||||
. "'" . $data_write . "', "
|
||||
. "'" . $data_binary . "', "
|
||||
. "'" . $this->db->dbEscapeString((string)$this->page_name) . "', "
|
||||
. "'" . ($_SERVER["REMOTE_ADDR"] ?? '') . "', "
|
||||
. "'" . $this->db->dbEscapeString($_SERVER['HTTP_USER_AGENT'] ?? '') . "', "
|
||||
. "'" . $this->db->dbEscapeString($_SERVER['HTTP_REFERER'] ?? '') . "', "
|
||||
. "'" . $this->db->dbEscapeString($_SERVER['SCRIPT_FILENAME'] ?? '') . "', "
|
||||
. "'" . $this->db->dbEscapeString($_SERVER['QUERY_STRING'] ?? '') . "', "
|
||||
. "'" . $this->db->dbEscapeString($_SERVER['SERVER_NAME'] ?? '') . "', "
|
||||
. "'" . $this->db->dbEscapeString($_SERVER['HTTP_HOST'] ?? '') . "', "
|
||||
. "'" . $this->db->dbEscapeString($_SERVER['HTTP_ACCEPT'] ?? '') . "', "
|
||||
. "'" . $this->db->dbEscapeString($_SERVER['HTTP_ACCEPT_CHARSET'] ?? '') . "', "
|
||||
. "'" . $this->db->dbEscapeString($_SERVER['HTTP_ACCEPT_ENCODING'] ?? '') . "', "
|
||||
. ($this->session->getSessionId() === false ?
|
||||
"NULL" :
|
||||
"'" . $this->session->getSessionId() . "'")
|
||||
. ", "
|
||||
. "'" . $this->db->dbEscapeString($this->action) . "', "
|
||||
. "'" . $this->db->dbEscapeString($this->action_id) . "', "
|
||||
. "'" . $this->db->dbEscapeString($this->action_yes) . "', "
|
||||
. "'" . $this->db->dbEscapeString($this->action_flag) . "', "
|
||||
. "'" . $this->db->dbEscapeString($this->action_menu) . "', "
|
||||
. "'" . $this->db->dbEscapeString($this->action_loaded) . "', "
|
||||
. "'" . $this->db->dbEscapeString($this->action_value) . "', "
|
||||
. "'" . $this->db->dbEscapeString($this->action_error) . "')";
|
||||
$this->db->dbExec($q, 'NULL');
|
||||
$q = <<<SQL
|
||||
INSERT INTO {DB_SCHEMA}.edit_log (
|
||||
username, euid, eucuid, eucuuid, event_date, event, error, data, data_binary, page,
|
||||
ip, user_agent, referer, script_name, query_string, server_name, http_host,
|
||||
http_accept, http_accept_charset, http_accept_encoding, session_id,
|
||||
action, action_id, action_sub_id, action_yes, action_flag, action_menu, action_loaded,
|
||||
action_value, action_type, action_error
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, NOW(), $5, $6, $7, $8, $9,
|
||||
$10, $11, $12, $13, $14, $15, $16,
|
||||
$17, $18, $19, $20,
|
||||
$21, $22, $23, $24, $25, $26, $27,
|
||||
$28, $29, $30
|
||||
)
|
||||
SQL;
|
||||
$this->db->dbExecParams(
|
||||
str_replace(
|
||||
['{DB_SCHEMA}'],
|
||||
[$DB_SCHEMA],
|
||||
$q
|
||||
),
|
||||
[
|
||||
// row 1
|
||||
'',
|
||||
is_numeric($this->session->get('EUID')) ?
|
||||
$this->session->get('EUID') : null,
|
||||
is_string($this->session->get('ECUID')) ?
|
||||
$this->session->get('ECUID') : null,
|
||||
!empty($this->session->get('ECUUID')) && Uids::validateUuuidv4($this->session->get('ECUID')) ?
|
||||
$this->session->get('ECUID') : null,
|
||||
(string)$event,
|
||||
'',
|
||||
$data_write,
|
||||
$data_binary,
|
||||
(string)$this->page_name,
|
||||
// row 2
|
||||
$_SERVER["REMOTE_ADDR"] ?? '',
|
||||
$_SERVER['HTTP_USER_AGENT'] ?? '',
|
||||
$_SERVER['HTTP_REFERER'] ?? '',
|
||||
$_SERVER['SCRIPT_FILENAME'] ?? '',
|
||||
$_SERVER['QUERY_STRING'] ?? '',
|
||||
$_SERVER['SERVER_NAME'] ?? '',
|
||||
$_SERVER['HTTP_HOST'] ?? '',
|
||||
// row 3
|
||||
$_SERVER['HTTP_ACCEPT'] ?? '',
|
||||
$_SERVER['HTTP_ACCEPT_CHARSET'] ?? '',
|
||||
$_SERVER['HTTP_ACCEPT_ENCODING'] ?? '',
|
||||
$this->session->getSessionId() !== '' ?
|
||||
$this->session->getSessionId() : null,
|
||||
// row 4
|
||||
$this->action ?? '',
|
||||
$this->action_id ?? '',
|
||||
$this->action_sub_id ?? '',
|
||||
$this->action_yes ?? '',
|
||||
$this->action_flag ?? '',
|
||||
$this->action_menu ?? '',
|
||||
$this->action_loaded ?? '',
|
||||
$this->action_value ?? '',
|
||||
$this->action_type ?? '',
|
||||
$this->action_error ?? '',
|
||||
],
|
||||
'NULL'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -302,10 +455,7 @@ class Backend
|
||||
?string $set_content_path = null,
|
||||
int $flag = 0,
|
||||
): array {
|
||||
if (
|
||||
$set_content_path === null ||
|
||||
!is_string($set_content_path)
|
||||
) {
|
||||
if ($set_content_path === null) {
|
||||
/** @deprecated adbTopMenu missing set_content_path parameter */
|
||||
trigger_error(
|
||||
'Calling adbTopMenu without set_content_path parameter is deprecated',
|
||||
@@ -318,7 +468,7 @@ class Backend
|
||||
}
|
||||
|
||||
// get the session pages array
|
||||
$PAGES = $_SESSION['PAGES'] ?? null;
|
||||
$PAGES = $this->session->get('PAGES');
|
||||
if (!isset($PAGES) || !is_array($PAGES)) {
|
||||
$PAGES = [];
|
||||
}
|
||||
@@ -504,9 +654,9 @@ class Backend
|
||||
string $data,
|
||||
string $key_name,
|
||||
string $key_value,
|
||||
string $associate = null,
|
||||
string $file = null,
|
||||
string $db_schema = null,
|
||||
?string $associate = null,
|
||||
?string $file = null,
|
||||
?string $db_schema = null,
|
||||
): void {
|
||||
/** @var string $DB_SCHEMA check schema */
|
||||
$DB_SCHEMA = 'public';
|
||||
@@ -515,16 +665,30 @@ class Backend
|
||||
} elseif (!empty($this->db->dbGetSchema())) {
|
||||
$DB_SCHEMA = $this->db->dbGetSchema();
|
||||
}
|
||||
$q = "INSERT INTO " . $DB_SCHEMA . ".live_queue ("
|
||||
. "queue_key, key_value, key_name, type, target, data, group_key, action, associate, file"
|
||||
. ") VALUES ("
|
||||
. "'" . $this->db->dbEscapeString($queue_key) . "', '" . $this->db->dbEscapeString($key_value) . "', "
|
||||
. "'" . $this->db->dbEscapeString($key_name) . "', '" . $this->db->dbEscapeString($type) . "', "
|
||||
. "'" . $this->db->dbEscapeString($target) . "', '" . $this->db->dbEscapeString($data) . "', "
|
||||
. "'" . $this->queue_key . "', '" . $this->action . "', "
|
||||
. "'" . $this->db->dbEscapeString((string)$associate) . "', "
|
||||
. "'" . $this->db->dbEscapeString((string)$file) . "')";
|
||||
$this->db->dbExec($q);
|
||||
$q = <<<SQL
|
||||
INSERT INTO {DB_SCHEMA}.live_queue (
|
||||
queue_key, key_value, key_name, type,
|
||||
target, data, group_key, action, associate, file
|
||||
) VALUES (
|
||||
$1, $2, $3, $4,
|
||||
$5, $6, $7, $8, $9, $10
|
||||
)
|
||||
SQL;
|
||||
// $this->db->dbExec($q);
|
||||
$this->db->dbExecParams(
|
||||
str_replace(
|
||||
['{DB_SCHEMA}'],
|
||||
[$DB_SCHEMA],
|
||||
$q
|
||||
),
|
||||
[
|
||||
$queue_key, $key_value,
|
||||
$key_name, $type,
|
||||
$target, $data,
|
||||
$this->queue_key, $this->action,
|
||||
(string)$associate, (string)$file
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -415,8 +415,6 @@ class EditBase
|
||||
$elements[] = $this->form->formCreateElement('lock_until');
|
||||
$elements[] = $this->form->formCreateElement('lock_after');
|
||||
$elements[] = $this->form->formCreateElement('admin');
|
||||
$elements[] = $this->form->formCreateElement('debug');
|
||||
$elements[] = $this->form->formCreateElement('db_debug');
|
||||
$elements[] = $this->form->formCreateElement('edit_language_id');
|
||||
$elements[] = $this->form->formCreateElement('edit_scheme_id');
|
||||
$elements[] = $this->form->formCreateElementListTable('edit_access_user');
|
||||
|
||||
114
src/Basic.php
114
src/Basic.php
@@ -90,7 +90,7 @@ class Basic
|
||||
* @deprecated DO NOT USE Class\Basic anymore. Use dedicated logger and sub classes
|
||||
*/
|
||||
public function __construct(
|
||||
\CoreLibs\Logging\Logging $log = null,
|
||||
?\CoreLibs\Logging\Logging $log = null,
|
||||
?string $session_name = null
|
||||
) {
|
||||
trigger_error('Class \CoreLibs\Basic is deprected', E_USER_DEPRECATED);
|
||||
@@ -1139,118 +1139,6 @@ class Basic
|
||||
|
||||
// *** BETTER PASSWORD OPTIONS END ***
|
||||
|
||||
// *** COLORS ***
|
||||
// [!!! DEPRECATED !!!]
|
||||
// moved to \CoreLibs\Convert\Colors
|
||||
|
||||
/**
|
||||
* converts a hex RGB color to the int numbers
|
||||
* @param string $hexStr RGB hexstring
|
||||
* @param bool $returnAsString flag to return as string
|
||||
* @param string $seperator string seperator: default: ","
|
||||
* @return string|array<mixed>|bool false on error or array with RGB or
|
||||
* a string with the seperator
|
||||
* @deprecated use \CoreLibs\Convert\Colors::hex2rgb() instead
|
||||
*/
|
||||
public static function hex2rgb(string $hexStr, bool $returnAsString = false, string $seperator = ',')
|
||||
{
|
||||
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::hex2rgb()', E_USER_DEPRECATED);
|
||||
return \CoreLibs\Convert\Colors::hex2rgb($hexStr, $returnAsString, $seperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* converts the rgb values from int data to the valid rgb html hex string
|
||||
* optional can turn of leading #
|
||||
* @param int $red red 0-255
|
||||
* @param int $green green 0-255
|
||||
* @param int $blue blue 0-255
|
||||
* @param bool $hex_prefix default true, prefix with "#"
|
||||
* @return string|bool rgb in hex values with leading # if set
|
||||
* @deprecated use \CoreLibs\Convert\Colors::rgb2hex() instead
|
||||
*/
|
||||
public static function rgb2hex(int $red, int $green, int $blue, bool $hex_prefix = true)
|
||||
{
|
||||
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::rgb2hex()', E_USER_DEPRECATED);
|
||||
return \CoreLibs\Convert\Colors::rgb2hex($red, $green, $blue, $hex_prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
* converts and int RGB to the HTML color string in hex format
|
||||
* @param int $red red 0-255
|
||||
* @param int $green green 0-255
|
||||
* @param int $blue blue 0-255
|
||||
* @return string|bool hex rgb string
|
||||
* @deprecated use rgb2hex instead
|
||||
*/
|
||||
public static function rgb2html(int $red, int $green, int $blue)
|
||||
{
|
||||
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::rgb2hex()', E_USER_DEPRECATED);
|
||||
// check that each color is between 0 and 255
|
||||
return \CoreLibs\Convert\Colors::rgb2hex($red, $green, $blue, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* converts RGB to HSB/V values
|
||||
* returns:
|
||||
* array with hue (0-360), sat (0-100%), brightness/value (0-100%)
|
||||
* @param int $red red 0-255
|
||||
* @param int $green green 0-255
|
||||
* @param int $blue blue 0-255
|
||||
* @return array<mixed>|bool Hue, Sat, Brightness/Value
|
||||
* @deprecated use \CoreLibs\Convert\Colors::rgb2hsb() instead
|
||||
*/
|
||||
public static function rgb2hsb(int $red, int $green, int $blue)
|
||||
{
|
||||
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::rgb2hsb()', E_USER_DEPRECATED);
|
||||
return \CoreLibs\Convert\Colors::rgb2hsb($red, $green, $blue);
|
||||
}
|
||||
|
||||
/**
|
||||
* converts HSB/V to RGB values RGB is full INT
|
||||
* @param int $H hue 0-360
|
||||
* @param float $S saturation 0-1 (float)
|
||||
* @param float $V brightness/value 0-1 (float)
|
||||
* @return array<mixed>|bool 0 red/1 green/2 blue array
|
||||
* @deprecated use \CoreLibs\Convert\Colors::hsb2rgb() instead
|
||||
*/
|
||||
public static function hsb2rgb(int $H, float $S, float $V)
|
||||
{
|
||||
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::hsb2rgb()', E_USER_DEPRECATED);
|
||||
return \CoreLibs\Convert\Colors::hsb2rgb($H, (int)round($S * 100), (int)round($V * 100));
|
||||
}
|
||||
|
||||
/**
|
||||
* converts a RGB (0-255) to HSL
|
||||
* return:
|
||||
* array with hue (0-360), saturation (0-100%) and luminance (0-100%)
|
||||
* @param int $r red 0-255
|
||||
* @param int $g green 0-255
|
||||
* @param int $b blue 0-255
|
||||
* @return array<mixed>|bool hue/sat/luminance
|
||||
* @deprecated use \CoreLibs\Convert\Colors::rgb2hsl() instead
|
||||
*/
|
||||
public static function rgb2hsl(int $r, int $g, int $b)
|
||||
{
|
||||
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::rgb2hsl()', E_USER_DEPRECATED);
|
||||
return \CoreLibs\Convert\Colors::rgb2hsb($r, $g, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* converts an HSL to RGB
|
||||
* @param int $h hue: 0-360 (degrees)
|
||||
* @param float $s saturation: 0-1
|
||||
* @param float $l luminance: 0-1
|
||||
* @return array<mixed>|bool red/blue/green 0-255 each
|
||||
* @deprecated use \CoreLibs\Convert\Colors::hsl2rgb() instead
|
||||
*/
|
||||
public static function hsl2rgb(int $h, float $s, float $l)
|
||||
{
|
||||
trigger_error('Method ' . __METHOD__ . ' is deprecated, use \CoreLibs\Convert\Colors::hsl2rgb()', E_USER_DEPRECATED);
|
||||
return \CoreLibs\Convert\Colors::hsl2rgb($h, $s * 100, $l * 100);
|
||||
}
|
||||
|
||||
// *** COLORS END ***
|
||||
|
||||
// *** EMAIL FUNCTIONS ***
|
||||
// [!!! DEPRECATED !!!]
|
||||
// Moved to \CoreLibs\Check\Email
|
||||
|
||||
@@ -119,6 +119,13 @@ class Colors
|
||||
|
||||
/**
|
||||
* check if html/css color string is valid
|
||||
*
|
||||
* TODO: update check for correct validate values
|
||||
* - space instead of ","
|
||||
* - / opcatiy checks
|
||||
* - loose numeric values
|
||||
* - lab/lch,oklab/oklch validation too
|
||||
*
|
||||
* @param string $color A color string of any format
|
||||
* @param int $flags defaults to ALL, else use | to combined from
|
||||
* HEX_RGB, HEX_RGBA, RGB, RGBA, HSL, HSLA
|
||||
@@ -168,9 +175,9 @@ class Colors
|
||||
if (preg_match("/$regex/", $color)) {
|
||||
// if valid regex, we now need to check if the content is actually valid
|
||||
// only for rgb/hsl type
|
||||
/** @var int|false */
|
||||
/** @var int<0, max>|false */
|
||||
$rgb_flag = strpos($color, 'rgb');
|
||||
/** @var int|false */
|
||||
/** @var int<0, max>|false */
|
||||
$hsl_flag = strpos($color, 'hsl');
|
||||
// if both not match, return true
|
||||
if (
|
||||
|
||||
@@ -51,6 +51,23 @@ class File
|
||||
// return lines in file
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the mime type of a file via finfo
|
||||
* if file not found, throws exception
|
||||
* else returns '' for any other finfo read problem
|
||||
*
|
||||
* @param string $read_file File to read, relative or absolute path
|
||||
* @return string
|
||||
*/
|
||||
public static function getMimeType(string $read_file): string
|
||||
{
|
||||
$finfo = new \finfo(FILEINFO_MIME_TYPE);
|
||||
if (!is_file($read_file)) {
|
||||
throw new \UnexpectedValueException('[getMimeType] File not found: ' . $read_file);
|
||||
}
|
||||
return $finfo->file($read_file) ?: '';
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
|
||||
@@ -509,6 +509,48 @@ class ArrayHandler
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove entries from a simple array, will not keep key order
|
||||
*
|
||||
* any array content is allowed
|
||||
*
|
||||
* https://stackoverflow.com/a/369608
|
||||
*
|
||||
* @param array<mixed> $array Array where elements are located
|
||||
* @param array<mixed> $remove Elements to remove
|
||||
* @return array<mixed> Array with $remove elements removed
|
||||
*/
|
||||
public static function arrayRemoveEntry(array $array, array $remove): array
|
||||
{
|
||||
return array_diff($array, $remove);
|
||||
}
|
||||
|
||||
/**
|
||||
* From the array with key -> mixed values,
|
||||
* return only the entries where the key matches the key given in the key list parameter
|
||||
*
|
||||
* key list is a list[string]
|
||||
* if key list is empty, return array as is
|
||||
*
|
||||
* @param array<string,mixed> $array
|
||||
* @param array<string> $key_list
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function arrayReturnMatchingKeyOnly(
|
||||
array $array,
|
||||
array $key_list
|
||||
): array {
|
||||
// on empty return as is
|
||||
if (empty($key_list)) {
|
||||
return $array;
|
||||
}
|
||||
return array_filter(
|
||||
$array,
|
||||
fn($key) => in_array($key, $key_list),
|
||||
ARRAY_FILTER_USE_KEY
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
|
||||
359
src/Convert/Color/CieXyz.php
Normal file
359
src/Convert/Color/CieXyz.php
Normal file
@@ -0,0 +1,359 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2024/11/12
|
||||
* DESCRIPTION:
|
||||
* CIE XYZ color space conversion
|
||||
* This for various interims work
|
||||
* none public
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Convert\Color;
|
||||
|
||||
use CoreLibs\Convert\Math;
|
||||
use CoreLibs\Convert\Color\Coordinates\RGB;
|
||||
use CoreLibs\Convert\Color\Coordinates\Lab;
|
||||
use CoreLibs\Convert\Color\Coordinates\XYZ;
|
||||
|
||||
class CieXyz
|
||||
{
|
||||
// MARK: public wrapper functions
|
||||
|
||||
/**
|
||||
* Convert from RGB to OkLab
|
||||
* via xyz D65
|
||||
*
|
||||
* @param RGB $rgb
|
||||
* @return Lab
|
||||
*/
|
||||
public static function rgbViaXyzD65ToOkLab(RGB $rgb): Lab
|
||||
{
|
||||
return self::xyzD65ToOkLab(
|
||||
self::linRgbToXyzD65($rgb)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convet from OkLab to RGB
|
||||
* via xyz D65
|
||||
*
|
||||
* @param Lab $lab
|
||||
* @return RGB
|
||||
*/
|
||||
public static function okLabViaXyzD65ToRgb(Lab $lab): RGB
|
||||
{
|
||||
return self::xyzD65ToLinRgb(
|
||||
self::okLabToXyzD65($lab)
|
||||
)->fromLinear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert RGB to CIE Lab
|
||||
* via xyz D65 to xyz D50
|
||||
*
|
||||
* @param RGB $rgb
|
||||
* @return Lab
|
||||
*/
|
||||
public static function rgbViaXyzD65ViaXyzD50ToLab(RGB $rgb): Lab
|
||||
{
|
||||
return self::xyzD50ToLab(
|
||||
self::xyzD65ToXyzD50(
|
||||
self::linRgbToXyzD65($rgb)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert CIE Lab to RGB
|
||||
* via xyz D50 to xyz D65
|
||||
*
|
||||
* @param Lab $lab
|
||||
* @return RGB
|
||||
*/
|
||||
public static function labViaXyzD50ViaXyzD65ToRgb(Lab $lab): RGB
|
||||
{
|
||||
return self::xyzD65ToLinRgb(
|
||||
self::xyzD50ToXyxD65(
|
||||
self::labToXyzD50($lab)
|
||||
)
|
||||
)->fromLinear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from oklab to cie lab
|
||||
*
|
||||
* @param Lab $lab
|
||||
* @return Lab
|
||||
*/
|
||||
public static function okLabViaXyzD65ViaXyzD50ToLab(Lab $lab): Lab
|
||||
{
|
||||
return self::xyzD50ToLab(
|
||||
self::xyzD65ToXyzD50(
|
||||
self::okLabToXyzD65($lab)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from cie lab to oklab
|
||||
*
|
||||
* @param Lab $lab
|
||||
* @return Lab
|
||||
*/
|
||||
public static function labViaXyzD50ViaXyzD65ToOkLab(Lab $lab): Lab
|
||||
{
|
||||
return self::xyzD65ToOkLab(
|
||||
self::xyzD50ToXyxD65(
|
||||
self::labToXyzD50($lab)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// MARK: helper convert any array to array{float, float, float}
|
||||
|
||||
/**
|
||||
* This is a hack for phpstan until we write a proper matrix to class
|
||||
* conversion wrapper function
|
||||
*
|
||||
* @param array<array<float|int>|float|int> $_array
|
||||
* @return array{0:float,1:float,2:float}
|
||||
*/
|
||||
private static function convertArray(array $_array): array
|
||||
{
|
||||
/** @var array{0:float,1:float,2:float} */
|
||||
return [$_array[0], $_array[1], $_array[2]];
|
||||
}
|
||||
|
||||
// MARK: xyzD65 <-> xyzD50
|
||||
|
||||
/**
|
||||
* xyzD65 to xyzD50 whitepoint
|
||||
*
|
||||
* @param XYZ $xyz
|
||||
* @return XYZ
|
||||
*/
|
||||
private static function xyzD65ToXyzD50(XYZ $xyz): XYZ
|
||||
{
|
||||
return new XYZ(self::convertArray(Math::multiplyMatrices(
|
||||
a: [
|
||||
[1.0479298208405488, 0.022946793341019088, -0.05019222954313557],
|
||||
[0.029627815688159344, 0.990434484573249, -0.01707382502938514],
|
||||
[-0.009243058152591178, 0.015055144896577895, 0.7518742899580008],
|
||||
],
|
||||
b: $xyz->returnAsArray(),
|
||||
)), options: ["whitepoint" => 'D50']);
|
||||
}
|
||||
|
||||
/**
|
||||
* xyzD50 to xyzD65 whitepoint
|
||||
*
|
||||
* @param XYZ $xyz
|
||||
* @return XYZ
|
||||
*/
|
||||
private static function xyzD50ToXyxD65(XYZ $xyz): XYZ
|
||||
{
|
||||
return new XYZ(self::convertArray(Math::multiplyMatrices(
|
||||
a: [
|
||||
[0.9554734527042182, -0.023098536874261423, 0.0632593086610217],
|
||||
[-0.028369706963208136, 1.0099954580058226, 0.021041398966943008],
|
||||
[0.012314001688319899, -0.020507696433477912, 1.3303659366080753],
|
||||
],
|
||||
b: $xyz->returnAsArray()
|
||||
)), options: ["whitepoint" => 'D65']);
|
||||
}
|
||||
|
||||
// MARK: xyzD50 <-> Lab
|
||||
|
||||
/**
|
||||
* Convert xyzD50 to Lab (Cie)
|
||||
*
|
||||
* @param XYZ $xyz
|
||||
* @return Lab
|
||||
*/
|
||||
private static function xyzD50ToLab(XYZ $xyz): Lab
|
||||
{
|
||||
$_xyz = $xyz->returnAsArray();
|
||||
$d50 = [
|
||||
0.3457 / 0.3585,
|
||||
1.00000,
|
||||
(1.0 - 0.3457 - 0.3585) / 0.3585,
|
||||
];
|
||||
|
||||
$a = 216 / 24389;
|
||||
$b = 24389 / 27;
|
||||
|
||||
$_xyz = array_map(
|
||||
fn ($k, $v) => $v / $d50[$k],
|
||||
array_keys($_xyz),
|
||||
array_values($_xyz),
|
||||
);
|
||||
|
||||
$f = array_map(
|
||||
fn ($v) => (($v > $a) ?
|
||||
pow($v, 1 / 3) :
|
||||
(($b * $v + 16) / 116)
|
||||
),
|
||||
$_xyz,
|
||||
);
|
||||
|
||||
return new Lab([
|
||||
(116 * $f[1]) - 16,
|
||||
500 * ($f[0] - $f[1]),
|
||||
200 * ($f[1] - $f[2]),
|
||||
], colorspace: 'CIELab');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Lab (Cie) to xyz D50
|
||||
*
|
||||
* @param Lab $lab
|
||||
* @return XYZ
|
||||
*/
|
||||
private static function labToXyzD50(Lab $lab): XYZ
|
||||
{
|
||||
$_lab = $lab->returnAsArray();
|
||||
$a = 24389 / 27;
|
||||
$b = 216 / 24389;
|
||||
$f = [];
|
||||
$f[1] = ($_lab[0] + 16) / 116;
|
||||
$f[0] = $_lab[1] / 500 + $f[1];
|
||||
$f[2] = $f[1] - $_lab[2] / 200;
|
||||
$xyz = [
|
||||
// x
|
||||
pow($f[0], 3) > $b ?
|
||||
pow($f[0], 3) :
|
||||
(116 * $f[0] - 16) / $a,
|
||||
// y
|
||||
$_lab[0] > $a * $b ?
|
||||
pow(($_lab[0] + 16) / 116, 3) :
|
||||
$_lab[0] / $a,
|
||||
// z
|
||||
pow($f[2], 3) > $b ?
|
||||
pow($f[2], 3) :
|
||||
(116 * $f[2] - 16) / $a,
|
||||
];
|
||||
|
||||
$d50 = [
|
||||
0.3457 / 0.3585,
|
||||
1.00000,
|
||||
(1.0 - 0.3457 - 0.3585) / 0.3585,
|
||||
];
|
||||
|
||||
return new XYZ(
|
||||
self::convertArray(array_map(
|
||||
fn ($k, $v) => $v * $d50[$k],
|
||||
array_keys($xyz),
|
||||
$xyz,
|
||||
)),
|
||||
options: ["whitepoint" => 'D50']
|
||||
);
|
||||
}
|
||||
|
||||
// MARK: xyzD65 <-> (linear)RGB
|
||||
|
||||
/**
|
||||
* convert linear RGB to xyz D65
|
||||
* if rgb is not flagged linear, it will be auto converted
|
||||
*
|
||||
* @param RGB $rgb
|
||||
* @return XYZ
|
||||
*/
|
||||
private static function linRgbToXyzD65(RGB $rgb): XYZ
|
||||
{
|
||||
// if not linear, convert to linear
|
||||
if (!(bool)$rgb->get('linear')) {
|
||||
$rgb = (new RGB($rgb->returnAsArray()))->toLinear();
|
||||
}
|
||||
return new XYZ(self::convertArray(Math::multiplyMatrices(
|
||||
[
|
||||
[0.41239079926595934, 0.357584339383878, 0.1804807884018343],
|
||||
[0.21263900587151027, 0.715168678767756, 0.07219231536073371],
|
||||
[0.01933081871559182, 0.11919477979462598, 0.9505321522496607],
|
||||
],
|
||||
$rgb->returnAsArray()
|
||||
)), options: ["whitepoint" => 'D65']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert xyz D65 to linear RGB
|
||||
*
|
||||
* @param XYZ $xyz
|
||||
* @return RGB
|
||||
*/
|
||||
private static function xyzD65ToLinRgb(XYZ $xyz): RGB
|
||||
{
|
||||
// xyz D65 to linrgb
|
||||
return new RGB(self::convertArray(Math::multiplyMatrices(
|
||||
a : [
|
||||
[ 3.2409699419045226, -1.537383177570094, -0.4986107602930034 ],
|
||||
[ -0.9692436362808796, 1.8759675015077202, 0.04155505740717559 ],
|
||||
[ 0.05563007969699366, -0.20397695888897652, 1.0569715142428786 ],
|
||||
],
|
||||
b : $xyz->returnAsArray()
|
||||
)), options: ["linear" => true]);
|
||||
}
|
||||
|
||||
// MARK: xyzD65 <-> OkLab
|
||||
|
||||
/**
|
||||
* xyz D65 to OkLab
|
||||
*
|
||||
* @param XYZ $xyz
|
||||
* @return Lab
|
||||
*/
|
||||
private static function xyzD65ToOkLab(XYZ $xyz): Lab
|
||||
{
|
||||
return new Lab(self::convertArray(Math::multiplyMatrices(
|
||||
[
|
||||
[0.2104542553, 0.7936177850, -0.0040720468],
|
||||
[1.9779984951, -2.4285922050, 0.4505937099],
|
||||
[0.0259040371, 0.7827717662, -0.8086757660],
|
||||
],
|
||||
array_map(
|
||||
callback: fn ($v) => pow((float)$v, 1 / 3),
|
||||
array: Math::multiplyMatrices(
|
||||
a: [
|
||||
[0.8190224432164319, 0.3619062562801221, -0.12887378261216414],
|
||||
[0.0329836671980271, 0.9292868468965546, 0.03614466816999844],
|
||||
[0.048177199566046255, 0.26423952494422764, 0.6335478258136937],
|
||||
],
|
||||
b: $xyz->returnAsArray(),
|
||||
),
|
||||
)
|
||||
)), colorspace: 'OkLab');
|
||||
}
|
||||
|
||||
/**
|
||||
* xyz D65 to OkLab
|
||||
*
|
||||
* @param Lab $lab
|
||||
* @return XYZ
|
||||
*/
|
||||
private static function okLabToXyzD65(Lab $lab): XYZ
|
||||
{
|
||||
return new XYZ(self::convertArray(Math::multiplyMatrices(
|
||||
a: [
|
||||
[1.2268798733741557, -0.5578149965554813, 0.28139105017721583],
|
||||
[-0.04057576262431372, 1.1122868293970594, -0.07171106666151701],
|
||||
[-0.07637294974672142, -0.4214933239627914, 1.5869240244272418],
|
||||
],
|
||||
b: array_map(
|
||||
callback: fn ($v) => is_numeric($v) ? $v ** 3 : 0,
|
||||
array: Math::multiplyMatrices(
|
||||
a: [
|
||||
[0.99999999845051981432, 0.39633779217376785678, 0.21580375806075880339],
|
||||
[1.0000000088817607767, -0.1055613423236563494, -0.063854174771705903402],
|
||||
[1.0000000546724109177, -0.089484182094965759684, -1.2914855378640917399],
|
||||
],
|
||||
// Divide $lightness by 100 to convert from CSS OkLab
|
||||
b: $lab->returnAsArray(),
|
||||
),
|
||||
),
|
||||
)), options: ["whitepoint" => 'D65']);
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
1103
src/Convert/Color/Color.php
Normal file
1103
src/Convert/Color/Color.php
Normal file
File diff suppressed because it is too large
Load Diff
190
src/Convert/Color/Coordinates/HSB.php
Normal file
190
src/Convert/Color/Coordinates/HSB.php
Normal file
@@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2024/11/11
|
||||
* DESCRIPTION:
|
||||
* Color Coordinate: HSB/HSV
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Convert\Color\Coordinates;
|
||||
|
||||
use CoreLibs\Convert\Color\Utils;
|
||||
|
||||
class HSB implements Interface\CoordinatesInterface
|
||||
{
|
||||
/** @var array<string> allowed colorspaces */
|
||||
private const COLORSPACES = ['sRGB'];
|
||||
|
||||
/** @var float hue */
|
||||
private float $H = 0.0;
|
||||
/** @var float saturation */
|
||||
private float $S = 0.0;
|
||||
/** @var float brightness / value */
|
||||
private float $B = 0.0;
|
||||
|
||||
/** @var string color space: either ok or cie */
|
||||
private string $colorspace = ''; /** @phpstan-ignore-line */
|
||||
|
||||
/**
|
||||
* HSB (HSV) color coordinates
|
||||
* Hue/Saturation/Brightness or Value
|
||||
*
|
||||
* @param string|array{0:float,1:float,2:float} $colors
|
||||
* @param string $colorspace [default=sRGB]
|
||||
* @param array<string,string> $options [default=[]]
|
||||
* @throws \InvalidArgumentException only array colors allowed
|
||||
*/
|
||||
public function __construct(string|array $colors, string $colorspace = 'sRGB', array $options = [])
|
||||
{
|
||||
if (!is_array($colors)) {
|
||||
throw new \InvalidArgumentException('Only array colors allowed', 0);
|
||||
}
|
||||
$this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* set from array
|
||||
* where 0: Hue, 1: Saturation, 2: Brightness
|
||||
*
|
||||
* @param string|array{0:float,1:float,2:float} $colors
|
||||
* @param string $colorspace [default=sRGB]
|
||||
* @param array<string,string> $options [default=[]]
|
||||
* @return self
|
||||
*/
|
||||
public static function create(string|array $colors, string $colorspace = 'sRGB', array $options = []): self
|
||||
{
|
||||
return new HSB($colors, $colorspace, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* parse options
|
||||
*
|
||||
* @param array<string,string> $options
|
||||
* @return self
|
||||
*/
|
||||
private function parseOptions(array $options): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set color
|
||||
*
|
||||
* @param string $name
|
||||
* @param float $value
|
||||
* @return void
|
||||
*/
|
||||
private function set(string $name, float $value): void
|
||||
{
|
||||
$name = strtoupper($name);
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new \ErrorException('Creation of dynamic property is not allowed', 0);
|
||||
}
|
||||
switch ($name) {
|
||||
case 'H':
|
||||
if ($value == 360.0) {
|
||||
$value = 0;
|
||||
}
|
||||
// if ($value < 0 || $value > 360) {
|
||||
if (Utils::compare(0.0, $value, 360.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for hue is not in the range of 0 to 360',
|
||||
1
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'S':
|
||||
// if ($value < 0 || $value > 100) {
|
||||
if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for saturation is not in the range of 0 to 100',
|
||||
2
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'B':
|
||||
// if ($value < 0 || $value > 100) {
|
||||
if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for brightness is not in the range of 0 to 100',
|
||||
3
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
$this->$name = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* get color
|
||||
*
|
||||
* @param string $name
|
||||
* @return float
|
||||
*/
|
||||
public function get(string $name): float|string|bool
|
||||
{
|
||||
$name = strtoupper($name);
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new \ErrorException('Creation of dynamic property is not allowed', 0);
|
||||
}
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the colorspace
|
||||
*
|
||||
* @param string $colorspace
|
||||
* @return self
|
||||
*/
|
||||
private function setColorspace(string $colorspace): self
|
||||
{
|
||||
if (!in_array($colorspace, $this::COLORSPACES)) {
|
||||
throw new \InvalidArgumentException('Not allowed colorspace', 0);
|
||||
}
|
||||
$this->colorspace = $colorspace;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color as array
|
||||
* where 0: Hue, 1: Saturation, 2: Brightness
|
||||
*
|
||||
* @return array{0:float,1:float,2:float}
|
||||
*/
|
||||
public function returnAsArray(): array
|
||||
{
|
||||
return [$this->H, $this->S, $this->B];
|
||||
}
|
||||
|
||||
/**
|
||||
* set color as array
|
||||
* where 0: Hue, 1: Saturation, 2: Brightness
|
||||
*
|
||||
* @param array{0:float,1:float,2:float} $colors
|
||||
* @return self
|
||||
*/
|
||||
private function setFromArray(array $colors): self
|
||||
{
|
||||
$this->set('H', $colors[0]);
|
||||
$this->set('S', $colors[1]);
|
||||
$this->set('B', $colors[2]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* no hsb in css
|
||||
*
|
||||
* @param float|string|null $opacity
|
||||
* @return string
|
||||
* @throws \ErrorException
|
||||
*/
|
||||
public function toCssString(null|float|string $opacity = null): string
|
||||
{
|
||||
throw new \ErrorException('HSB is not available as CSS color string', 0);
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
195
src/Convert/Color/Coordinates/HSL.php
Normal file
195
src/Convert/Color/Coordinates/HSL.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2024/11/11
|
||||
* DESCRIPTION:
|
||||
* Color Coordinate: HSL
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Convert\Color\Coordinates;
|
||||
|
||||
use CoreLibs\Convert\Color\Utils;
|
||||
|
||||
class HSL implements Interface\CoordinatesInterface
|
||||
{
|
||||
/** @var array<string> allowed colorspaces */
|
||||
private const COLORSPACES = ['sRGB'];
|
||||
|
||||
/** @var float hue */
|
||||
private float $H = 0.0;
|
||||
/** @var float saturation */
|
||||
private float $S = 0.0;
|
||||
/** @var float lightness (luminance) */
|
||||
private float $L = 0.0;
|
||||
|
||||
/** @var string color space: either sRGB */
|
||||
private string $colorspace = '';
|
||||
|
||||
/**
|
||||
* Color Coordinate HSL
|
||||
* Hue/Saturation/Lightness
|
||||
*
|
||||
* @param string|array{0:float,1:float,2:float} $colors
|
||||
* @param string $colorspace [default=sRGB]
|
||||
* @param array<string,string> $options [default=[]]
|
||||
* @throws \InvalidArgumentException only array colors allowed
|
||||
*/
|
||||
public function __construct(string|array $colors, string $colorspace = 'sRGB', array $options = [])
|
||||
{
|
||||
if (!is_array($colors)) {
|
||||
throw new \InvalidArgumentException('Only array colors allowed', 0);
|
||||
}
|
||||
$this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* set from array
|
||||
* where 0: Hue, 1: Saturation, 2: Lightness
|
||||
*
|
||||
* @param string|array{0:float,1:float,2:float} $colors
|
||||
* @param string $colorspace [default=sRGB]
|
||||
* @param array<string,string> $options [default=[]]
|
||||
* @return self
|
||||
*/
|
||||
public static function create(string|array $colors, string $colorspace = 'sRGB', array $options = []): self
|
||||
{
|
||||
return new HSL($colors, $colorspace, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* parse options
|
||||
*
|
||||
* @param array<string,string> $options
|
||||
* @return self
|
||||
*/
|
||||
private function parseOptions(array $options): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set color
|
||||
*
|
||||
* @param string $name
|
||||
* @param float $value
|
||||
* @return void
|
||||
*/
|
||||
private function set(string $name, float $value): void
|
||||
{
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new \ErrorException('Creation of dynamic property is not allowed', 0);
|
||||
}
|
||||
switch ($name) {
|
||||
case 'H':
|
||||
if ($value == 360.0) {
|
||||
$value = 0;
|
||||
}
|
||||
// if ($value < 0 || $value > 360) {
|
||||
if (Utils::compare(0.0, $value, 360.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for hue is not in the range of 0 to 360',
|
||||
1
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'S':
|
||||
// if ($value < 0 || $value > 100) {
|
||||
if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for saturation is not in the range of 0 to 100',
|
||||
2
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'L':
|
||||
// if ($value < 0 || $value > 100) {
|
||||
if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for lightness is not in the range of 0 to 100',
|
||||
3
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
$this->$name = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* get color
|
||||
*
|
||||
* @param string $name
|
||||
* @return float
|
||||
*/
|
||||
public function get(string $name): float|string|bool
|
||||
{
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new \ErrorException('Creation of dynamic property is not allowed', 0);
|
||||
}
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the colorspace
|
||||
*
|
||||
* @param string $colorspace
|
||||
* @return self
|
||||
*/
|
||||
private function setColorspace(string $colorspace): self
|
||||
{
|
||||
if (!in_array($colorspace, $this::COLORSPACES)) {
|
||||
throw new \InvalidArgumentException('Not allowed colorspace', 0);
|
||||
}
|
||||
$this->colorspace = $colorspace;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color as array
|
||||
* where 0: Hue, 1: Saturation, 2: Lightness
|
||||
*
|
||||
* @return array{0:float,1:float,2:float}
|
||||
*/
|
||||
public function returnAsArray(): array
|
||||
{
|
||||
return [$this->H, $this->S, $this->L];
|
||||
}
|
||||
|
||||
/**
|
||||
* set color as array
|
||||
* where 0: Hue, 1: Saturation, 2: Lightness
|
||||
*
|
||||
* @param array{0:float,1:float,2:float} $colors
|
||||
* @return self
|
||||
*/
|
||||
private function setFromArray(array $colors): self
|
||||
{
|
||||
$this->set('H', $colors[0]);
|
||||
$this->set('S', $colors[1]);
|
||||
$this->set('L', $colors[2]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert to css string with optional opacityt
|
||||
*
|
||||
* @param float|string|null $opacity
|
||||
* @return string
|
||||
*/
|
||||
public function toCssString(null|float|string $opacity = null): string
|
||||
{
|
||||
$string = 'hsl('
|
||||
. $this->H
|
||||
. ' '
|
||||
. $this->S
|
||||
. ' '
|
||||
. $this->L
|
||||
. Utils::setOpacity($opacity)
|
||||
. ')';
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
195
src/Convert/Color/Coordinates/HWB.php
Normal file
195
src/Convert/Color/Coordinates/HWB.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2024/11/11
|
||||
* DESCRIPTION:
|
||||
* Color Coordinate: HWB
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Convert\Color\Coordinates;
|
||||
|
||||
use CoreLibs\Convert\Color\Utils;
|
||||
|
||||
class HWB implements Interface\CoordinatesInterface
|
||||
{
|
||||
/** @var array<string> allowed colorspaces */
|
||||
private const COLORSPACES = ['sRGB'];
|
||||
|
||||
/** @var float Hue */
|
||||
private float $H = 0.0;
|
||||
/** @var float Whiteness */
|
||||
private float $W = 0.0;
|
||||
/** @var float Blackness */
|
||||
private float $B = 0.0;
|
||||
|
||||
/** @var string color space: either ok or cie */
|
||||
private string $colorspace = '';
|
||||
|
||||
/**
|
||||
* Color Coordinate: HWB
|
||||
* Hue/Whiteness/Blackness
|
||||
*
|
||||
* @param string|array{0:float,1:float,2:float} $colors
|
||||
* @param string $colorspace [default=sRGB]
|
||||
* @param array<string,string> $options [default=[]]
|
||||
* @throws \InvalidArgumentException only array colors allowed
|
||||
*/
|
||||
public function __construct(string|array $colors, string $colorspace = 'sRGB', array $options = [])
|
||||
{
|
||||
if (!is_array($colors)) {
|
||||
throw new \InvalidArgumentException('Only array colors allowed', 0);
|
||||
}
|
||||
$this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* set from array
|
||||
* where 0: Hue, 1: Whiteness, 2: Blackness
|
||||
*
|
||||
* @param string|array{0:float,1:float,2:float} $colors
|
||||
* @param string $colorspace [default=sRGB]
|
||||
* @param array<string,string> $options [default=[]]
|
||||
* @return self
|
||||
*/
|
||||
public static function create(string|array $colors, string $colorspace = 'sRGB', array $options = []): self
|
||||
{
|
||||
return new HWB($colors, $colorspace, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* parse options
|
||||
*
|
||||
* @param array<string,string> $options
|
||||
* @return self
|
||||
*/
|
||||
private function parseOptions(array $options): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set color
|
||||
*
|
||||
* @param string $name
|
||||
* @param float $value
|
||||
* @return void
|
||||
*/
|
||||
private function set(string $name, float $value): void
|
||||
{
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new \ErrorException('Creation of dynamic property is not allowed', 0);
|
||||
}
|
||||
switch ($name) {
|
||||
case 'H':
|
||||
if ($value == 360.0) {
|
||||
$value = 0;
|
||||
}
|
||||
// if ($value < 0 || $value > 360) {
|
||||
if (Utils::compare(0.0, $value, 360.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for hue is not in the range of 0 to 360',
|
||||
1
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'W':
|
||||
// if ($value < 0 || $value > 100) {
|
||||
if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for whiteness is not in the range of 0 to 100',
|
||||
2
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'B':
|
||||
// if ($value < 0 || $value > 100) {
|
||||
if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for blackness is not in the range of 0 to 100',
|
||||
3
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
$this->$name = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* get color
|
||||
*
|
||||
* @param string $name
|
||||
* @return float
|
||||
*/
|
||||
public function get(string $name): float|string|bool
|
||||
{
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new \ErrorException('Creation of dynamic property is not allowed', 0);
|
||||
}
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the colorspace
|
||||
*
|
||||
* @param string $colorspace
|
||||
* @return self
|
||||
*/
|
||||
private function setColorspace(string $colorspace): self
|
||||
{
|
||||
if (!in_array($colorspace, $this::COLORSPACES)) {
|
||||
throw new \InvalidArgumentException('Not allowed colorspace', 0);
|
||||
}
|
||||
$this->colorspace = $colorspace;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color as array
|
||||
* where 0: Hue, 1: Whiteness, 2: Blackness
|
||||
*
|
||||
* @return array{0:float,1:float,2:float}
|
||||
*/
|
||||
public function returnAsArray(): array
|
||||
{
|
||||
return [$this->H, $this->W, $this->B];
|
||||
}
|
||||
|
||||
/**
|
||||
* set color as array
|
||||
* where 0: Hue, 1: Whiteness, 2: Blackness
|
||||
*
|
||||
* @param array{0:float,1:float,2:float} $colors
|
||||
* @return self
|
||||
*/
|
||||
private function setFromArray(array $colors): self
|
||||
{
|
||||
$this->set('H', $colors[0]);
|
||||
$this->set('W', $colors[1]);
|
||||
$this->set('B', $colors[2]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert to css string with optional opacityt
|
||||
*
|
||||
* @param float|string|null $opacity
|
||||
* @return string
|
||||
*/
|
||||
public function toCssString(null|float|string $opacity = null): string
|
||||
{
|
||||
$string = 'hwb('
|
||||
. $this->H
|
||||
. ' '
|
||||
. $this->W
|
||||
. ' '
|
||||
. $this->B
|
||||
. Utils::setOpacity($opacity)
|
||||
. ')';
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: Ymd
|
||||
* DESCRIPTION:
|
||||
* DescriptionHere
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Convert\Color\Coordinates\Interface;
|
||||
|
||||
interface CoordinatesInterface
|
||||
{
|
||||
/**
|
||||
* create class via "Class::create()" call
|
||||
* was used for multiple create interfaces
|
||||
* no longer needed, use "new Class()" instead
|
||||
*
|
||||
* @param string|array{0:float,1:float,2:float} $colors
|
||||
* @param string $colorspace [default='']
|
||||
* @param array<string,string|bool|int> $options [default=[]]
|
||||
* @return self
|
||||
*/
|
||||
public static function create(string|array $colors, string $colorspace = '', array $options = []): self;
|
||||
|
||||
/**
|
||||
* get color
|
||||
*
|
||||
* @param string $name
|
||||
* @return float
|
||||
*/
|
||||
public function get(string $name): float|string|bool;
|
||||
|
||||
/**
|
||||
* Returns the color as array
|
||||
* where 0: Lightness, 1: a, 2: b
|
||||
*
|
||||
* @return array{0:float,1:float,2:float}
|
||||
*/
|
||||
public function returnAsArray(): array;
|
||||
|
||||
/**
|
||||
* Convert into css string with optional opacity
|
||||
*
|
||||
* @param null|float|string|null $opacity
|
||||
* @return string
|
||||
*/
|
||||
public function toCssString(null|float|string $opacity = null): string;
|
||||
}
|
||||
|
||||
// __END__
|
||||
227
src/Convert/Color/Coordinates/LCH.php
Normal file
227
src/Convert/Color/Coordinates/LCH.php
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2024/11/11
|
||||
* DESCRIPTION:
|
||||
* Color Coordinate: Lch
|
||||
* for oklch or cie
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Convert\Color\Coordinates;
|
||||
|
||||
use CoreLibs\Convert\Color\Utils;
|
||||
|
||||
class LCH implements Interface\CoordinatesInterface
|
||||
{
|
||||
/** @var array<string> allowed colorspaces */
|
||||
private const COLORSPACES = ['OkLab', 'CIELab'];
|
||||
|
||||
/** @var float Lightness/Luminance
|
||||
* CIE: 0 to 100
|
||||
* OKlch: 0.0 to 1.0
|
||||
* BOTH: 0% to 100%
|
||||
*/
|
||||
private float $L = 0.0;
|
||||
/** @var float Chroma
|
||||
* CIE: 0 to 150, cannot be more than 230
|
||||
* OkLch: 0 to 0.4, does not exceed 0.5
|
||||
* BOTH: 0% to 100% (0 to 150, 0 to 0.4)
|
||||
*/
|
||||
private float $C = 0.0;
|
||||
/** @var float Hue
|
||||
* 0 to 360 deg
|
||||
*/
|
||||
private float $H = 0.0;
|
||||
|
||||
/** @var string color space: either ok or cie */
|
||||
private string $colorspace = '';
|
||||
|
||||
/**
|
||||
* Color Coordinate Lch
|
||||
* for oklch
|
||||
*
|
||||
* @param string|array{0:float,1:float,2:float} $colors
|
||||
* @param string $colorspace [default='']
|
||||
* @param array<string,string> $options [default=[]]
|
||||
* @throws \InvalidArgumentException only array colors allowed
|
||||
*/
|
||||
public function __construct(string|array $colors, string $colorspace = '', array $options = [])
|
||||
{
|
||||
if (!is_array($colors)) {
|
||||
throw new \InvalidArgumentException('Only array colors allowed', 0);
|
||||
}
|
||||
$this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* set from array
|
||||
* where 0: Lightness, 1: Chroma, 2: Hue
|
||||
*
|
||||
* @param string|array{0:float,1:float,2:float} $colors
|
||||
* @param string $colorspace [default='']
|
||||
* @param array<string,string> $options [default=[]]
|
||||
* @return self
|
||||
*/
|
||||
public static function create(string|array $colors, string $colorspace = '', array $options = []): self
|
||||
{
|
||||
return new LCH($colors, $colorspace, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* parse options
|
||||
*
|
||||
* @param array<string,string> $options
|
||||
* @return self
|
||||
*/
|
||||
private function parseOptions(array $options): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set color
|
||||
*
|
||||
* @param string $name
|
||||
* @param float $value
|
||||
* @return void
|
||||
*/
|
||||
private function set(string $name, float $value): void
|
||||
{
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new \ErrorException('Creation of dynamic property is not allowed', 0);
|
||||
}
|
||||
switch ($name) {
|
||||
case 'L':
|
||||
// if ($this->colorspace == 'CIELab' && ($value < 0 || $value > 100)) {
|
||||
if ($this->colorspace == 'CIELab' && Utils::compare(0.0, $value, 100.0, Utils::ESPILON_BIG)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for lightness is not in the range of 0 to 100 for CIE Lab',
|
||||
1
|
||||
);
|
||||
// } elseif ($this->colorspace == 'OkLab' && ($value < 0 || $value > 1)) {
|
||||
} elseif ($this->colorspace == 'OkLab' && Utils::compare(0.0, $value, 1.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for lightness is not in the range of 0.0 to 1.0 for OkLab',
|
||||
1
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'C':
|
||||
// if ($this->colorspace == 'CIELab' && ($value < 0 || $value > 230)) {
|
||||
if ($this->colorspace == 'CIELab' && Utils::compare(0.0, $value, 230.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for chroma is not in the range of '
|
||||
. '0 to 150 and a maximum of 230 for CIE Lab',
|
||||
1
|
||||
);
|
||||
// } elseif ($this->colorspace == 'OkLab' && ($value < 0 || $value > 0.55)) {
|
||||
} elseif ($this->colorspace == 'OkLab' && Utils::compare(0.0, $value, 0.55, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for lightness is not in the range of '
|
||||
. '0.0 to 0.4 and a maximum of 0.5 for OkLab',
|
||||
1
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'H':
|
||||
// if ($value < 0 || $value > 360) {
|
||||
if (Utils::compare(0.0, $value, 360.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for hue is not in the range of 0.0 to 360.0',
|
||||
1
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
$this->$name = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* get color
|
||||
*
|
||||
* @param string $name
|
||||
* @return float
|
||||
*/
|
||||
public function get(string $name): float|string|bool
|
||||
{
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new \ErrorException('Creation of dynamic property is not allowed', 0);
|
||||
}
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the colorspace
|
||||
*
|
||||
* @param string $colorspace
|
||||
* @return self
|
||||
*/
|
||||
private function setColorspace(string $colorspace): self
|
||||
{
|
||||
if (!in_array($colorspace, $this::COLORSPACES)) {
|
||||
throw new \InvalidArgumentException('Not allowed colorspace', 0);
|
||||
}
|
||||
$this->colorspace = $colorspace;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color as array
|
||||
* where 0: Lightness, 1: Chroma, 2: Hue
|
||||
*
|
||||
* @return array{0:float,1:float,2:float}
|
||||
*/
|
||||
public function returnAsArray(): array
|
||||
{
|
||||
return [$this->L, $this->C, $this->H];
|
||||
}
|
||||
|
||||
/**
|
||||
* set color as array
|
||||
* where 0: Lightness, 1: Chroma, 2: Hue
|
||||
*
|
||||
* @param array{0:float,1:float,2:float} $colors
|
||||
* @return self
|
||||
*/
|
||||
private function setFromArray(array $colors): self
|
||||
{
|
||||
$this->set('L', $colors[0]);
|
||||
$this->set('C', $colors[1]);
|
||||
$this->set('H', $colors[2]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert into css string with optional opacity
|
||||
*
|
||||
* @param null|float|string|null $opacity
|
||||
* @return string
|
||||
*/
|
||||
public function toCssString(null|float|string $opacity = null): string
|
||||
{
|
||||
$string = '';
|
||||
switch ($this->colorspace) {
|
||||
case 'CIELab':
|
||||
$string = 'lch';
|
||||
break;
|
||||
case 'OkLab':
|
||||
$string = 'oklch';
|
||||
break;
|
||||
}
|
||||
$string .= '('
|
||||
. $this->L
|
||||
. ' '
|
||||
. $this->C
|
||||
. ' '
|
||||
. $this->H
|
||||
. Utils::setOpacity($opacity)
|
||||
. ');';
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
233
src/Convert/Color/Coordinates/Lab.php
Normal file
233
src/Convert/Color/Coordinates/Lab.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2024/11/11
|
||||
* DESCRIPTION:
|
||||
* Color Coordinate: Lab
|
||||
* for oklab or cie
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Convert\Color\Coordinates;
|
||||
|
||||
use CoreLibs\Convert\Color\Utils;
|
||||
|
||||
class Lab implements Interface\CoordinatesInterface
|
||||
{
|
||||
/** @var array<string> allowed colorspaces */
|
||||
private const COLORSPACES = ['OkLab', 'CIELab'];
|
||||
|
||||
/** @var float lightness/luminance
|
||||
* CIE: 0f to 100f
|
||||
* OKlab: 0.0 to 1.0
|
||||
* BOTH: 0% to 100%
|
||||
*/
|
||||
private float $L = 0.0;
|
||||
/** @var float a axis distance
|
||||
* CIE: -125 to 125, cannot be more than +/- 160
|
||||
* OKlab: -0.4 to 0.4, cannot exceed +/- 0.5
|
||||
* BOTH: -100% to 100% (+/-125 or 0.4)
|
||||
*/
|
||||
private float $a = 0.0;
|
||||
/** @var float b axis distance
|
||||
* CIE: -125 to 125, cannot be more than +/- 160
|
||||
* OKlab: -0.4 to 0.4, cannot exceed +/- 0.5
|
||||
* BOTH: -100% to 100% (+/-125 or 0.4)
|
||||
*/
|
||||
private float $b = 0.0;
|
||||
|
||||
/** @var string color space: either ok or cie */
|
||||
private string $colorspace = '';
|
||||
|
||||
/**
|
||||
* Color Coordinate: Lab
|
||||
* for oklab or cie
|
||||
*
|
||||
* @param string|array{0:float,1:float,2:float} $colors
|
||||
* @param string $colorspace [default='']
|
||||
* @param array<string,string> $options [default=[]]
|
||||
* @throws \InvalidArgumentException only array colors allowed
|
||||
*/
|
||||
public function __construct(string|array $colors, string $colorspace = '', array $options = [])
|
||||
{
|
||||
if (!is_array($colors)) {
|
||||
throw new \InvalidArgumentException('Only array colors allowed', 0);
|
||||
}
|
||||
$this->setColorspace($colorspace)->parseOptions($options)->setFromArray($colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* set from array
|
||||
* where 0: Lightness, 1: a, 2: b
|
||||
*
|
||||
* @param array{0:float,1:float,2:float} $colors
|
||||
* @param string $colorspace [default='']
|
||||
* @param array<string,string> $options [default=[]]
|
||||
* @return self
|
||||
*/
|
||||
public static function create(string|array $colors, string $colorspace = '', array $options = []): self
|
||||
{
|
||||
return new Lab($colors, $colorspace, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* parse options
|
||||
*
|
||||
* @param array<string,string> $options
|
||||
* @return self
|
||||
*/
|
||||
private function parseOptions(array $options): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set color
|
||||
*
|
||||
* @param string $name
|
||||
* @param float $value
|
||||
* @return void
|
||||
*/
|
||||
private function set(string $name, float $value): void
|
||||
{
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new \ErrorException('Creation of dynamic property is not allowed', 0);
|
||||
}
|
||||
switch ($name) {
|
||||
case 'L':
|
||||
// if ($this->colorspace == 'CIELab' && ($value < 0 || $value > 100)) {
|
||||
if ($this->colorspace == 'CIELab' && Utils::compare(0.0, $value, 100.0, Utils::ESPILON_BIG)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for lightness is not in the range of 0 to 100 for CIE Lab',
|
||||
1
|
||||
);
|
||||
// } elseif ($this->colorspace == 'OkLab' && ($value < 0 || $value > 1)) {
|
||||
} elseif ($this->colorspace == 'OkLab' && Utils::compare(0.0, $value, 1.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for lightness is not in the range of 0.0 to 1.0 for OkLab',
|
||||
1
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'a':
|
||||
// if ($this->colorspace == 'CIELab' && ($value < -125 || $value > 125)) {
|
||||
if ($this->colorspace == 'CIELab' && Utils::compare(-125.0, $value, 125.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for a is not in the range of -125 to 125 for CIE Lab',
|
||||
2
|
||||
);
|
||||
// } elseif ($this->colorspace == 'OkLab' && ($value < -0.55 || $value > 0.55)) {
|
||||
} elseif ($this->colorspace == 'OkLab' && Utils::compare(-0.55, $value, 0.55, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for a is not in the range of -0.5 to 0.5 for OkLab',
|
||||
2
|
||||
);
|
||||
}
|
||||
break;
|
||||
case 'b':
|
||||
// if ($this->colorspace == 'CIELab' && ($value < -125 || $value > 125)) {
|
||||
if ($this->colorspace == 'CIELab' && Utils::compare(-125.0, $value, 125.0, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for b is not in the range of -125 to 125 for CIE Lab',
|
||||
3
|
||||
);
|
||||
// } elseif ($this->colorspace == 'OkLab' && ($value < -0.55 || $value > 0.55)) {
|
||||
} elseif ($this->colorspace == 'OkLab' && Utils::compare(-0.55, $value, 0.55, Utils::EPSILON_SMALL)) {
|
||||
throw new \LengthException(
|
||||
'Argument value ' . $value . ' for b is not in the range of -0.5 to 0.5 for OkLab',
|
||||
3
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
$this->$name = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* get color
|
||||
*
|
||||
* @param string $name
|
||||
* @return float
|
||||
*/
|
||||
public function get(string $name): float|string|bool
|
||||
{
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new \ErrorException('Creation of dynamic property is not allowed', 0);
|
||||
}
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the colorspace
|
||||
*
|
||||
* @param string $colorspace
|
||||
* @return self
|
||||
*/
|
||||
private function setColorspace(string $colorspace): self
|
||||
{
|
||||
if (!in_array($colorspace, $this::COLORSPACES)) {
|
||||
throw new \InvalidArgumentException('Not allowed colorspace', 0);
|
||||
}
|
||||
$this->colorspace = $colorspace;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color as array
|
||||
* where 0: Lightness, 1: a, 2: b
|
||||
*
|
||||
* @return array{0:float,1:float,2:float}
|
||||
*/
|
||||
public function returnAsArray(): array
|
||||
{
|
||||
return [$this->L, $this->a, $this->b];
|
||||
}
|
||||
|
||||
/**
|
||||
* set color as array
|
||||
* where 0: Lightness, 1: a, 2: b
|
||||
*
|
||||
* @param array{0:float,1:float,2:float} $colors
|
||||
* @return self
|
||||
*/
|
||||
private function setFromArray(array $colors): self
|
||||
{
|
||||
$this->set('L', $colors[0]);
|
||||
$this->set('a', $colors[1]);
|
||||
$this->set('b', $colors[2]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert into css string with optional opacity
|
||||
*
|
||||
* @param null|float|string|null $opacity
|
||||
* @return string
|
||||
*/
|
||||
public function toCssString(null|float|string $opacity = null): string
|
||||
{
|
||||
$string = '';
|
||||
switch ($this->colorspace) {
|
||||
case 'CIELab':
|
||||
$string = 'lab';
|
||||
break;
|
||||
case 'OkLab':
|
||||
$string = 'oklab';
|
||||
break;
|
||||
}
|
||||
$string .= '('
|
||||
. $this->L
|
||||
. ' '
|
||||
. $this->a
|
||||
. ' '
|
||||
. $this->b
|
||||
. Utils::setOpacity($opacity)
|
||||
. ');';
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
329
src/Convert/Color/Coordinates/RGB.php
Normal file
329
src/Convert/Color/Coordinates/RGB.php
Normal file
@@ -0,0 +1,329 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2024/11/11
|
||||
* DESCRIPTION:
|
||||
* Color Coordinate: RGB
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Convert\Color\Coordinates;
|
||||
|
||||
use CoreLibs\Convert\Color\Utils;
|
||||
|
||||
class RGB implements Interface\CoordinatesInterface
|
||||
{
|
||||
/** @var array<string> allowed colorspaces */
|
||||
private const COLORSPACES = ['sRGB'];
|
||||
|
||||
/** @var float red 0 to 255 or 0.0f to 1.0f for linear RGB */
|
||||
private float $R = 0.0;
|
||||
/** @var float green 0 to 255 or 0.0f to 1.0f for linear RGB */
|
||||
private float $G = 0.0;
|
||||
/** @var float blue 0 to 255 or 0.0f to 1.0f for linear RGB */
|
||||
private float $B = 0.0;
|
||||
|
||||
/** @var string color space: either ok or cie */
|
||||
private string $colorspace = '';
|
||||
|
||||
/** @var bool set if this is linear */
|
||||
private bool $linear = false;
|
||||
|
||||
/**
|
||||
* Color Coordinate RGB
|
||||
* @param array{0:float,1:float,2:float}|string $colors RGB color array or hex string
|
||||
* @param string $colorspace [default=sRGB]
|
||||
* @param array<string,bool> $options [default=[]] only "linear" allowed at the moment
|
||||
*/
|
||||
public function __construct(string|array $colors, string $colorspace = 'sRGB', array $options = [])
|
||||
{
|
||||
$this->setColorspace($colorspace)->parseOptions($options);
|
||||
if (is_array($colors)) {
|
||||
$this->setFromArray($colors);
|
||||
} else {
|
||||
$this->setFromHex($colors);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set from array or string
|
||||
* where 0: Red, 1: Green, 2: Blue
|
||||
* OR #ffffff or ffffff
|
||||
*
|
||||
* @param array{0:float,1:float,2:float}|string $colors RGB color array or hex string
|
||||
* @param string $colorspace [default=sRGB]
|
||||
* @param array<string,bool> $options [default=[]] only "linear" allowed at the moment
|
||||
* @return self
|
||||
*/
|
||||
public static function create(string|array $colors, string $colorspace = 'sRGB', array $options = []): self
|
||||
{
|
||||
return new RGB($colors, $colorspace, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* parse options
|
||||
*
|
||||
* @param array<string,bool> $options
|
||||
* @return self
|
||||
*/
|
||||
private function parseOptions(array $options): self
|
||||
{
|
||||
$this->flagLinear($options['linear'] ?? false);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* set color
|
||||
*
|
||||
* @param string $name
|
||||
* @param float $value
|
||||
* @return void
|
||||
*/
|
||||
private function set(string $name, float $value): void
|
||||
{
|
||||
// do not allow setting linear from outside
|
||||
if ($name == 'linear') {
|
||||
return;
|
||||
}
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new \ErrorException('Creation of dynamic property is not allowed', 0);
|
||||
}
|
||||
// if not linear
|
||||
if (!$this->linear && ((int)$value < 0 || (int)$value > 255)) {
|
||||
throw new \LengthException('Argument value ' . $value . ' for color ' . $name
|
||||
. ' is not in the range of 0 to 255', 1);
|
||||
} elseif (
|
||||
// $this->linear && ($value < 0.0 || $value > 1.0)
|
||||
$this->linear && Utils::compare(0.0, $value, 1.0, 0.000001)
|
||||
) {
|
||||
throw new \LengthException('Argument value ' . $value . ' for color ' . $name
|
||||
. ' is not in the range of 0 to 1 for linear rgb', 2);
|
||||
}
|
||||
$this->$name = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* get color
|
||||
*
|
||||
* @param string $name
|
||||
* @return float|bool
|
||||
*/
|
||||
public function get(string $name): float|string|bool
|
||||
{
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new \ErrorException('Creation of dynamic property is not allowed', 0);
|
||||
}
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the colorspace
|
||||
*
|
||||
* @param string $colorspace
|
||||
* @return self
|
||||
*/
|
||||
private function setColorspace(string $colorspace): self
|
||||
{
|
||||
if (!in_array($colorspace, $this::COLORSPACES)) {
|
||||
throw new \InvalidArgumentException('Not allowed colorspace', 0);
|
||||
}
|
||||
$this->colorspace = $colorspace;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color as array
|
||||
* where 0: Red, 1: Green, 2: Blue
|
||||
*
|
||||
* @return array{0:float,1:float,2:float}
|
||||
*/
|
||||
public function returnAsArray(): array
|
||||
{
|
||||
return [$this->R, $this->G, $this->B];
|
||||
}
|
||||
|
||||
/**
|
||||
* set color as array
|
||||
* where 0: Red, 1: Green, 2: Blue
|
||||
*
|
||||
* @param array{0:float,1:float,2:float} $colors
|
||||
* @return self
|
||||
*/
|
||||
private function setFromArray(array $colors): self
|
||||
{
|
||||
$this->set('R', $colors[0]);
|
||||
$this->set('G', $colors[1]);
|
||||
$this->set('B', $colors[2]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current set RGB as hex string. with or without # prefix
|
||||
*
|
||||
* @param bool $hex_prefix
|
||||
* @return string
|
||||
*/
|
||||
public function returnAsHex(bool $hex_prefix = true): string
|
||||
{
|
||||
// prefix
|
||||
$hex_color = '';
|
||||
if ($hex_prefix === true) {
|
||||
$hex_color = '#';
|
||||
}
|
||||
// convert if in linear
|
||||
if ($this->linear) {
|
||||
$this->fromLinear();
|
||||
}
|
||||
foreach ($this->returnAsArray() as $color) {
|
||||
$hex_color .= str_pad(dechex((int)$color), 2, '0', STR_PAD_LEFT);
|
||||
}
|
||||
return $hex_color;
|
||||
}
|
||||
|
||||
/**
|
||||
* set colors RGB from hex string
|
||||
*
|
||||
* @param string $hex_string
|
||||
* @return self
|
||||
*/
|
||||
private function setFromHex(string $hex_string): self
|
||||
{
|
||||
$hex_string = preg_replace("/[^0-9A-Fa-f]/", '', $hex_string); // Gets a proper hex string
|
||||
if (empty($hex_string) || !is_string($hex_string)) {
|
||||
throw new \InvalidArgumentException('hex_string argument cannot be empty', 3);
|
||||
}
|
||||
$rgbArray = [];
|
||||
if (strlen($hex_string) == 6) {
|
||||
// If a proper hex code, convert using bitwise operation.
|
||||
// No overhead... faster
|
||||
$colorVal = hexdec($hex_string);
|
||||
$rgbArray = [
|
||||
0xFF & ($colorVal >> 0x10),
|
||||
0xFF & ($colorVal >> 0x8),
|
||||
0xFF & $colorVal
|
||||
];
|
||||
} elseif (strlen($hex_string) == 3) {
|
||||
// If shorthand notation, need some string manipulations
|
||||
$rgbArray = [
|
||||
hexdec(str_repeat(substr($hex_string, 0, 1), 2)),
|
||||
hexdec(str_repeat(substr($hex_string, 1, 1), 2)),
|
||||
hexdec(str_repeat(substr($hex_string, 2, 1), 2))
|
||||
];
|
||||
} else {
|
||||
// Invalid hex color code
|
||||
throw new \UnexpectedValueException('Invalid hex_string: ' . $hex_string, 4);
|
||||
}
|
||||
return $this->setFromArray($rgbArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* set as linear
|
||||
* can be used as chain call on create if input is linear RGB
|
||||
* RGB::__construct**(...)->flagLinear();
|
||||
* as it returns self
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function flagLinear(bool $linear): self
|
||||
{
|
||||
$this->linear = $linear;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Both function source:
|
||||
* https://bottosson.github.io/posts/colorwrong/#what-can-we-do%3F
|
||||
* but reverse f: fromLinear and f_inv for toLinear
|
||||
* Code copied from here:
|
||||
* https://stackoverflow.com/a/12894053
|
||||
*
|
||||
* converts RGB to linear
|
||||
* We come from 0-255 so we need to divide by 255
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function toLinear(): self
|
||||
{
|
||||
// if linear, as is
|
||||
if ($this->linear) {
|
||||
return $this;
|
||||
}
|
||||
$this->flagLinear(true)->setFromArray(array_map(
|
||||
callback: function (int|float $v) {
|
||||
$v = (float)($v / 255);
|
||||
$abs = abs($v);
|
||||
$sign = ($v < 0) ? -1 : 1;
|
||||
return (float)(
|
||||
$abs <= 0.04045 ?
|
||||
$v / 12.92 :
|
||||
$sign * pow(($abs + 0.055) / 1.055, 2.4)
|
||||
);
|
||||
},
|
||||
array: $this->returnAsArray(),
|
||||
));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert back to normal sRGB from linear RGB
|
||||
* we go to 0-255 rgb so we multiply by 255
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function fromLinear(): self
|
||||
{
|
||||
// if not linear, as is
|
||||
if (!$this->linear) {
|
||||
return $this;
|
||||
}
|
||||
$this->flagLinear(false)->setFromArray(array_map(
|
||||
callback: function (int|float $v) {
|
||||
$abs = abs($v);
|
||||
$sign = ($v < 0) ? -1 : 1;
|
||||
// during reverse in some situations the values can become negative in very small ways
|
||||
// like -...E16 and ...E17
|
||||
return ($ret = (float)(255 * (
|
||||
$abs <= 0.0031308 ?
|
||||
$v * 12.92 :
|
||||
$sign * (1.055 * pow($abs, 1.0 / 2.4) - 0.055)
|
||||
))) < 0 ? 0 : $ret;
|
||||
},
|
||||
array: $this->returnAsArray(),
|
||||
));
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert to css string with optional opacity
|
||||
* Note: if this is a linear RGB, the data will converted during this operation and the converted back
|
||||
*
|
||||
* @param float|string|null $opacity
|
||||
* @return string
|
||||
*/
|
||||
public function toCssString(null|float|string $opacity = null): string
|
||||
{
|
||||
// if we are in linear mode, convert to normal mode temporary
|
||||
$was_linear = false;
|
||||
if ($this->linear) {
|
||||
$this->fromLinear();
|
||||
$was_linear = true;
|
||||
}
|
||||
$string = 'rgb('
|
||||
. (int)round($this->R, 0)
|
||||
. ' '
|
||||
. (int)round($this->G, 0)
|
||||
. ' '
|
||||
. (int)round($this->B, 0)
|
||||
. Utils::setOpacity($opacity)
|
||||
. ')';
|
||||
if ($was_linear) {
|
||||
$this->toLinear();
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
202
src/Convert/Color/Coordinates/XYZ.php
Normal file
202
src/Convert/Color/Coordinates/XYZ.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2024/11/11
|
||||
* DESCRIPTION:
|
||||
* Color Coordinate: XYZ (Cie) (colorspace CIEXYZ)
|
||||
* Note, this is only for the D50 & D65 whitepoint conversion
|
||||
* https://en.wikipedia.org/wiki/CIE_1931_color_space#Construction_of_the_CIE_XYZ_color_space_from_the_Wright%E2%80%93Guild_data
|
||||
* https://en.wikipedia.org/wiki/Standard_illuminant#Illuminant_series_D
|
||||
* https://en.wikipedia.org/wiki/Standard_illuminant#D65_values
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Convert\Color\Coordinates;
|
||||
|
||||
// use CoreLibs\Convert\Color\Utils;
|
||||
|
||||
class XYZ implements Interface\CoordinatesInterface
|
||||
{
|
||||
/** @var array<string> allowed colorspaces */
|
||||
private const COLORSPACES = ['CIEXYZ'];
|
||||
/** @var array<string> allowed whitepoints
|
||||
* D50: ICC profile PCS (horizon light) <-> CieLab
|
||||
* D65: RGB color space (noon) <-> linear RGB
|
||||
*/
|
||||
private const ILLUMINANT = ['D50', 'D65'];
|
||||
|
||||
/** @var float X coordinate */
|
||||
private float $X = 0.0;
|
||||
/** @var float Y coordinate (Luminance) */
|
||||
private float $Y = 0.0;
|
||||
/** @var float Z coordinate (blue) */
|
||||
private float $Z = 0.0;
|
||||
|
||||
/** @var string color space: either ok or cie */
|
||||
private string $colorspace = '';
|
||||
|
||||
/** @var string illuminat white point: only D50 and D65 are allowed */
|
||||
private string $whitepoint = '';
|
||||
|
||||
/**
|
||||
* Color Coordinate Lch
|
||||
* for oklch conversion
|
||||
*
|
||||
* @param string|array{0:float,1:float,2:float} $colors
|
||||
* @param string $colorspace [default=CIEXYZ]
|
||||
* @param array<string,string> $options [default=[]] Only "whitepoint" option allowed
|
||||
* @throws \InvalidArgumentException only array colors allowed
|
||||
*/
|
||||
public function __construct(
|
||||
string|array $colors,
|
||||
string $colorspace = 'CIEXYZ',
|
||||
array $options = [],
|
||||
) {
|
||||
if (!is_array($colors)) {
|
||||
throw new \InvalidArgumentException('Only array colors allowed', 0);
|
||||
}
|
||||
$this->setColorspace($colorspace)
|
||||
->parseOptions($options)
|
||||
->setFromArray($colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* set from array
|
||||
* where 0: X, 1: Y, 2: Z
|
||||
*
|
||||
* @param array{0:float,1:float,2:float} $colors
|
||||
* @param string $colorspace [default=CIEXYZ]
|
||||
* @param array<string,string> $options [default=[]] Only "whitepoint" option allowed
|
||||
* @return self
|
||||
*/
|
||||
public static function create(
|
||||
string|array $colors,
|
||||
string $colorspace = 'CIEXYZ',
|
||||
array $options = [],
|
||||
): self {
|
||||
return new XYZ($colors, $colorspace, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* parse options
|
||||
*
|
||||
* @param array<string,string> $options
|
||||
* @return self
|
||||
*/
|
||||
private function parseOptions(array $options): self
|
||||
{
|
||||
$this->setWhitepoint($options['whitepoint'] ?? '');
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set color
|
||||
*
|
||||
* @param string $name
|
||||
* @param float $value
|
||||
* @return void
|
||||
*/
|
||||
private function set(string $name, float $value): void
|
||||
{
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new \ErrorException('Creation of dynamic property is not allowed', 0);
|
||||
}
|
||||
// TODO: setup XYZ value limits
|
||||
// X: 0 to 95.047, Y: 0 to 100, Z: 0 to 108.88
|
||||
// if (Utils::compare(0.0, $value, 100.0, Utils::EPSILON_SMALL))) {
|
||||
// throw new \LengthException('Argument value ' . $value . ' for color ' . $name
|
||||
// . ' is not in the range of 0 to 100.0', 1);
|
||||
// }
|
||||
$this->$name = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* get color
|
||||
*
|
||||
* @param string $name
|
||||
* @return float
|
||||
*/
|
||||
public function get(string $name): float|string|bool
|
||||
{
|
||||
if (!property_exists($this, $name)) {
|
||||
throw new \ErrorException('Creation of dynamic property is not allowed', 0);
|
||||
}
|
||||
return $this->$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the colorspace
|
||||
*
|
||||
* @param string $colorspace
|
||||
* @return self
|
||||
*/
|
||||
private function setColorspace(string $colorspace): self
|
||||
{
|
||||
if (!in_array($colorspace, $this::COLORSPACES)) {
|
||||
throw new \InvalidArgumentException('Not allowed colorspace', 0);
|
||||
}
|
||||
$this->colorspace = $colorspace;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the whitepoint flag
|
||||
*
|
||||
* @param string $whitepoint
|
||||
* @return self
|
||||
*/
|
||||
private function setWhitepoint(string $whitepoint): self
|
||||
{
|
||||
if (empty($whitepoint)) {
|
||||
$this->whitepoint = '';
|
||||
return $this;
|
||||
}
|
||||
if (!in_array($whitepoint, $this::ILLUMINANT)) {
|
||||
throw new \InvalidArgumentException('Not allowed whitepoint', 0);
|
||||
}
|
||||
$this->whitepoint = $whitepoint;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the color as array
|
||||
* where 0: X, 1: Y, 2: Z
|
||||
*
|
||||
* @return array{0:float,1:float,2:float}
|
||||
*/
|
||||
public function returnAsArray(): array
|
||||
{
|
||||
return [$this->X, $this->Y, $this->Z];
|
||||
}
|
||||
|
||||
/**
|
||||
* set color as array
|
||||
* where 0: X, 1: Y, 2: Z
|
||||
*
|
||||
* @param array{0:float,1:float,2:float} $colors
|
||||
* @return self
|
||||
*/
|
||||
private function setFromArray(array $colors): self
|
||||
{
|
||||
$this->set('X', $colors[0]);
|
||||
$this->set('Y', $colors[1]);
|
||||
$this->set('Z', $colors[2]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* no hsb in css
|
||||
*
|
||||
* @param float|string|null $opacity
|
||||
* @return string
|
||||
* @throws \ErrorException
|
||||
*/
|
||||
public function toCssString(null|float|string $opacity = null): string
|
||||
{
|
||||
throw new \ErrorException('XYZ is not available as CSS color string', 0);
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
35
src/Convert/Color/Stringify.php
Normal file
35
src/Convert/Color/Stringify.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2024/11/11
|
||||
* DESCRIPTION:
|
||||
* Convert color coordinate to CSS string
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Convert\Color;
|
||||
|
||||
use CoreLibs\Convert\Color\Coordinates\RGB;
|
||||
use CoreLibs\Convert\Color\Coordinates\HSL;
|
||||
use CoreLibs\Convert\Color\Coordinates\HWB;
|
||||
use CoreLibs\Convert\Color\Coordinates\Lab;
|
||||
use CoreLibs\Convert\Color\Coordinates\LCH;
|
||||
|
||||
class Stringify
|
||||
{
|
||||
/**
|
||||
* return the CSS string including optional opacity
|
||||
*
|
||||
* @param RGB|Lab|LCH|HSL|HWB $data
|
||||
* @param null|float|string $opacity
|
||||
* @return string
|
||||
*/
|
||||
public static function toCssString(RGB|Lab|LCH|HSL|HWB $data, null|float|string $opacity): string
|
||||
{
|
||||
return $data->toCssString($opacity);
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
56
src/Convert/Color/Utils.php
Normal file
56
src/Convert/Color/Utils.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2024/11/14
|
||||
* DESCRIPTION:
|
||||
* Utils for color
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Convert\Color;
|
||||
|
||||
use CoreLibs\Convert\Math;
|
||||
|
||||
class Utils
|
||||
{
|
||||
/** @var float deviation allowed for valid data checks, small */
|
||||
public const EPSILON_SMALL = 0.000000000001;
|
||||
/** @var float deviation allowed for valid data checks, medium */
|
||||
public const EPSILON_MEDIUM = 0.0000001;
|
||||
/** @var float deviation allowed for valid data checks, big */
|
||||
public const ESPILON_BIG = 0.0001;
|
||||
|
||||
public static function compare(float $lower, float $value, float $upper, float $epslion): bool
|
||||
{
|
||||
if (
|
||||
Math::compareWithEpsilon($value, '<', $lower, $epslion) ||
|
||||
Math::compareWithEpsilon($value, '>', $upper, $epslion)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the opactiy sub string part and return it
|
||||
*
|
||||
* @param null|float|string|null $opacity
|
||||
* @return string
|
||||
*/
|
||||
public static function setOpacity(null|float|string $opacity = null): string
|
||||
{
|
||||
// set opacity, either a string or float
|
||||
if (is_string($opacity)) {
|
||||
$opacity = ' / ' . $opacity;
|
||||
} elseif ($opacity !== null) {
|
||||
$opacity = ' / ' . $opacity;
|
||||
} else {
|
||||
$opacity = '';
|
||||
}
|
||||
return $opacity;
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
@@ -17,6 +17,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Convert;
|
||||
|
||||
use CoreLibs\Convert\Color\Color;
|
||||
use CoreLibs\Convert\Color\Coordinates;
|
||||
|
||||
class Colors
|
||||
{
|
||||
/**
|
||||
@@ -30,6 +33,7 @@ class Colors
|
||||
* @param bool $hex_prefix default true, prefix with "#"
|
||||
* @return string rgb in hex values with leading # if set,
|
||||
* @throws \LengthException If any argument is not in the range of 0~255
|
||||
* @deprecated v9.20.0 use: new Coordinates\RGB([$red, $green, $blue]))->returnAsHex(true/false for #)
|
||||
*/
|
||||
public static function rgb2hex(
|
||||
int $red,
|
||||
@@ -37,20 +41,7 @@ class Colors
|
||||
int $blue,
|
||||
bool $hex_prefix = true
|
||||
): string {
|
||||
$hex_color = '';
|
||||
if ($hex_prefix === true) {
|
||||
$hex_color = '#';
|
||||
}
|
||||
foreach (['red', 'green', 'blue'] as $color) {
|
||||
// if not valid, abort
|
||||
if ($$color < 0 || $$color > 255) {
|
||||
throw new \LengthException('Argument value ' . $$color . ' for color ' . $color
|
||||
. ' is not in the range of 0 to 255', 1);
|
||||
}
|
||||
// pad left with 0
|
||||
$hex_color .= str_pad(dechex($$color), 2, '0', STR_PAD_LEFT);
|
||||
}
|
||||
return $hex_color;
|
||||
return (new Coordinates\RGB([$red, $green, $blue]))->returnAsHex($hex_prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,32 +54,29 @@ class Colors
|
||||
* or a string with the seperator
|
||||
* @throws \InvalidArgumentException if hex string is empty
|
||||
* @throws \UnexpectedValueException if the hex string value is not valid
|
||||
* @deprecated v9.20.0 use: new Coordinates\RGB($hex_string) (build string/array from return data)
|
||||
*/
|
||||
public static function hex2rgb(
|
||||
string $hex_string,
|
||||
bool $return_as_string = false,
|
||||
string $seperator = ','
|
||||
): string|array {
|
||||
$hex_string = preg_replace("/[^0-9A-Fa-f]/", '', $hex_string); // Gets a proper hex string
|
||||
if (!is_string($hex_string)) {
|
||||
throw new \InvalidArgumentException('hex_string argument cannot be empty', 1);
|
||||
}
|
||||
$rgbArray = [];
|
||||
if (strlen($hex_string) == 6) {
|
||||
// If a proper hex code, convert using bitwise operation.
|
||||
// No overhead... faster
|
||||
$colorVal = hexdec($hex_string);
|
||||
$rgbArray['r'] = 0xFF & ($colorVal >> 0x10);
|
||||
$rgbArray['g'] = 0xFF & ($colorVal >> 0x8);
|
||||
$rgbArray['b'] = 0xFF & $colorVal;
|
||||
} elseif (strlen($hex_string) == 3) {
|
||||
// If shorthand notation, need some string manipulations
|
||||
$rgbArray['r'] = hexdec(str_repeat(substr($hex_string, 0, 1), 2));
|
||||
$rgbArray['g'] = hexdec(str_repeat(substr($hex_string, 1, 1), 2));
|
||||
$rgbArray['b'] = hexdec(str_repeat(substr($hex_string, 2, 1), 2));
|
||||
} else {
|
||||
// Invalid hex color code
|
||||
throw new \UnexpectedValueException('Invalid hex_string: ' . $hex_string, 2);
|
||||
// rewrite to previous r/g/b key output
|
||||
foreach ((new Coordinates\RGB($hex_string))->returnAsArray() as $p => $el) {
|
||||
$k = '';
|
||||
switch ($p) {
|
||||
case 0:
|
||||
$k = 'r';
|
||||
break;
|
||||
case 1:
|
||||
$k = 'g';
|
||||
break;
|
||||
case 2:
|
||||
$k = 'b';
|
||||
break;
|
||||
}
|
||||
$rgbArray[$k] = (int)round($el);
|
||||
}
|
||||
// returns the rgb string or the associative array
|
||||
return $return_as_string ? implode($seperator, $rgbArray) : $rgbArray;
|
||||
@@ -105,42 +93,16 @@ class Colors
|
||||
* @param int $blue blue 0-255
|
||||
* @return array<int|float> Hue, Sat, Brightness/Value
|
||||
* @throws \LengthException If any argument is not in the range of 0~255
|
||||
* @deprecated v9.20.0 use: Color::rgbToHsb(...)->returnAsArray() will return float unrounded
|
||||
*/
|
||||
public static function rgb2hsb(int $red, int $green, int $blue): array
|
||||
{
|
||||
// check that rgb is from 0 to 255
|
||||
foreach (['red', 'green', 'blue'] as $color) {
|
||||
if ($$color < 0 || $$color > 255) {
|
||||
throw new \LengthException('Argument value ' . $$color . ' for color ' . $color
|
||||
. ' is not in the range of 0 to 255', 1);
|
||||
}
|
||||
$$color = $$color / 255;
|
||||
}
|
||||
|
||||
$MAX = max($red, $green, $blue);
|
||||
$MIN = min($red, $green, $blue);
|
||||
$HUE = 0;
|
||||
|
||||
if ($MAX == $MIN) {
|
||||
return [0, 0, round($MAX * 100)];
|
||||
}
|
||||
if ($red == $MAX) {
|
||||
$HUE = ($green - $blue) / ($MAX - $MIN);
|
||||
} elseif ($green == $MAX) {
|
||||
$HUE = 2 + (($blue - $red) / ($MAX - $MIN));
|
||||
} elseif ($blue == $MAX) {
|
||||
$HUE = 4 + (($red - $green) / ($MAX - $MIN));
|
||||
}
|
||||
$HUE *= 60;
|
||||
if ($HUE < 0) {
|
||||
$HUE += 360;
|
||||
}
|
||||
|
||||
return [
|
||||
(int)round($HUE),
|
||||
(int)round((($MAX - $MIN) / $MAX) * 100),
|
||||
(int)round($MAX * 100)
|
||||
];
|
||||
return array_map(
|
||||
fn ($v) => (int)round($v),
|
||||
Color::rgbToHsb(
|
||||
new Coordinates\RGB([$red, $green, $blue])
|
||||
)->returnAsArray()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,80 +115,16 @@ class Colors
|
||||
* @param float $V brightness/value 0-100 (int)
|
||||
* @return array<int> 0 red/1 green/2 blue array as 0-255
|
||||
* @throws \LengthException If any argument is not in the valid range
|
||||
* @deprecated v9.20.0 use: Color::hsbToRgb(...)->returnAsArray() will return float unrounded
|
||||
*/
|
||||
public static function hsb2rgb(float $H, float $S, float $V): array
|
||||
{
|
||||
// check that H is 0 to 359, 360 = 0
|
||||
// and S and V are 0 to 1
|
||||
if ($H == 360) {
|
||||
$H = 0;
|
||||
}
|
||||
if ($H < 0 || $H > 359) {
|
||||
throw new \LengthException('Argument value ' . $H . ' for hue is not in the range of 0 to 359', 1);
|
||||
}
|
||||
if ($S < 0 || $S > 100) {
|
||||
throw new \LengthException('Argument value ' . $S . ' for saturation is not in the range of 0 to 100', 2);
|
||||
}
|
||||
if ($V < 0 || $V > 100) {
|
||||
throw new \LengthException('Argument value ' . $V . ' for brightness is not in the range of 0 to 100', 3);
|
||||
}
|
||||
// convert to internal 0-1 format
|
||||
$S /= 100;
|
||||
$V /= 100;
|
||||
|
||||
if ($S == 0) {
|
||||
$V = (int)round($V * 255);
|
||||
return [$V, $V, $V];
|
||||
}
|
||||
|
||||
$Hi = floor($H / 60);
|
||||
$f = ($H / 60) - $Hi;
|
||||
$p = $V * (1 - $S);
|
||||
$q = $V * (1 - ($S * $f));
|
||||
$t = $V * (1 - ($S * (1 - $f)));
|
||||
|
||||
switch ($Hi) {
|
||||
case 0:
|
||||
$red = $V;
|
||||
$green = $t;
|
||||
$blue = $p;
|
||||
break;
|
||||
case 1:
|
||||
$red = $q;
|
||||
$green = $V;
|
||||
$blue = $p;
|
||||
break;
|
||||
case 2:
|
||||
$red = $p;
|
||||
$green = $V;
|
||||
$blue = $t;
|
||||
break;
|
||||
case 3:
|
||||
$red = $p;
|
||||
$green = $q;
|
||||
$blue = $V;
|
||||
break;
|
||||
case 4:
|
||||
$red = $t;
|
||||
$green = $p;
|
||||
$blue = $V;
|
||||
break;
|
||||
case 5:
|
||||
$red = $V;
|
||||
$green = $p;
|
||||
$blue = $q;
|
||||
break;
|
||||
default:
|
||||
$red = 0;
|
||||
$green = 0;
|
||||
$blue = 0;
|
||||
}
|
||||
|
||||
return [
|
||||
(int)round($red * 255),
|
||||
(int)round($green * 255),
|
||||
(int)round($blue * 255)
|
||||
];
|
||||
return array_map(
|
||||
fn ($v) => (int)round($v),
|
||||
Color::hsbToRgb(
|
||||
new Coordinates\HSB([$H, $S, $V])
|
||||
)->returnAsArray()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,50 +137,16 @@ class Colors
|
||||
* @param int $blue blue 0-255
|
||||
* @return array<float> hue/sat/luminance
|
||||
* @throws \LengthException If any argument is not in the range of 0~255
|
||||
* @deprecated v9.20.0 use: Color::rgbToHsl(...)->returnAsArray() will return float unrounded
|
||||
*/
|
||||
public static function rgb2hsl(int $red, int $green, int $blue): array
|
||||
{
|
||||
// check that rgb is from 0 to 255
|
||||
foreach (['red', 'green', 'blue'] as $color) {
|
||||
if ($$color < 0 || $$color > 255) {
|
||||
throw new \LengthException('Argument value ' . $$color . ' for color ' . $color
|
||||
. ' is not in the range of 0 to 255', 1);
|
||||
}
|
||||
$$color = $$color / 255;
|
||||
}
|
||||
|
||||
$min = min($red, $green, $blue);
|
||||
$max = max($red, $green, $blue);
|
||||
$chroma = $max - $min;
|
||||
$sat = 0;
|
||||
$hue = 0;
|
||||
// luminance
|
||||
$lum = ($max + $min) / 2;
|
||||
|
||||
// achromatic
|
||||
if ($chroma == 0) {
|
||||
// H, S, L
|
||||
return [0.0, 0.0, round($lum * 100, 1)];
|
||||
} else {
|
||||
$sat = $chroma / (1 - abs(2 * $lum - 1));
|
||||
if ($max == $red) {
|
||||
$hue = fmod((($green - $blue) / $chroma), 6);
|
||||
if ($hue < 0) {
|
||||
$hue = (6 - fmod(abs($hue), 6));
|
||||
}
|
||||
} elseif ($max == $green) {
|
||||
$hue = ($blue - $red) / $chroma + 2;
|
||||
} elseif ($max == $blue) {
|
||||
$hue = ($red - $green) / $chroma + 4;
|
||||
}
|
||||
$hue = $hue * 60;
|
||||
// $sat = 1 - abs(2 * $lum - 1);
|
||||
return [
|
||||
round($hue, 1),
|
||||
round($sat * 100, 1),
|
||||
round($lum * 100, 1)
|
||||
];
|
||||
}
|
||||
return array_map(
|
||||
fn ($v) => round($v, 1),
|
||||
Color::rgbToHsl(
|
||||
new Coordinates\RGB([$red, $green, $blue])
|
||||
)->returnAsArray()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,57 +158,16 @@ class Colors
|
||||
* @param float $lum luminance: 0-100
|
||||
* @return array<int,float|int> red/blue/green 0-255 each
|
||||
* @throws \LengthException If any argument is not in the valid range
|
||||
* @deprecated v9.20.0 use: Color::hslToRgb(...)->returnAsArray() will return float unrounded
|
||||
*/
|
||||
public static function hsl2rgb(float $hue, float $sat, float $lum): array
|
||||
{
|
||||
if ($hue == 360) {
|
||||
$hue = 0;
|
||||
}
|
||||
if ($hue < 0 || $hue > 359) {
|
||||
throw new \LengthException('Argument value ' . $hue . ' for hue is not in the range of 0 to 359', 1);
|
||||
}
|
||||
if ($sat < 0 || $sat > 100) {
|
||||
throw new \LengthException('Argument value ' . $sat . ' for saturation is not in the range of 0 to 100', 2);
|
||||
}
|
||||
if ($lum < 0 || $lum > 100) {
|
||||
throw new \LengthException('Argument value ' . $lum . ' for luminance is not in the range of 0 to 100', 3);
|
||||
}
|
||||
// calc to internal convert value for hue
|
||||
$hue = (1 / 360) * $hue;
|
||||
// convert to internal 0-1 format
|
||||
$sat /= 100;
|
||||
$lum /= 100;
|
||||
// if saturation is 0
|
||||
if ($sat == 0) {
|
||||
$lum = (int)round($lum * 255);
|
||||
return [$lum, $lum, $lum];
|
||||
} else {
|
||||
$m2 = $lum < 0.5 ? $lum * ($sat + 1) : ($lum + $sat) - ($lum * $sat);
|
||||
$m1 = $lum * 2 - $m2;
|
||||
$hueue = function ($base) use ($m1, $m2) {
|
||||
// base = hue, hue > 360 (1) - 360 (1), else < 0 + 360 (1)
|
||||
$base = $base < 0 ? $base + 1 : ($base > 1 ? $base - 1 : $base);
|
||||
// 6: 60, 2: 180, 3: 240
|
||||
// 2/3 = 240
|
||||
// 1/3 = 120 (all from 360)
|
||||
if ($base * 6 < 1) {
|
||||
return $m1 + ($m2 - $m1) * $base * 6;
|
||||
}
|
||||
if ($base * 2 < 1) {
|
||||
return $m2;
|
||||
}
|
||||
if ($base * 3 < 2) {
|
||||
return $m1 + ($m2 - $m1) * ((2 / 3) - $base) * 6;
|
||||
}
|
||||
return $m1;
|
||||
};
|
||||
|
||||
return [
|
||||
(int)round(255 * $hueue($hue + (1 / 3))),
|
||||
(int)round(255 * $hueue($hue)),
|
||||
(int)round(255 * $hueue($hue - (1 / 3)))
|
||||
];
|
||||
}
|
||||
return array_map(
|
||||
fn ($v) => round($v),
|
||||
Color::hslToRgb(
|
||||
new Coordinates\HSL([$hue, $sat, $lum])
|
||||
)->returnAsArray()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class SetVarTypeMain
|
||||
*/
|
||||
protected static function makeStrMain(
|
||||
mixed $val,
|
||||
string $default = null,
|
||||
?string $default = null,
|
||||
bool $to_null = false
|
||||
): ?string {
|
||||
// int/float/string/bool/null, everything else is ignored
|
||||
@@ -113,7 +113,7 @@ class SetVarTypeMain
|
||||
*/
|
||||
protected static function makeIntMain(
|
||||
mixed $val,
|
||||
int $default = null,
|
||||
?int $default = null,
|
||||
bool $to_null = false
|
||||
): ?int {
|
||||
// if we can filter it to a valid int, we can convert it
|
||||
@@ -167,7 +167,7 @@ class SetVarTypeMain
|
||||
*/
|
||||
protected static function makeFloatMain(
|
||||
mixed $val,
|
||||
float $default = null,
|
||||
?float $default = null,
|
||||
bool $to_null = false
|
||||
): ?float {
|
||||
if (
|
||||
|
||||
@@ -16,16 +16,22 @@ class Html
|
||||
/**
|
||||
* full wrapper for html entities
|
||||
*
|
||||
* uses default params as: ENT_QUOTES | ENT_HTML5
|
||||
* switches from ENT_HTML401 to ENT_HTML5 as we assume all our pages have <!DOCTYPE html>
|
||||
* removed: ENT_SUBSTITUTE -> wrong characters will be replaced with space
|
||||
* encodes in UTF-8
|
||||
* does not double encode
|
||||
*
|
||||
* @param mixed $string string to html encode
|
||||
* @param int $flags [default: ENT_QUOTES | ENT_HTML5]
|
||||
* @return mixed if string, encoded, else as is (eg null)
|
||||
*/
|
||||
public static function htmlent(mixed $string): mixed
|
||||
public static function htmlent(mixed $string, int $flags = ENT_QUOTES | ENT_HTML5): mixed
|
||||
{
|
||||
if (is_string($string)) {
|
||||
return htmlentities($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false);
|
||||
} else {
|
||||
return $string;
|
||||
return htmlentities($string, $flags, 'UTF-8', false);
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,15 +60,11 @@ class Html
|
||||
*/
|
||||
public static function checked(array|string $haystack, string $needle, int $type = 0): ?string
|
||||
{
|
||||
if (is_array($haystack)) {
|
||||
if (in_array($needle, $haystack)) {
|
||||
if (is_array($haystack) && in_array($needle, $haystack)) {
|
||||
return $type ? 'checked' : 'selected';
|
||||
}
|
||||
} else {
|
||||
if ($haystack == $needle) {
|
||||
} elseif (!is_array($haystack) && $haystack == $needle) {
|
||||
return $type ? 'checked' : 'selected';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,180 @@ class Math
|
||||
return (float)$number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* calc cube root
|
||||
*
|
||||
* @param float $number Number to cubic root
|
||||
* @return float Calculated value
|
||||
*/
|
||||
public static function cbrt(float|int $number): float
|
||||
{
|
||||
return pow((float)$number, 1.0 / 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* use PHP_FLOAT_EPSILON to compare if two float numbers are matching
|
||||
*
|
||||
* @param float $x
|
||||
* @param float $y
|
||||
* @param float $epsilon [default=PHP_FLOAT_EPSILON]
|
||||
* @return bool True equal
|
||||
*/
|
||||
public static function equalWithEpsilon(float $x, float $y, float $epsilon = PHP_FLOAT_EPSILON): bool
|
||||
{
|
||||
if (abs($x - $y) < $epsilon) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two value base on direction given
|
||||
* The default delta is PHP_FLOAT_EPSILON
|
||||
*
|
||||
* @param float $value
|
||||
* @param string $compare
|
||||
* @param float $limit
|
||||
* @param float $epsilon [default=PHP_FLOAT_EPSILON]
|
||||
* @return bool True on smaller/large or equal
|
||||
*/
|
||||
public static function compareWithEpsilon(
|
||||
float $value,
|
||||
string $compare,
|
||||
float $limit,
|
||||
float $epsilon = PHP_FLOAT_EPSILON
|
||||
): bool {
|
||||
switch ($compare) {
|
||||
case '<':
|
||||
if ($value < ($limit - $epsilon)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case '<=':
|
||||
if ($value <= ($limit - $epsilon)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case '==':
|
||||
return self::equalWithEpsilon($value, $limit, $epsilon);
|
||||
case '>':
|
||||
if ($value > ($limit + $epsilon)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case '>=':
|
||||
if ($value >= ($limit + $epsilon)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is directly inspired by the multiplyMatrices() function in color.js
|
||||
* form Lea Verou and Chris Lilley.
|
||||
* (see https://github.com/LeaVerou/color.js/blob/main/src/multiply-matrices.js)
|
||||
* From:
|
||||
* https://github.com/matthieumastadenis/couleur/blob/3842cf51c9517e77afaa0a36ec78643a0c258e0b/src/utils/utils.php#L507
|
||||
*
|
||||
* It returns an array which is the product of the two number matrices passed as parameters.
|
||||
*
|
||||
* NOTE:
|
||||
* if the right side (B matrix) has a missing row, this row will be fillwed with 0 instead of
|
||||
* throwing an error:
|
||||
* A:
|
||||
* [
|
||||
* [1, 2, 3],
|
||||
* [4, 5, 6],
|
||||
* ]
|
||||
* B:
|
||||
* [
|
||||
* [7, 8, 9],
|
||||
* [10, 11, 12],
|
||||
* ]
|
||||
* The B will get a third row with [0, 0, 0] added to make the multiplication work as it will be
|
||||
* rewritten as
|
||||
* B-rewrite:
|
||||
* [
|
||||
* [7, 10, 0],
|
||||
* [8, 11, 12],
|
||||
* [0, 0, 0] <- automatically added
|
||||
* ]
|
||||
*
|
||||
* The same is done for unbalanced entries, they are filled with 0
|
||||
*
|
||||
* @param array<float|int|array<int|float>> $a m x n matrice
|
||||
* @param array<float|int|array<int|float>> $b n x p matrice
|
||||
*
|
||||
* @return array<float|int|array<int|float>> m x p product
|
||||
*/
|
||||
public static function multiplyMatrices(array $a, array $b): array
|
||||
{
|
||||
$m = count($a);
|
||||
|
||||
if (!is_array($a[0] ?? null)) {
|
||||
// $a is vector, convert to [[a, b, c, ...]]
|
||||
$a = [$a];
|
||||
}
|
||||
|
||||
if (!is_array($b[0])) {
|
||||
// $b is vector, convert to [[a], [b], [c], ...]]
|
||||
$b = array_map(
|
||||
callback: fn ($v) => [ $v ],
|
||||
array: $b,
|
||||
);
|
||||
}
|
||||
|
||||
$p = count($b[0]);
|
||||
|
||||
// transpose $b:
|
||||
// so that we can multiply row by row
|
||||
$bCols = array_map(
|
||||
callback: fn ($k) => array_map(
|
||||
(fn ($i) => is_array($i) ? $i[$k] ?? 0 : 0),
|
||||
$b,
|
||||
),
|
||||
array: array_keys($b[0]),
|
||||
);
|
||||
|
||||
$product = array_map(
|
||||
callback: fn ($row) => array_map(
|
||||
callback: fn ($col) => is_array($row) ?
|
||||
array_reduce(
|
||||
array: $row,
|
||||
callback: fn ($a, $v, $i = null) => $a + $v * (
|
||||
// if last entry missing for full copy add a 0 to it
|
||||
$col[$i ?? array_search($v, $row, true)] ?? 0 /** @phpstan-ignore-line */
|
||||
),
|
||||
initial: 0,
|
||||
) :
|
||||
array_reduce(
|
||||
array: $col,
|
||||
callback: fn ($a, $v) => $a + $v * $row,
|
||||
initial: 0,
|
||||
),
|
||||
array: $bCols,
|
||||
),
|
||||
array: $a,
|
||||
);
|
||||
|
||||
if ($m === 1) {
|
||||
// Avoid [[a, b, c, ...]]:
|
||||
return $product[0];
|
||||
}
|
||||
|
||||
if ($p === 1) {
|
||||
// Avoid [[a], [b], [c], ...]]:
|
||||
return array_map(
|
||||
callback: fn ($v) => $v[0] ?? 0,
|
||||
array: $product,
|
||||
);
|
||||
}
|
||||
|
||||
return $product;
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
|
||||
@@ -15,6 +15,7 @@ class MimeEncode
|
||||
/**
|
||||
* wrapper function for mb mime convert
|
||||
* for correct conversion with long strings
|
||||
* NOTE: This is only a wrapper for mb_encode_mimeheader to stay compatible
|
||||
*
|
||||
* @param string $string string to encode
|
||||
* @param string $encoding target encoding
|
||||
@@ -29,38 +30,9 @@ class MimeEncode
|
||||
$current_internal_encoding = mb_internal_encoding();
|
||||
// set internal encoding, so the mimeheader encode works correctly
|
||||
mb_internal_encoding($encoding);
|
||||
// if a subject, make a work around for the broken mb_mimencode
|
||||
$pos = 0;
|
||||
// after 36 single bytes characters,
|
||||
// if then comes MB, it is broken
|
||||
// has to 2 x 36 < 74 so the mb_encode_mimeheader
|
||||
// 74 hardcoded split does not get triggered
|
||||
$split = 36;
|
||||
$_string = '';
|
||||
while ($pos < mb_strlen($string, $encoding)) {
|
||||
$output = mb_strimwidth($string, $pos, $split, "", $encoding);
|
||||
$pos += mb_strlen($output, $encoding);
|
||||
// if the strinlen is 0 here, get out of the loop
|
||||
if (!mb_strlen($output, $encoding)) {
|
||||
$pos += mb_strlen($string, $encoding);
|
||||
}
|
||||
$_string_encoded = mb_encode_mimeheader($output, $encoding);
|
||||
// only make linebreaks if we have mime encoded code inside
|
||||
// the space only belongs in the second line
|
||||
if ($_string && preg_match("/^=\?/", $_string_encoded)) {
|
||||
$_string .= $line_break . " ";
|
||||
} elseif (
|
||||
// hack for plain text with space at the end
|
||||
mb_strlen($output, $encoding) == $split &&
|
||||
mb_substr($output, -1, 1, $encoding) == " "
|
||||
) {
|
||||
// if output ends with space, add one more
|
||||
$_string_encoded .= " ";
|
||||
}
|
||||
$_string .= $_string_encoded;
|
||||
}
|
||||
// strip out any spaces BEFORE a line break
|
||||
$string = str_replace(" " . $line_break, $line_break, $_string);
|
||||
// use the internal convert to mime header
|
||||
// it works from PHP 8.2 on
|
||||
$string = mb_encode_mimeheader($string, $encoding, 'B', $line_break);
|
||||
// before we end, reset internal encoding
|
||||
mb_internal_encoding($current_internal_encoding);
|
||||
// return mime encoded string
|
||||
|
||||
@@ -35,7 +35,7 @@ class SetVarTypeNull extends Extends\SetVarTypeMain
|
||||
* @param string|null $default Default override value
|
||||
* @return string|null Input value as string or default as string/null
|
||||
*/
|
||||
public static function makeStr(mixed $val, string $default = null): ?string
|
||||
public static function makeStr(mixed $val, ?string $default = null): ?string
|
||||
{
|
||||
return SetVarTypeMain::makeStrMain($val, $default, true);
|
||||
}
|
||||
@@ -60,7 +60,7 @@ class SetVarTypeNull extends Extends\SetVarTypeMain
|
||||
* @param int|null $default Default override value
|
||||
* @return int|null Input value as int or default as int/null
|
||||
*/
|
||||
public static function makeInt(mixed $val, int $default = null): ?int
|
||||
public static function makeInt(mixed $val, ?int $default = null): ?int
|
||||
{
|
||||
return SetVarTypeMain::makeIntMain($val, $default, true);
|
||||
}
|
||||
@@ -84,7 +84,7 @@ class SetVarTypeNull extends Extends\SetVarTypeMain
|
||||
* @param float|null $default Default override value
|
||||
* @return float|null Input value as float or default as float/null
|
||||
*/
|
||||
public static function makeFloat(mixed $val, float $default = null): ?float
|
||||
public static function makeFloat(mixed $val, ?float $default = null): ?float
|
||||
{
|
||||
return SetVarTypeMain::makeFloatMain($val, $default, true);
|
||||
}
|
||||
|
||||
@@ -118,6 +118,34 @@ class Strings
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip any duplicated slahes from a path
|
||||
* eg: //foo///bar/foo.inc -> /foo/bar/foo.inc
|
||||
*
|
||||
* @param string $path Path to strip slashes from
|
||||
* @return string Clean path, on error returns original path
|
||||
*/
|
||||
public static function stripMultiplePathSlashes(string $path): string
|
||||
{
|
||||
return preg_replace(
|
||||
'#/+#',
|
||||
'/',
|
||||
$path
|
||||
) ?? $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove UTF8 BOM Byte string from line
|
||||
* Note: this is often found in CSV files exported from Excel at the first row, first element
|
||||
*
|
||||
* @param string $text
|
||||
* @return string
|
||||
*/
|
||||
public static function stripUTF8BomBytes(string $text): string
|
||||
{
|
||||
return trim($text, pack('H*', 'EFBBBF'));
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
|
||||
@@ -15,17 +15,111 @@ namespace CoreLibs\Create;
|
||||
|
||||
class Session
|
||||
{
|
||||
/** @var string current session name */
|
||||
private string $session_name = '';
|
||||
/** @var string current session id */
|
||||
private string $session_id = '';
|
||||
/** @var bool flag auto write close */
|
||||
private bool $auto_write_close = false;
|
||||
/** @var string regenerate option, default never */
|
||||
private string $regenerate = 'never';
|
||||
/** @var int regenerate interval either 1 to 100 for random or 0 to 3600 for interval */
|
||||
private int $regenerate_interval = 0;
|
||||
|
||||
/** @var array<string> allowed session id regenerate (rotate) options */
|
||||
private const ALLOWED_REGENERATE_OPTIONS = ['none', 'random', 'interval'];
|
||||
/** @var int default random interval */
|
||||
public const DEFAULT_REGENERATE_RANDOM = 100;
|
||||
/** @var int default rotate internval in minutes */
|
||||
public const DEFAULT_REGENERATE_INTERVAL = 5 * 60;
|
||||
/** @var int maximum time for regenerate interval is one hour */
|
||||
public const MAX_REGENERATE_INTERAL = 60 * 60;
|
||||
|
||||
/**
|
||||
* init a session, if array is empty or array does not have session_name set
|
||||
* then no auto init is run
|
||||
*
|
||||
* @param string $session_name if set and not empty, will start session
|
||||
* @param array{auto_write_close?:bool,session_strict?:bool,regenerate?:string,regenerate_interval?:int} $options
|
||||
*/
|
||||
public function __construct(string $session_name = '')
|
||||
{
|
||||
if (!empty($session_name)) {
|
||||
$this->startSession($session_name);
|
||||
public function __construct(
|
||||
string $session_name,
|
||||
array $options = []
|
||||
) {
|
||||
$this->setOptions($options);
|
||||
$this->initSession($session_name);
|
||||
}
|
||||
|
||||
// MARK: private methods
|
||||
|
||||
/**
|
||||
* set session class options
|
||||
*
|
||||
* @param array{auto_write_close?:bool,session_strict?:bool,regenerate?:string,regenerate_interval?:int} $options
|
||||
* @return void
|
||||
*/
|
||||
private function setOptions(array $options): void
|
||||
{
|
||||
if (
|
||||
!isset($options['auto_write_close']) ||
|
||||
!is_bool($options['auto_write_close'])
|
||||
) {
|
||||
$options['auto_write_close'] = false;
|
||||
}
|
||||
$this->auto_write_close = $options['auto_write_close'];
|
||||
if (
|
||||
!isset($options['session_strict']) ||
|
||||
!is_bool($options['session_strict'])
|
||||
) {
|
||||
$options['session_strict'] = true;
|
||||
}
|
||||
// set strict options, on not started sessiononly
|
||||
if (
|
||||
$options['session_strict'] &&
|
||||
$this->getSessionStatus() === PHP_SESSION_NONE
|
||||
) {
|
||||
// use cookies to store session IDs
|
||||
ini_set('session.use_cookies', 1);
|
||||
// use cookies only (do not send session IDs in URLs)
|
||||
ini_set('session.use_only_cookies', 1);
|
||||
// do not send session IDs in URLs
|
||||
ini_set('session.use_trans_sid', 0);
|
||||
}
|
||||
// session regenerate id options
|
||||
if (
|
||||
empty($options['regenerate']) ||
|
||||
!in_array($options['regenerate'], self::ALLOWED_REGENERATE_OPTIONS)
|
||||
) {
|
||||
$options['regenerate'] = 'never';
|
||||
}
|
||||
$this->regenerate = (string)$options['regenerate'];
|
||||
// for regenerate: 'random' (default 100)
|
||||
// regenerate_interval must be between (1 = always) and 100 (1 in 100)
|
||||
// for regenerate: 'interval' (default 5min)
|
||||
// regenerate_interval must be 0 = always, to 3600 (every hour)
|
||||
if (
|
||||
$options['regenerate'] == 'random' &&
|
||||
(
|
||||
!isset($options['regenerate_interval']) ||
|
||||
!is_numeric($options['regenerate_interval']) ||
|
||||
$options['regenerate_interval'] < 0 ||
|
||||
$options['regenerate_interval'] > 100
|
||||
)
|
||||
) {
|
||||
$options['regenerate_interval'] = self::DEFAULT_REGENERATE_RANDOM;
|
||||
}
|
||||
if (
|
||||
$options['regenerate'] == 'interval' &&
|
||||
(
|
||||
!isset($options['regenerate_interval']) ||
|
||||
!is_numeric($options['regenerate_interval']) ||
|
||||
$options['regenerate_interval'] < 1 ||
|
||||
$options['regenerate_interval'] > self::MAX_REGENERATE_INTERAL
|
||||
)
|
||||
) {
|
||||
$options['regenerate_interval'] = self::DEFAULT_REGENERATE_INTERVAL;
|
||||
}
|
||||
$this->regenerate_interval = (int)($options['regenerate_interval'] ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,37 +130,99 @@ class Session
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function startSessionCall(): void
|
||||
private function startSessionCall(): void
|
||||
{
|
||||
session_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* check if we are in CLI, we set this, so we can mock this
|
||||
* Not this is just a wrapper for the static System::checkCLI call
|
||||
* get current set session id or false if none started
|
||||
*
|
||||
* @return bool True if we are in a CLI enviroment, or false for everything else
|
||||
* @return string|false
|
||||
*/
|
||||
public function checkCliStatus(): bool
|
||||
public function getSessionIdCall(): string|false
|
||||
{
|
||||
return \CoreLibs\Get\System::checkCLI();
|
||||
return session_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set session name call. If not valid session name, will return false
|
||||
* automatically closes a session if the auto write close flag is set
|
||||
*
|
||||
* @param string $session_name A valid string for session name
|
||||
* @return bool True if session name is valid,
|
||||
* False if not
|
||||
* @return bool
|
||||
*/
|
||||
public function setSessionName(string $session_name): bool
|
||||
private function closeSessionCall(): bool
|
||||
{
|
||||
if (!$this->checkValidSessionName($session_name)) {
|
||||
if ($this->auto_write_close) {
|
||||
return $this->writeClose();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
session_name($session_name);
|
||||
return true;
|
||||
|
||||
// MARK: regenerate session
|
||||
|
||||
/**
|
||||
* auto rotate session id
|
||||
*
|
||||
* @return void
|
||||
* @throws \RuntimeException failure to regenerate session id
|
||||
* @throws \UnexpectedValueException failed to get new session id
|
||||
* @throws \RuntimeException failed to set new sesson id
|
||||
* @throws \UnexpectedValueException new session id generated does not match the new set one
|
||||
*/
|
||||
private function sessionRegenerateSessionId()
|
||||
{
|
||||
// never
|
||||
if ($this->regenerate == 'never') {
|
||||
return;
|
||||
}
|
||||
// regenerate
|
||||
if (
|
||||
!(
|
||||
// is not session obsolete
|
||||
empty($_SESSION['SESSION_REGENERATE_OBSOLETE']) &&
|
||||
(
|
||||
(
|
||||
// random
|
||||
$this->regenerate == 'random' &&
|
||||
mt_rand(1, $this->regenerate_interval) == 1
|
||||
) || (
|
||||
// interval type
|
||||
$this->regenerate == 'interval' &&
|
||||
($_SESSION['SESSION_REGENERATE_TIMESTAMP'] ?? 0) + $this->regenerate_interval < time()
|
||||
)
|
||||
)
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// Set current session to expire in 1 minute
|
||||
$_SESSION['SESSION_REGENERATE_OBSOLETE'] = true;
|
||||
$_SESSION['SESSION_REGENERATE_EXPIRES'] = time() + 60;
|
||||
$_SESSION['SESSION_REGENERATE_TIMESTAMP'] = time();
|
||||
// Create new session without destroying the old one
|
||||
if (session_regenerate_id(false) === false) {
|
||||
throw new \RuntimeException('[SESSION] Session id regeneration failed', 1);
|
||||
}
|
||||
// Grab current session ID and close both sessions to allow other scripts to use them
|
||||
if (false === ($new_session_id = $this->getSessionIdCall())) {
|
||||
throw new \UnexpectedValueException('[SESSION] getSessionIdCall did not return a session id', 2);
|
||||
}
|
||||
$this->writeClose();
|
||||
// Set session ID to the new one, and start it back up again
|
||||
if (($get_new_session_id = session_id($new_session_id)) === false) {
|
||||
throw new \RuntimeException('[SESSION] set session_id failed', 3);
|
||||
}
|
||||
if ($get_new_session_id != $new_session_id) {
|
||||
throw new \UnexpectedValueException('[SESSION] new session id does not match the new set one', 4);
|
||||
}
|
||||
$this->session_id = $new_session_id;
|
||||
$this->startSessionCall();
|
||||
// Don't want this one to expire
|
||||
unset($_SESSION['SESSION_REGENERATE_OBSOLETE']);
|
||||
unset($_SESSION['SESSION_REGENERATE_EXPIRES']);
|
||||
}
|
||||
|
||||
// MARK: session validation
|
||||
|
||||
/**
|
||||
* check if session name is valid
|
||||
@@ -94,15 +250,34 @@ class Session
|
||||
}
|
||||
|
||||
/**
|
||||
* start session with given session name if set
|
||||
* validate _SESSION key, must be valid variable
|
||||
*
|
||||
* @param int|float|string $key
|
||||
* @return true
|
||||
*/
|
||||
private function checkValidSessionEntryKey(int|float|string $key): true
|
||||
{
|
||||
if (!is_string($key) || is_numeric($key)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'[SESSION] Given key for _SESSION is not a valid value for a varaible: ' . $key,
|
||||
1
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// MARK: init session (on class start)
|
||||
|
||||
/**
|
||||
* stinitart session with given session name if set
|
||||
* aborts on command line or if sessions are not enabled
|
||||
* also aborts if session cannot be started
|
||||
* On sucess returns the session id
|
||||
*
|
||||
* @param string|null $session_name
|
||||
* @return string|bool
|
||||
* @param string $session_name
|
||||
* @return void
|
||||
*/
|
||||
public function startSession(?string $session_name = null): string|bool
|
||||
private function initSession(string $session_name): void
|
||||
{
|
||||
// we can't start sessions on command line
|
||||
if ($this->checkCliStatus()) {
|
||||
@@ -115,39 +290,95 @@ class Session
|
||||
// session_status
|
||||
// initial the session if there is no session running already
|
||||
if (!$this->checkActiveSession()) {
|
||||
// if session name is emtpy, check if there is a global set
|
||||
// this is a deprecated fallback
|
||||
$session_name = $session_name ?? $GLOBALS['SET_SESSION_NAME'] ?? '';
|
||||
// DEPRECTED: constant SET_SESSION_NAME is no longer used
|
||||
// if set, set special session name
|
||||
if (!empty($session_name)) {
|
||||
// invalid session name, abort
|
||||
if (!$this->checkValidSessionName($session_name)) {
|
||||
throw new \UnexpectedValueException('[SESSION] Invalid session name: ' . $session_name, 3);
|
||||
}
|
||||
$this->setSessionName($session_name);
|
||||
throw new \UnexpectedValueException('[SESSION] Invalid session name: ' . $this->session_name, 3);
|
||||
}
|
||||
// set session name
|
||||
$this->session_name = $session_name;
|
||||
session_name($this->session_name);
|
||||
// start session
|
||||
$this->startSessionCall();
|
||||
}
|
||||
// if we still have no active session
|
||||
// if we faild to start the session
|
||||
if (!$this->checkActiveSession()) {
|
||||
throw new \RuntimeException('[SESSION] Failed to activate session', 4);
|
||||
throw new \RuntimeException('[SESSION] Failed to activate session', 5);
|
||||
}
|
||||
if (false === ($session_id = $this->getSessionId())) {
|
||||
throw new \UnexpectedValueException('[SESSION] getSessionId did not return a session id', 5);
|
||||
if (
|
||||
!empty($_SESSION['SESSION_REGENERATE_OBSOLETE']) &&
|
||||
!empty($_SESSION['SESSION_REGENERATE_EXPIRES']) && $_SESSION['SESSION_REGENERATE_EXPIRES'] < time()
|
||||
) {
|
||||
$this->sessionDestroy();
|
||||
throw new \RuntimeException('[SESSION] Expired session found', 6);
|
||||
}
|
||||
return $session_id;
|
||||
} elseif ($session_name != $this->getSessionName()) {
|
||||
throw new \UnexpectedValueException(
|
||||
'[SESSION] Another session exists with a different name: ' . $this->getSessionName(),
|
||||
4
|
||||
);
|
||||
}
|
||||
// check session id
|
||||
if (false === ($session_id = $this->getSessionIdCall())) {
|
||||
throw new \UnexpectedValueException('[SESSION] getSessionIdCall did not return a session id', 7);
|
||||
}
|
||||
// set session id
|
||||
$this->session_id = $session_id;
|
||||
// run session id re-create from time to time
|
||||
$this->sessionRegenerateSessionId();
|
||||
// if flagged auto close, write close session
|
||||
if ($this->auto_write_close) {
|
||||
$this->writeClose();
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: public set/get status
|
||||
|
||||
/**
|
||||
* start session, will only run after initSession
|
||||
*
|
||||
* @return bool True if started, False if alrady running
|
||||
*/
|
||||
public function restartSession(): bool
|
||||
{
|
||||
if (!$this->checkActiveSession()) {
|
||||
if (empty($this->session_name)) {
|
||||
throw new \RuntimeException('[SESSION] Cannot restart session without a session name', 1);
|
||||
}
|
||||
$this->startSessionCall();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* get current set session id or false if none started
|
||||
* current set session id
|
||||
*
|
||||
* @return string|bool
|
||||
* @return string
|
||||
*/
|
||||
public function getSessionId(): string|bool
|
||||
public function getSessionId(): string
|
||||
{
|
||||
return session_id();
|
||||
return $this->session_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the auto write close flag
|
||||
*
|
||||
* @param bool $flag
|
||||
* @return Session
|
||||
*/
|
||||
public function setAutoWriteClose(bool $flag): Session
|
||||
{
|
||||
$this->auto_write_close = $flag;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* return the auto write close flag
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkAutoWriteClose(): bool
|
||||
{
|
||||
return $this->auto_write_close;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,6 +406,34 @@ class Session
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if we are in CLI, we set this, so we can mock this
|
||||
* Not this is just a wrapper for the static System::checkCLI call
|
||||
*
|
||||
* @return bool True if we are in a CLI enviroment, or false for everything else
|
||||
*/
|
||||
public function checkCliStatus(): bool
|
||||
{
|
||||
return \CoreLibs\Get\System::checkCLI();
|
||||
}
|
||||
|
||||
/**
|
||||
* get session status
|
||||
* PHP_SESSION_DISABLED if sessions are disabled.
|
||||
* PHP_SESSION_NONE if sessions are enabled, but none exists.
|
||||
* PHP_SESSION_ACTIVE if sessions are enabled, and one exists.
|
||||
*
|
||||
* https://www.php.net/manual/en/function.session-status.php
|
||||
*
|
||||
* @return int See possible return int values above
|
||||
*/
|
||||
public function getSessionStatus(): int
|
||||
{
|
||||
return session_status();
|
||||
}
|
||||
|
||||
// MARK: write close session
|
||||
|
||||
/**
|
||||
* unlock the session file, so concurrent AJAX requests can be done
|
||||
* NOTE: after this has been called, no changes in _SESSION will be stored
|
||||
@@ -188,17 +447,24 @@ class Session
|
||||
return session_write_close();
|
||||
}
|
||||
|
||||
// MARK: session close and clean up
|
||||
|
||||
/**
|
||||
* Proper destroy a session
|
||||
* - unset the _SESSION array
|
||||
* - unset cookie if cookie on and we have not strict mode
|
||||
* - unset session_name and session_id internal vars
|
||||
* - destroy session
|
||||
*
|
||||
* @return bool
|
||||
* @return bool True on successful session destroy
|
||||
*/
|
||||
public function sessionDestroy(): bool
|
||||
{
|
||||
$_SESSION = [];
|
||||
// abort to false if not unsetable
|
||||
if (!session_unset()) {
|
||||
return false;
|
||||
}
|
||||
$this->clear();
|
||||
if (
|
||||
ini_get('session.use_cookies') &&
|
||||
!ini_get('session.use_strict_mode')
|
||||
@@ -218,68 +484,93 @@ class Session
|
||||
$params['httponly']
|
||||
);
|
||||
}
|
||||
// unset internal vars
|
||||
$this->session_name = '';
|
||||
$this->session_id = '';
|
||||
return session_destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* get session status
|
||||
* PHP_SESSION_DISABLED if sessions are disabled.
|
||||
* PHP_SESSION_NONE if sessions are enabled, but none exists.
|
||||
* PHP_SESSION_ACTIVE if sessions are enabled, and one exists.
|
||||
*
|
||||
* https://www.php.net/manual/en/function.session-status.php
|
||||
*
|
||||
* @return int See possible return int values above
|
||||
*/
|
||||
public function getSessionStatus(): int
|
||||
{
|
||||
return session_status();
|
||||
}
|
||||
|
||||
// _SESSION set/unset methods
|
||||
// MARK: _SESSION set/unset methods
|
||||
|
||||
/**
|
||||
* unset all _SESSION entries
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unsetAllS(): void
|
||||
public function clear(): void
|
||||
{
|
||||
foreach (array_keys($_SESSION ?? []) as $name) {
|
||||
unset($_SESSION[$name]);
|
||||
$this->restartSession();
|
||||
if (!session_unset()) {
|
||||
throw new \RuntimeException('[SESSION] Cannot unset session vars', 1);
|
||||
}
|
||||
if (!empty($_SESSION)) {
|
||||
$_SESSION = [];
|
||||
}
|
||||
$this->closeSessionCall();
|
||||
}
|
||||
|
||||
/**
|
||||
* set _SESSION entry 'name' with any value
|
||||
*
|
||||
* @param string|int $name array name in _SESSION
|
||||
* @param string $name array name in _SESSION
|
||||
* @param mixed $value value to set (can be anything)
|
||||
* @return Session
|
||||
*/
|
||||
public function set(string $name, mixed $value): Session
|
||||
{
|
||||
$this->checkValidSessionEntryKey($name);
|
||||
$this->restartSession();
|
||||
$_SESSION[$name] = $value;
|
||||
$this->closeSessionCall();
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* set many session entries in one set
|
||||
*
|
||||
* @param array<string,mixed> $set key is the key in the _SESSION, value is any data to set
|
||||
* @return void
|
||||
*/
|
||||
public function setS(string|int $name, mixed $value): void
|
||||
public function setMany(array $set): void
|
||||
{
|
||||
$_SESSION[$name] = $value;
|
||||
$this->restartSession();
|
||||
// skip any that are not valid
|
||||
foreach ($set as $key => $value) {
|
||||
$this->checkValidSessionEntryKey($key);
|
||||
$_SESSION[$key] = $value;
|
||||
}
|
||||
$this->closeSessionCall();
|
||||
}
|
||||
|
||||
/**
|
||||
* get _SESSION 'name' entry or empty string if not set
|
||||
*
|
||||
* @param string|int $name value key to get from _SESSION
|
||||
* @return mixed value stored in _SESSION
|
||||
* @param string $name value key to get from _SESSION
|
||||
* @return mixed value stored in _SESSION, if not found set to null
|
||||
*/
|
||||
public function getS(string|int $name): mixed
|
||||
public function get(string $name): mixed
|
||||
{
|
||||
return $_SESSION[$name] ?? '';
|
||||
return $_SESSION[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* get multiple session entries
|
||||
*
|
||||
* @param array<string> $set
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function getMany(array $set): array
|
||||
{
|
||||
return array_intersect_key($_SESSION, array_flip($set));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a name is set in the _SESSION array
|
||||
*
|
||||
* @param string|int $name Name to check for
|
||||
* @param string $name Name to check for
|
||||
* @return bool True for set, False fornot set
|
||||
*/
|
||||
public function issetS(string|int $name): bool
|
||||
public function isset(string $name): bool
|
||||
{
|
||||
return isset($_SESSION[$name]);
|
||||
}
|
||||
@@ -287,67 +578,36 @@ class Session
|
||||
/**
|
||||
* unset one _SESSION entry 'name' if exists
|
||||
*
|
||||
* @param string|int $name _SESSION key name to remove
|
||||
* @return void
|
||||
* @param string $name _SESSION key name to remove
|
||||
* @return Session
|
||||
*/
|
||||
public function unsetS(string|int $name): void
|
||||
public function unset(string $name): Session
|
||||
{
|
||||
if (isset($_SESSION[$name])) {
|
||||
if (!isset($_SESSION[$name])) {
|
||||
return $this;
|
||||
}
|
||||
$this->restartSession();
|
||||
unset($_SESSION[$name]);
|
||||
$this->closeSessionCall();
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
// set/get below
|
||||
// ->var = value;
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
* reset many session entry
|
||||
*
|
||||
* @param string|int $name
|
||||
* @param mixed $value
|
||||
* @param array<string> $set list of session keys to reset
|
||||
* @return void
|
||||
*/
|
||||
public function __set(string|int $name, mixed $value): void
|
||||
public function unsetMany(array $set): void
|
||||
{
|
||||
$_SESSION[$name] = $value;
|
||||
$this->restartSession();
|
||||
foreach ($set as $key) {
|
||||
if (!isset($_SESSION[$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param string|int $name
|
||||
* @return mixed If name is not found, it will return null
|
||||
*/
|
||||
public function __get(string|int $name): mixed
|
||||
{
|
||||
if (isset($_SESSION[$name])) {
|
||||
return $_SESSION[$name];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param string|int $name
|
||||
* @return bool
|
||||
*/
|
||||
public function __isset(string|int $name): bool
|
||||
{
|
||||
return isset($_SESSION[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param string|int $name
|
||||
* @return void
|
||||
*/
|
||||
public function __unset(string|int $name): void
|
||||
{
|
||||
if (isset($_SESSION[$name])) {
|
||||
unset($_SESSION[$name]);
|
||||
unset($_SESSION[$key]);
|
||||
}
|
||||
$this->closeSessionCall();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class Uids
|
||||
$uniqid_length++;
|
||||
}
|
||||
/** @var int<1,max> make sure that internal this is correct */
|
||||
$random_bytes_length = ($uniqid_length - ($uniqid_length % 2)) / 2;
|
||||
$random_bytes_length = (int)(($uniqid_length - ($uniqid_length % 2)) / 2);
|
||||
$uniqid = bin2hex(random_bytes($random_bytes_length));
|
||||
// if not forced shorten return next lower length
|
||||
if (!$force_length) {
|
||||
@@ -56,26 +56,6 @@ class Uids
|
||||
*/
|
||||
public static function uuidv4(): string
|
||||
{
|
||||
/* return sprintf(
|
||||
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
// 32 bits for "time_low"
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
// 16 bits for "time_mid"
|
||||
mt_rand(0, 0xffff),
|
||||
// 16 bits for "time_hi_and_version",
|
||||
// four most significant bits holds version number 4
|
||||
mt_rand(0, 0x0fff) | 0x4000,
|
||||
// 16 bits, 8 bits for "clk_seq_hi_res",
|
||||
// 8 bits for "clk_seq_low",
|
||||
// two most significant bits holds zero and one for variant DCE1.1
|
||||
mt_rand(0, 0x3fff) | 0x8000,
|
||||
// 48 bits for "node"
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0xffff)
|
||||
); */
|
||||
|
||||
$data = random_bytes(16);
|
||||
assert(strlen($data) == 16);
|
||||
|
||||
@@ -93,6 +73,20 @@ class Uids
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
||||
}
|
||||
|
||||
/**
|
||||
* regex validate uuid v4
|
||||
*
|
||||
* @param string $uuidv4
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateUuuidv4(string $uuidv4): bool
|
||||
{
|
||||
if (!preg_match("/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/", $uuidv4)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a uniq id based on lengths
|
||||
*
|
||||
|
||||
@@ -236,7 +236,7 @@ class ArrayIO extends \CoreLibs\DB\IO
|
||||
*/
|
||||
public function getPkId(): int|string|null
|
||||
{
|
||||
return $this->pk_id;
|
||||
return $this->pk_id ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,7 +374,7 @@ class ArrayIO extends \CoreLibs\DB\IO
|
||||
public function dbDelete(array $table_array = [], bool $acl_limit = false): array
|
||||
{
|
||||
// is array and has values, override set and set new
|
||||
if (is_array($table_array) && count($table_array)) {
|
||||
if (count($table_array)) {
|
||||
$this->table_array = $table_array;
|
||||
}
|
||||
if (!$this->dbCheckPkSet()) {
|
||||
@@ -440,7 +440,7 @@ class ArrayIO extends \CoreLibs\DB\IO
|
||||
public function dbRead(bool $edit = false, array $table_array = []): array
|
||||
{
|
||||
// if array give, overrules internal array
|
||||
if (is_array($table_array) && count($table_array)) {
|
||||
if (count($table_array)) {
|
||||
$this->table_array = $table_array;
|
||||
}
|
||||
if (!$this->dbCheckPkSet()) {
|
||||
|
||||
121
src/DB/IO.php
121
src/DB/IO.php
@@ -284,7 +284,8 @@ class IO
|
||||
public const ERROR_HASH_TYPE = 'adler32';
|
||||
/** @var string regex to get returning with matches at position 1 */
|
||||
public const REGEX_RETURNING = '/\s+returning\s+(.+\s*(?:.+\s*)+);?$/i';
|
||||
/** @var array<string> allowed convert target for placeholder: pg or pdo (currently not available) */
|
||||
/** @var array<string> allowed convert target for placeholder:
|
||||
* pg or pdo (currently not available) */
|
||||
public const DB_CONVERT_PLACEHOLDER_TARGET = ['pg'];
|
||||
// REGEX_SELECT
|
||||
// REGEX_UPDATE
|
||||
@@ -823,6 +824,10 @@ class IO
|
||||
);
|
||||
break;
|
||||
default:
|
||||
// no context on DB_INFO
|
||||
if ($id == 'DB_INFO') {
|
||||
$context = [];
|
||||
}
|
||||
// used named arguments so we can easy change the order of debug
|
||||
$this->log->debug(
|
||||
group_id: $debug_id,
|
||||
@@ -910,7 +915,7 @@ class IO
|
||||
if ($cursor !== false) {
|
||||
[$db_prefix, $db_error_string] = $this->db_functions->__dbPrintError($cursor);
|
||||
}
|
||||
if ($cursor === false && method_exists($this->db_functions, '__dbPrintError')) {
|
||||
if ($cursor === false && method_exists($this->db_functions, '__dbPrintError')) { /** @phpstan-ignore-line */
|
||||
[$db_prefix, $db_error_string] = $this->db_functions->__dbPrintError();
|
||||
}
|
||||
// prefix the master if not the same
|
||||
@@ -1307,29 +1312,14 @@ class IO
|
||||
}
|
||||
|
||||
/**
|
||||
* count $ leading parameters only
|
||||
* count placeholder entries in the query
|
||||
*
|
||||
* @param string $query Query to check
|
||||
* @return int Number of parameters found
|
||||
*/
|
||||
private function __dbCountQueryParams(string $query): int
|
||||
{
|
||||
$match = [];
|
||||
// regex for params: only stand alone $number allowed
|
||||
// exclude all '' enclosed strings, ignore all numbers [note must start with digit]
|
||||
// can have space/tab/new line
|
||||
// must have <> = , ( [not equal, equal, comma, opening round bracket]
|
||||
// can have space/tab/new line
|
||||
// $ number with 1-9 for first and 0-9 for further digits
|
||||
// /s for matching new line in . list
|
||||
// [disabled, we don't used ^ or $] /m for multi line match
|
||||
// Matches in 1:, must be array_filtered to remove empty, count with array_unique
|
||||
preg_match_all(
|
||||
'/(?:\'.*?\')?\s*(?:\?\?|<>|[(=,])\s*(?:\d+|(?:\'.*?\')|(\$[1-9]{1}(?:[0-9]{1,})?))/s',
|
||||
$query,
|
||||
$match
|
||||
);
|
||||
return count(array_unique(array_filter($match[1])));
|
||||
return $this->db_functions->__dbCountQueryParams($query);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1729,7 +1719,7 @@ class IO
|
||||
{
|
||||
if (
|
||||
!empty($this->dbh) &&
|
||||
$this->dbh instanceof \PgSql\Connection
|
||||
$this->dbh instanceof \PgSql\Connection /** @phpstan-ignore-line future could be other */
|
||||
) {
|
||||
// reset any client encodings set
|
||||
$this->dbResetEncoding();
|
||||
@@ -1810,14 +1800,13 @@ class IO
|
||||
$html_tags = ['{b}', '{/b}', '{br}'];
|
||||
$replace_html = ['<b>', '</b>', '<br>'];
|
||||
$replace_text = ['', '', ' **** '];
|
||||
$string = '';
|
||||
$string .= '{b}-DB-info->{/b} Connected to db {b}\'' . $this->db_name . '\'{/b} ';
|
||||
$string .= 'with schema {b}\'' . $this->db_schema . '\'{/b} ';
|
||||
$string .= 'as user {b}\'' . $this->db_user . '\'{/b} ';
|
||||
$string .= 'at host {b}\'' . $this->db_host . '\'{/b} ';
|
||||
$string .= 'on port {b}\'' . $this->db_port . '\'{/b} ';
|
||||
$string .= 'with ssl mode {b}\'' . $this->db_ssl . '\'{/b}{br}';
|
||||
$string .= '{b}-DB-info->{/b} DB IO Class debug output: {b}'
|
||||
$string = '{b}-DB-info->{/b} Connected to db {b}\'' . $this->db_name . '\'{/b} '
|
||||
. 'with schema {b}\'' . $this->db_schema . '\'{/b} '
|
||||
. 'as user {b}\'' . $this->db_user . '\'{/b} '
|
||||
. 'at host {b}\'' . $this->db_host . '\'{/b} '
|
||||
. 'on port {b}\'' . $this->db_port . '\'{/b} '
|
||||
. 'with ssl mode {b}\'' . $this->db_ssl . '\'{/b}{br}'
|
||||
. '{b}-DB-info->{/b} DB IO Class debug output: {b}'
|
||||
. ($this->dbGetDebug() ? 'Yes' : 'No') . '{/b}';
|
||||
if ($log === true) {
|
||||
// if debug, remove / change b
|
||||
@@ -1825,7 +1814,7 @@ class IO
|
||||
$html_tags,
|
||||
$replace_text,
|
||||
$string
|
||||
), 'dbInfo');
|
||||
), 'DB_INFO');
|
||||
} else {
|
||||
$string = $string . '{br}';
|
||||
}
|
||||
@@ -1892,7 +1881,12 @@ class IO
|
||||
$matches = [];
|
||||
// compare has =, >, < prefix, and gets stripped
|
||||
// if the rest is not X.Y format then error
|
||||
preg_match("/^([<>=]{1,})(\d{1,})\.(\d{1,})/", $compare, $matches);
|
||||
if (!preg_match("/^([<>=]{1,})(\d{1,})\.(\d{1,})/", $compare, $matches)) {
|
||||
$this->log->error('Could not regex match compare version string', [
|
||||
"compare" => $compare
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
$compare = $matches[1];
|
||||
$to_master = $matches[2];
|
||||
$to_minor = $matches[3];
|
||||
@@ -1904,11 +1898,18 @@ class IO
|
||||
}
|
||||
// db_version can return X.Y.Z
|
||||
// we only compare the first two
|
||||
preg_match(
|
||||
if (
|
||||
!preg_match(
|
||||
"/^(\d{1,})\.(\d{1,})\.?(\d{1,})?/",
|
||||
$this->dbVersion(),
|
||||
$matches
|
||||
);
|
||||
)
|
||||
) {
|
||||
$this->log->error('Could not regex match dbVersion string', [
|
||||
"dbVersion" => $this->dbVersion()
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
$master = $matches[1];
|
||||
$minor = $matches[2];
|
||||
$version = $master . ($minor < 10 ? '0' : '') . $minor;
|
||||
@@ -1969,7 +1970,7 @@ class IO
|
||||
if (is_array($array)) {
|
||||
$this->nbsp = '';
|
||||
$string .= $this->__printArray($array);
|
||||
$this->__dbDebugMessage('db', $string, 'dbDumpData');
|
||||
$this->__dbDebugMessage('db', $string, 'DB_INFO');
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
@@ -2398,7 +2399,7 @@ class IO
|
||||
// flag if we have cache data stored at the moment
|
||||
'cached' => false,
|
||||
// when fetch array or cache read returns false
|
||||
// in loop read that means dbReturn retuns false without erro
|
||||
// in loop read that means dbReturn retuns false without error
|
||||
'finished' => false,
|
||||
// read from cache/db (pos == rows)
|
||||
'read_finished' => false,
|
||||
@@ -3141,7 +3142,8 @@ class IO
|
||||
'count' => 0,
|
||||
'query' => '',
|
||||
'result' => null,
|
||||
'returning_id' => false
|
||||
'returning_id' => false,
|
||||
'placeholder_converted' => [],
|
||||
];
|
||||
// if this is an insert query, check if we can add a return
|
||||
if ($this->dbCheckQueryForInsert($query, true)) {
|
||||
@@ -3181,6 +3183,39 @@ class IO
|
||||
$this->prepare_cursor[$stm_name]['pk_name'] = $pk_name;
|
||||
}
|
||||
}
|
||||
// QUERY PARAMS: run query params check and rewrite
|
||||
if ($this->dbGetConvertPlaceholder() === true) {
|
||||
try {
|
||||
$this->placeholder_converted = ConvertPlaceholder::convertPlaceholderInQuery(
|
||||
$query,
|
||||
null,
|
||||
$this->dbGetConvertPlaceholderTarget()
|
||||
);
|
||||
// write the new queries over the old
|
||||
if (!empty($this->placeholder_converted['query'])) {
|
||||
$query = $this->placeholder_converted['query'];
|
||||
}
|
||||
$this->prepare_cursor[$stm_name]['placeholder_converted'] = $this->placeholder_converted;
|
||||
} catch (\OutOfRangeException $e) {
|
||||
$this->__dbError($e->getCode(), context:[
|
||||
'statement_name' => $stm_name,
|
||||
'query' => $query,
|
||||
'location' => 'dbPrepare',
|
||||
'error' => 'OutOfRangeException',
|
||||
'exception' => $e
|
||||
]);
|
||||
return false;
|
||||
} catch (\RuntimeException $e) {
|
||||
$this->__dbError($e->getCode(), context:[
|
||||
'statement_name' => $stm_name,
|
||||
'query' => $query,
|
||||
'location' => 'dbPrepare',
|
||||
'error' => 'RuntimeException',
|
||||
'exception' => $e
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// check prepared curser parameter count
|
||||
$this->prepare_cursor[$stm_name]['count'] = $this->__dbCountQueryParams($query);
|
||||
$this->prepare_cursor[$stm_name]['query'] = $query;
|
||||
@@ -3716,7 +3751,7 @@ class IO
|
||||
}
|
||||
|
||||
/**
|
||||
* convert db values (set)
|
||||
* convert db values (set) to php matching types
|
||||
*
|
||||
* @param Convert $convert
|
||||
* @return void
|
||||
@@ -3727,7 +3762,7 @@ class IO
|
||||
}
|
||||
|
||||
/**
|
||||
* unsert convert db values flag
|
||||
* unsert convert db values flag for converting db to php matching types
|
||||
*
|
||||
* @param Convert $convert
|
||||
* @return void
|
||||
@@ -3738,7 +3773,7 @@ class IO
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset to origincal config file set
|
||||
* Reset to original config file set for converting db to php matching type
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
@@ -3750,7 +3785,7 @@ class IO
|
||||
}
|
||||
|
||||
/**
|
||||
* check if a conert flag is set
|
||||
* check if a convert flag is set for converting db to php matching type
|
||||
*
|
||||
* @param Convert $convert
|
||||
* @return bool
|
||||
@@ -3764,7 +3799,7 @@ class IO
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if we want to auto convert PDO/\Pg placeholders
|
||||
* Set if we want to auto convert to PDO/\Pg placeholders
|
||||
*
|
||||
* @param bool $flag
|
||||
* @return void
|
||||
@@ -4275,7 +4310,7 @@ class IO
|
||||
* @param string $stm_name The name of the stored statement
|
||||
* @param string $key Key field name in prepared cursor array
|
||||
* Allowed are: pk_name, count, query, returning_id
|
||||
* @return null|string|int|bool Entry from each of the valid keys
|
||||
* @return null|string|int|bool|array<string,mixed> Entry from each of the valid keys
|
||||
* Will return false on error
|
||||
* Not ethat returnin_id also can return false
|
||||
* but will not set an error entry
|
||||
@@ -4283,7 +4318,7 @@ class IO
|
||||
public function dbGetPrepareCursorValue(
|
||||
string $stm_name,
|
||||
string $key
|
||||
): null|string|int|bool {
|
||||
): null|string|int|bool|array {
|
||||
// if no statement name
|
||||
if (empty($stm_name)) {
|
||||
$this->__dbError(
|
||||
@@ -4294,7 +4329,7 @@ class IO
|
||||
return false;
|
||||
}
|
||||
// if not a valid key
|
||||
if (!in_array($key, ['pk_name', 'count', 'query', 'returning_id'])) {
|
||||
if (!in_array($key, ['pk_name', 'count', 'query', 'returning_id', 'placeholder_converted'])) {
|
||||
$this->__dbError(
|
||||
102,
|
||||
false,
|
||||
|
||||
@@ -285,6 +285,22 @@ interface SqlFunctions
|
||||
*/
|
||||
public function __dbConnectionBusySocketWait(int $timeout_seconds = 3): bool;
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param string $parameter
|
||||
* @param bool $strip
|
||||
* @return string
|
||||
*/
|
||||
public function __dbVersionInfo(string $parameter, bool $strip = true): string;
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function __dbVersionInfoParameterList(): array;
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
@@ -292,6 +308,13 @@ interface SqlFunctions
|
||||
*/
|
||||
public function __dbVersion(): string;
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function __dbVersionNumeric(): int;
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
@@ -306,6 +329,14 @@ interface SqlFunctions
|
||||
?int &$end = null
|
||||
): ?array;
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param string $parameter
|
||||
* @return string|bool
|
||||
*/
|
||||
public function __dbParameter(string $parameter): string|bool;
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
@@ -343,6 +374,14 @@ interface SqlFunctions
|
||||
* @return string
|
||||
*/
|
||||
public function __dbGetEncoding(): string;
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param string $query
|
||||
* @return int
|
||||
*/
|
||||
public function __dbCountQueryParams(string $query): int;
|
||||
}
|
||||
|
||||
// __END__
|
||||
|
||||
@@ -51,6 +51,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\DB\SQL;
|
||||
|
||||
use CoreLibs\DB\Support\ConvertPlaceholder;
|
||||
|
||||
// below no ignore is needed if we want to use PgSql interface checks with PHP 8.0
|
||||
// as main system. Currently all @var sets are written as object
|
||||
/** @#phan-file-suppress PhanUndeclaredTypeProperty,PhanUndeclaredTypeParameter,PhanUndeclaredTypeReturnType */
|
||||
@@ -102,7 +104,7 @@ class PgSQL implements Interface\SqlFunctions
|
||||
* SELECT foo FROM bar WHERE foobar = $1
|
||||
*
|
||||
* @param string $query Query string with placeholders $1, ..
|
||||
* @param array<mixed> $params Matching parameters for each placerhold
|
||||
* @param array<mixed> $params Matching parameters for each placeholder
|
||||
* @return \PgSql\Result|false Query result
|
||||
*/
|
||||
public function __dbQueryParams(string $query, array $params): \PgSql\Result|false
|
||||
@@ -140,7 +142,7 @@ class PgSQL implements Interface\SqlFunctions
|
||||
* sends an async query to the server with params
|
||||
*
|
||||
* @param string $query Query string with placeholders $1, ..
|
||||
* @param array<mixed> $params Matching parameters for each placerhold
|
||||
* @param array<mixed> $params Matching parameters for each placeholder
|
||||
* @return bool true/false Query sent successful status
|
||||
*/
|
||||
public function __dbSendQueryParams(string $query, array $params): bool
|
||||
@@ -405,17 +407,13 @@ class PgSQL implements Interface\SqlFunctions
|
||||
}
|
||||
// no PK name given at all
|
||||
if (empty($pk_name)) {
|
||||
// if name is plurar, make it singular
|
||||
// if (preg_match("/.*s$/i", $table))
|
||||
// $table = substr($table, 0, -1);
|
||||
// set pk_name to "id"
|
||||
$pk_name = $table . "_id";
|
||||
}
|
||||
$seq = ($schema ? $schema . '.' : '') . $table . "_" . $pk_name . "_seq";
|
||||
$q = "SELECT CURRVAL('$seq') AS insert_id";
|
||||
$q = "SELECT CURRVAL(pg_get_serial_sequence($1, $2)) AS insert_id";
|
||||
// I have to do manually or I overwrite the original insert internal vars ...
|
||||
if ($q = $this->__dbQuery($q)) {
|
||||
if (is_array($res = $this->__dbFetchArray($q))) {
|
||||
if ($cursor = $this->__dbQueryParams($q, [$table, $pk_name])) {
|
||||
if (is_array($res = $this->__dbFetchArray($cursor))) {
|
||||
list($id) = $res;
|
||||
} else {
|
||||
return false;
|
||||
@@ -449,26 +447,36 @@ class PgSQL implements Interface\SqlFunctions
|
||||
$table_prefix = $schema . '.';
|
||||
}
|
||||
}
|
||||
$params = [$table_prefix . $table];
|
||||
$replace = ['', ''];
|
||||
// read from table the PK name
|
||||
// faster primary key get
|
||||
$q = "SELECT pg_attribute.attname AS column_name, "
|
||||
. "format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS type "
|
||||
. "FROM pg_index, pg_class, pg_attribute ";
|
||||
$q = <<<SQL
|
||||
SELECT
|
||||
pg_attribute.attname AS column_name,
|
||||
format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS type
|
||||
FROM pg_index, pg_class, pg_attribute{PG_NAMESPACE}
|
||||
WHERE
|
||||
-- regclass translates the OID to the name
|
||||
pg_class.oid = $1::regclass AND
|
||||
indrelid = pg_class.oid AND
|
||||
pg_attribute.attrelid = pg_class.oid AND
|
||||
pg_attribute.attnum = any(pg_index.indkey) AND
|
||||
indisprimary
|
||||
{NSPNAME}
|
||||
SQL;
|
||||
if ($schema) {
|
||||
$q .= ", pg_namespace ";
|
||||
$params[] = $schema;
|
||||
$replace = [
|
||||
", pg_namespace",
|
||||
"AND pg_class.relnamespace = pg_namespace.oid AND nspname = $2"
|
||||
];
|
||||
}
|
||||
$q .= "WHERE "
|
||||
// regclass translates the OID to the name
|
||||
. "pg_class.oid = '" . $table_prefix . $table . "'::regclass AND "
|
||||
. "indrelid = pg_class.oid AND ";
|
||||
if ($schema) {
|
||||
$q .= "nspname = '" . $schema . "' AND "
|
||||
. "pg_class.relnamespace = pg_namespace.oid AND ";
|
||||
}
|
||||
$q .= "pg_attribute.attrelid = pg_class.oid AND "
|
||||
. "pg_attribute.attnum = any(pg_index.indkey) "
|
||||
. "AND indisprimary";
|
||||
$cursor = $this->__dbQuery($q);
|
||||
$cursor = $this->__dbQueryParams(str_replace(
|
||||
['{PG_NAMESPACE}', '{NSPNAME}'],
|
||||
$replace,
|
||||
$q
|
||||
), $params);
|
||||
if ($cursor !== false) {
|
||||
$__db_fetch_array = $this->__dbFetchArray($cursor);
|
||||
if (!is_array($__db_fetch_array)) {
|
||||
@@ -893,11 +901,13 @@ class PgSQL implements Interface\SqlFunctions
|
||||
public function __dbSetSchema(string $db_schema): int
|
||||
{
|
||||
// check if schema actually exists
|
||||
$query = "SELECT EXISTS("
|
||||
. "SELECT 1 FROM information_schema.schemata "
|
||||
. "WHERE schema_name = " . $this->__dbEscapeLiteral($db_schema)
|
||||
. ")";
|
||||
$cursor = $this->__dbQuery($query);
|
||||
$query = <<<SQL
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM information_schema.schemata
|
||||
WHERE schema_name = $1
|
||||
)
|
||||
SQL;
|
||||
$cursor = $this->__dbQueryParams($query, [$db_schema]);
|
||||
// abort if execution fails
|
||||
if ($cursor === false) {
|
||||
return 1;
|
||||
@@ -966,6 +976,34 @@ class PgSQL implements Interface\SqlFunctions
|
||||
{
|
||||
return $this->__dbShow('client_encoding');
|
||||
}
|
||||
|
||||
/**
|
||||
* Count placeholder queries. $ only
|
||||
*
|
||||
* @param string $query
|
||||
* @return int
|
||||
*/
|
||||
public function __dbCountQueryParams(string $query): int
|
||||
{
|
||||
$matches = [];
|
||||
// regex for params: only stand alone $number allowed
|
||||
// exclude all '' enclosed strings, ignore all numbers [note must start with digit]
|
||||
// can have space/tab/new line
|
||||
// must have <> = , ( [not equal, equal, comma, opening round bracket]
|
||||
// can have space/tab/new line
|
||||
// $ number with 1-9 for first and 0-9 for further digits
|
||||
// Collects also PDO ? and :named, but they are ignored
|
||||
// /s for matching new line in . list
|
||||
// [disabled, we don't used ^ or $] /m for multi line match
|
||||
// Matches in 1:, must be array_filtered to remove empty, count with array_unique
|
||||
// Regex located in the ConvertPlaceholder class
|
||||
preg_match_all(
|
||||
ConvertPlaceholder::REGEX_LOOKUP_PLACEHOLDERS,
|
||||
$query,
|
||||
$matches
|
||||
);
|
||||
return count(array_unique(array_filter($matches[3])));
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
|
||||
@@ -14,6 +14,87 @@ namespace CoreLibs\DB\Support;
|
||||
|
||||
class ConvertPlaceholder
|
||||
{
|
||||
// NOTE for missing: range */+ are not iplemented in the regex below, but - is for now
|
||||
// NOTE some combinations are allowed, but the query will fail before this
|
||||
/** @var string split regex, entries before $ group */
|
||||
private const PATTERN_QUERY_SPLIT =
|
||||
'\?\?|' // UNKNOWN: double ??, is this to avoid something?
|
||||
. '[\(,]|' // for ',' and '(' mostly in INSERT or ANY()
|
||||
. '[<>=]|' // general set for <, >, = in any query with any combination
|
||||
. '\^@|' // text search for start from text with ^@
|
||||
. '\|\||' // 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
|
||||
* 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*';
|
||||
/** @var string parts to ignore in the SQL */
|
||||
private const PATTERN_IGNORE =
|
||||
// digit -> ignore
|
||||
'\d+|'
|
||||
// other string -> ignore
|
||||
. '(?:\'.*?\')|';
|
||||
/** @var string named parameters */
|
||||
private const PATTERN_NAMED = '(:\w+)';
|
||||
/** @var string question mark parameters */
|
||||
private const PATTERN_QUESTION_MARK = '(?:(?:\?\?)?\s*(\?{1}))';
|
||||
/** @var string numbered parameters */
|
||||
private const PATTERN_NUMBERED = '(\$[1-9]{1}(?:[0-9]{1,})?)';
|
||||
// below here are full regex that will be used
|
||||
/** @var string replace regex for named (:...) entries */
|
||||
public const REGEX_REPLACE_NAMED = '/'
|
||||
. '(' . self::PATTERN_ELEMENT . ')'
|
||||
. self::PATTERN_COMMENT
|
||||
. '('
|
||||
. self::PATTERN_IGNORE
|
||||
. self::PATTERN_NAMED
|
||||
. ')'
|
||||
. '/s';
|
||||
/** @var string replace regex for question mark (?) entries */
|
||||
public const REGEX_REPLACE_QUESTION_MARK = '/'
|
||||
. '(' . self::PATTERN_ELEMENT . ')'
|
||||
. self::PATTERN_COMMENT
|
||||
. '('
|
||||
. self::PATTERN_IGNORE
|
||||
. self::PATTERN_QUESTION_MARK
|
||||
. ')'
|
||||
. '/s';
|
||||
/** @var string replace regex for numbered ($n) entries */
|
||||
public const REGEX_REPLACE_NUMBERED = '/'
|
||||
. '(' . self::PATTERN_ELEMENT . ')'
|
||||
. self::PATTERN_COMMENT
|
||||
. '('
|
||||
. self::PATTERN_IGNORE
|
||||
. self::PATTERN_NUMBERED
|
||||
. ')'
|
||||
. '/s';
|
||||
/** @var string the main lookup query for all placeholders */
|
||||
public const REGEX_LOOKUP_PLACEHOLDERS = '/'
|
||||
// prefix string part, must match towards
|
||||
// seperator for ( = , ? - [and json/jsonb in pg doc section 9.15]
|
||||
. self::PATTERN_ELEMENT
|
||||
. self::PATTERN_COMMENT
|
||||
// match for replace part
|
||||
. '(?:'
|
||||
// ignore parts
|
||||
. self::PATTERN_IGNORE
|
||||
// :name named part (PDO) [1]
|
||||
. self::PATTERN_NAMED . '|'
|
||||
// ? question mark part (PDO) [2]
|
||||
. self::PATTERN_QUESTION_MARK . '|'
|
||||
// $n numbered part (\PG php) [3]
|
||||
. self::PATTERN_NUMBERED
|
||||
// end match
|
||||
. ')'
|
||||
// single line -> add line break to matches in "."
|
||||
. '/s';
|
||||
|
||||
/**
|
||||
* Convert PDO type query with placeholders to \PG style and vica versa
|
||||
* For PDO to: ? and :named
|
||||
@@ -27,42 +108,24 @@ class ConvertPlaceholder
|
||||
* found has -1 if an error occoured in the preg_match_all call
|
||||
*
|
||||
* @param string $query Query with placeholders to convert
|
||||
* @param array<mixed> $params The parameters that are used for the query, and will be updated
|
||||
* @param ?array<mixed> $params The parameters that are used for the query, and will be updated
|
||||
* @param string $convert_to Either pdo or pg, will be converted to lower case for check
|
||||
* @return array{original:array{query:string,params:array<mixed>},type:''|'named'|'numbered'|'question_mark',found:int,matches:array<string>,params_lookup:array<mixed>,query:string,params:array<mixed>}
|
||||
* @throws \OutOfRangeException 200
|
||||
* @return array{original:array{query:string,params:array<mixed>,empty_params:bool},type:''|'named'|'numbered'|'question_mark',found:int,matches:array<string>,params_lookup:array<mixed>,query:string,params:array<mixed>}
|
||||
* @throws \OutOfRangeException 200 If mixed placeholder types
|
||||
* @throws \InvalidArgumentException 300 or 301 if wrong convert to with found placeholders
|
||||
*/
|
||||
public static function convertPlaceholderInQuery(
|
||||
string $query,
|
||||
array $params,
|
||||
?array $params,
|
||||
string $convert_to = 'pg'
|
||||
): array {
|
||||
$convert_to = strtolower($convert_to);
|
||||
$matches = [];
|
||||
$pattern = '/'
|
||||
// prefix string part, must match towards
|
||||
. '(?:\'.*?\')?\s*(?:\?\?|[(=,])\s*'
|
||||
// match for replace part
|
||||
. '(?:'
|
||||
// digit -> ignore
|
||||
. '\d+|'
|
||||
// other string -> ignore
|
||||
. '(?:\'.*?\')|'
|
||||
// :name named part (PDO)
|
||||
. '(:\w+)|'
|
||||
// ? question mark part (PDO)
|
||||
. '(?:(?:\?\?)?\s*(\?{1}))|'
|
||||
// $n numbered part (\PG php)
|
||||
. '(\$[1-9]{1}(?:[0-9]{1,})?)'
|
||||
// end match
|
||||
. ')'
|
||||
// single line -> add line break to matches in "."
|
||||
. '/s';
|
||||
// matches:
|
||||
// 1: :named
|
||||
// 2: ? question mark
|
||||
// 3: $n numbered
|
||||
$found = preg_match_all($pattern, $query, $matches, PREG_UNMATCHED_AS_NULL);
|
||||
$found = preg_match_all(self::REGEX_LOOKUP_PLACEHOLDERS, $query, $matches, PREG_UNMATCHED_AS_NULL);
|
||||
// if false or null set to -1
|
||||
// || $found === null
|
||||
if ($found === false) {
|
||||
@@ -75,10 +138,10 @@ class ConvertPlaceholder
|
||||
/** @var array<string> 3: $n matches */
|
||||
$numbered_matches = array_filter($matches[3]);
|
||||
// count matches
|
||||
$count_named = count($named_matches);
|
||||
$count_named = count(array_unique($named_matches));
|
||||
$count_qmark = count($qmark_matches);
|
||||
$count_numbered = count($numbered_matches);
|
||||
// throw if mixed
|
||||
$count_numbered = count(array_unique($numbered_matches));
|
||||
// throw exception if mixed found
|
||||
if (
|
||||
($count_named && $count_qmark) ||
|
||||
($count_named && $count_numbered) ||
|
||||
@@ -86,35 +149,114 @@ class ConvertPlaceholder
|
||||
) {
|
||||
throw new \OutOfRangeException('Cannot have named, question mark and numbered in the same query', 200);
|
||||
}
|
||||
// rebuild
|
||||
$matches_return = [];
|
||||
$type = '';
|
||||
// // throw if invalid conversion
|
||||
// if (($count_named || $count_qmark) && $convert_to != 'pg') {
|
||||
// throw new \InvalidArgumentException('Cannot convert from named or question mark placeholders to PDO', 300);
|
||||
// }
|
||||
// if ($count_numbered && $convert_to != 'pdo') {
|
||||
// throw new \InvalidArgumentException('Cannot convert from numbered placeholders to Pg', 301);
|
||||
// }
|
||||
// return array
|
||||
$return_placeholders = [
|
||||
// original
|
||||
'original' => [
|
||||
'query' => $query,
|
||||
'params' => $params ?? [],
|
||||
'empty_params' => $params === null ? true : false,
|
||||
],
|
||||
// type found, empty if nothing was done
|
||||
'type' => '',
|
||||
// int: found, not found; -1: problem (set from false)
|
||||
'found' => (int)$found,
|
||||
'matches' => [],
|
||||
// old to new lookup check
|
||||
'params_lookup' => [],
|
||||
// this must match the count in params in new
|
||||
'needed' => 0,
|
||||
// new
|
||||
'query' => '',
|
||||
'params' => [],
|
||||
];
|
||||
// replace basic regex and name settings
|
||||
if ($count_named) {
|
||||
$return_placeholders['type'] = 'named';
|
||||
$return_placeholders['matches'] = $named_matches;
|
||||
$return_placeholders['needed'] = $count_named;
|
||||
} elseif ($count_qmark) {
|
||||
$return_placeholders['type'] = 'question_mark';
|
||||
$return_placeholders['matches'] = $qmark_matches;
|
||||
$return_placeholders['needed'] = $count_qmark;
|
||||
// for each ?:DTN: -> replace with $1 ... $n, any remaining :DTN: remove
|
||||
} elseif ($count_numbered) {
|
||||
$return_placeholders['type'] = 'numbered';
|
||||
$return_placeholders['matches'] = $numbered_matches;
|
||||
$return_placeholders['needed'] = $count_numbered;
|
||||
}
|
||||
// run convert only if matching type and direction
|
||||
if (
|
||||
(($count_named || $count_qmark) && $convert_to == 'pg') ||
|
||||
($count_numbered && $convert_to == 'pdo')
|
||||
) {
|
||||
$param_list = self::updateParamList($return_placeholders);
|
||||
$return_placeholders['params_lookup'] = $param_list['params_lookup'];
|
||||
$return_placeholders['query'] = $param_list['query'];
|
||||
$return_placeholders['params'] = $param_list['params'];
|
||||
}
|
||||
// return data
|
||||
return $return_placeholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the params list from one style to the other to match the query output
|
||||
* if original.empty_params is set to true, no params replacement is done
|
||||
* if param replacement has been done in a dbPrepare then this has to be run
|
||||
* with the return palceholders array with params in original filled and empty_params turned off
|
||||
*
|
||||
* phpcs:disable Generic.Files.LineLength
|
||||
* @param array{original:array{query:string,params:array<mixed>,empty_params:bool},type:''|'named'|'numbered'|'question_mark',found:int,matches?:array<string>,params_lookup?:array<mixed>,query?:string,params?:array<mixed>} $converted_placeholders
|
||||
* phpcs:enable Generic.Files.LineLength
|
||||
* @return array{params_lookup:array<mixed>,query:string,params:array<mixed>}
|
||||
*/
|
||||
public static function updateParamList(array $converted_placeholders): array
|
||||
{
|
||||
// skip if nothing set
|
||||
if (!$converted_placeholders['found']) {
|
||||
return [
|
||||
'params_lookup' => [],
|
||||
'query' => '',
|
||||
'params' => []
|
||||
];
|
||||
}
|
||||
$query_new = '';
|
||||
$params_new = [];
|
||||
$params_lookup = [];
|
||||
if ($count_named && $convert_to == 'pg') {
|
||||
$type = 'named';
|
||||
$matches_return = $named_matches;
|
||||
// only check for :named
|
||||
$pattern_replace = '/((?:\'.*?\')?\s*(?:\?\?|[(=,])\s*)(\d+|(?:\'.*?\')|(:\w+))/s';
|
||||
// set to null if params is empty
|
||||
$params = $converted_placeholders['original']['params'];
|
||||
$empty_params = $converted_placeholders['original']['empty_params'];
|
||||
switch ($converted_placeholders['type']) {
|
||||
case 'named':
|
||||
// 0: full
|
||||
// 0: full
|
||||
// 1: pre part
|
||||
// 2: keep part UNLESS '3' is set
|
||||
// 3: replace part :named
|
||||
$pos = 0;
|
||||
$query_new = preg_replace_callback(
|
||||
$pattern_replace,
|
||||
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params) {
|
||||
self::REGEX_REPLACE_NAMED,
|
||||
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) {
|
||||
// only count up if $match[3] is not yet in lookup table
|
||||
if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) {
|
||||
$pos++;
|
||||
$params_lookup[$matches[3]] = '$' . $pos;
|
||||
// skip params setup if param list is empty
|
||||
if (!$empty_params) {
|
||||
$params_new[] = $params[$matches[3]] ??
|
||||
throw new \RuntimeException(
|
||||
'Cannot lookup ' . $matches[3] . ' in params list',
|
||||
210
|
||||
);
|
||||
}
|
||||
}
|
||||
// add the connectors back (1), and the data sets only if no replacement will be done
|
||||
return $matches[1] . (
|
||||
empty($matches[3]) ?
|
||||
@@ -126,22 +268,21 @@ class ConvertPlaceholder
|
||||
)
|
||||
);
|
||||
},
|
||||
$query
|
||||
$converted_placeholders['original']['query']
|
||||
);
|
||||
} elseif ($count_qmark && $convert_to == 'pg') {
|
||||
$type = 'question_mark';
|
||||
$matches_return = $qmark_matches;
|
||||
break;
|
||||
case 'question_mark':
|
||||
if (!$empty_params) {
|
||||
// order and data stays the same
|
||||
$params_new = $params;
|
||||
// only check for ?
|
||||
$pattern_replace = '/((?:\'.*?\')?\s*(?:\?\?|[(=,])\s*)(\d+|(?:\'.*?\')|(?:(?:\?\?)?\s*(\?{1})))/s';
|
||||
$params_new = $params ?? [];
|
||||
}
|
||||
// 0: full
|
||||
// 1: pre part
|
||||
// 2: keep part UNLESS '3' is set
|
||||
// 3: replace part ?
|
||||
$pos = 0;
|
||||
$query_new = preg_replace_callback(
|
||||
$pattern_replace,
|
||||
self::REGEX_REPLACE_QUESTION_MARK,
|
||||
function ($matches) use (&$pos, &$params_lookup) {
|
||||
// only count pos up for actual replacements we will do
|
||||
if (!empty($matches[3])) {
|
||||
@@ -155,33 +296,31 @@ class ConvertPlaceholder
|
||||
'$' . $pos
|
||||
);
|
||||
},
|
||||
$query
|
||||
$converted_placeholders['original']['query']
|
||||
);
|
||||
// for each ?:DTN: -> replace with $1 ... $n, any remaining :DTN: remove
|
||||
} elseif ($count_numbered && $convert_to == 'pdo') {
|
||||
// convert numbered to named
|
||||
$type = 'numbered';
|
||||
$matches_return = $numbered_matches;
|
||||
// only check for $n
|
||||
$pattern_replace = '/((?:\'.*?\')?\s*(?:\?\?|[(=,])\s*)(\d+|(?:\'.*?\')|(\$[1-9]{1}(?:[0-9]{1,})?))/s';
|
||||
break;
|
||||
case 'numbered':
|
||||
// 0: full
|
||||
// 1: pre part
|
||||
// 2: keep part UNLESS '3' is set
|
||||
// 3: replace part $numbered
|
||||
$pos = 0;
|
||||
$query_new = preg_replace_callback(
|
||||
$pattern_replace,
|
||||
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params) {
|
||||
self::REGEX_REPLACE_NUMBERED,
|
||||
function ($matches) use (&$pos, &$params_new, &$params_lookup, $params, $empty_params) {
|
||||
// only count up if $match[3] is not yet in lookup table
|
||||
if (!empty($matches[3]) && empty($params_lookup[$matches[3]])) {
|
||||
$pos++;
|
||||
$params_lookup[$matches[3]] = ':' . $pos . '_named';
|
||||
// skip params setup if param list is empty
|
||||
if (!$empty_params) {
|
||||
$params_new[] = $params[($pos - 1)] ??
|
||||
throw new \RuntimeException(
|
||||
'Cannot lookup ' . ($pos - 1) . ' in params list',
|
||||
220
|
||||
);
|
||||
}
|
||||
}
|
||||
// add the connectors back (1), and the data sets only if no replacement will be done
|
||||
return $matches[1] . (
|
||||
empty($matches[3]) ?
|
||||
@@ -193,24 +332,12 @@ class ConvertPlaceholder
|
||||
)
|
||||
);
|
||||
},
|
||||
$query
|
||||
$converted_placeholders['original']['query']
|
||||
);
|
||||
break;
|
||||
}
|
||||
// return, old query is always set
|
||||
return [
|
||||
// original
|
||||
'original' => [
|
||||
'query' => $query,
|
||||
'params' => $params,
|
||||
],
|
||||
// type found, empty if nothing was done
|
||||
'type' => $type,
|
||||
// int: found, not found; -1: problem (set from false)
|
||||
'found' => (int)$found,
|
||||
'matches' => $matches_return,
|
||||
// old to new lookup check
|
||||
'params_lookup' => $params_lookup,
|
||||
// new
|
||||
'query' => $query_new ?? '',
|
||||
'params' => $params_new,
|
||||
];
|
||||
|
||||
@@ -116,6 +116,29 @@ class System
|
||||
3
|
||||
) === 'cli' ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all IP addresses
|
||||
* REMOTE_ADDR, HTTP_X_FORWARD_FOR, CLIENT_IP
|
||||
* and retuns them in an array with index of io source
|
||||
* if address source has addresses with "," will add "-array" with these as array block
|
||||
*
|
||||
* @return array<string,string|array<string>>
|
||||
*/
|
||||
public static function getIpAddresses(): array
|
||||
{
|
||||
$ip_addr = [];
|
||||
foreach (['REMOTE_ADDR', 'HTTP_X_FORWARDED_FOR', 'CLIENT_IP'] as $_ip_source) {
|
||||
if (!empty($_SERVER[$_ip_source])) {
|
||||
$ip_addr[$_ip_source] = $_SERVER[$_ip_source];
|
||||
// same level as ARRAY IF there is a , inside
|
||||
if (strstr($_SERVER[$_ip_source], ',') !== false) {
|
||||
$ip_addr[$_ip_source . '-array'] = explode(',', $_SERVER[$_ip_source]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $ip_addr;
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
|
||||
@@ -46,7 +46,7 @@ class CachedFileReader extends \CoreLibs\Language\Core\StringReader
|
||||
if (!is_resource($fd)) {
|
||||
$this->error = 3; // Cannot read file, probably permissions
|
||||
} else {
|
||||
$this->fd_str = fread($fd, filesize($filename) ?: 0) ?: '';
|
||||
$this->fd_str = fread($fd, filesize($filename) ?: 1) ?: '';
|
||||
fclose($fd);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -190,7 +190,6 @@ class GetTextReader
|
||||
private function loadTables(): void
|
||||
{
|
||||
if (
|
||||
is_array($this->cache_translations) &&
|
||||
is_array($this->table_originals) &&
|
||||
is_array($this->table_translations)
|
||||
) {
|
||||
@@ -318,10 +317,7 @@ class GetTextReader
|
||||
|
||||
if ($this->enable_cache) {
|
||||
// Caching enabled, get translated string from cache
|
||||
if (
|
||||
is_array($this->cache_translations) &&
|
||||
array_key_exists($string, $this->cache_translations)
|
||||
) {
|
||||
if (array_key_exists($string, $this->cache_translations)) {
|
||||
return $this->cache_translations[$string];
|
||||
} else {
|
||||
return $string;
|
||||
@@ -481,7 +477,7 @@ class GetTextReader
|
||||
$key = $single . chr(0) . $plural;
|
||||
|
||||
if ($this->enable_cache) {
|
||||
if (is_array($this->cache_translations) && !array_key_exists($key, $this->cache_translations)) {
|
||||
if (!array_key_exists($key, $this->cache_translations)) {
|
||||
return ($number != 1) ? $plural : $single;
|
||||
} else {
|
||||
$result = $this->cache_translations[$key];
|
||||
|
||||
@@ -128,7 +128,7 @@ class GetLocale
|
||||
$matches
|
||||
)
|
||||
) {
|
||||
$lang = ($matches['lang'] ?? 'en')
|
||||
$lang = $matches['lang']
|
||||
// add country only if set
|
||||
. (!empty($matches['country']) ? '_' . $matches['country'] : '');
|
||||
} else {
|
||||
@@ -235,7 +235,7 @@ class GetLocale
|
||||
$matches
|
||||
)
|
||||
) {
|
||||
$lang = ($matches['lang'] ?? 'en')
|
||||
$lang = $matches['lang']
|
||||
// add country only if set
|
||||
. (!empty($matches['country']) ? '_' . $matches['country'] : '');
|
||||
} else {
|
||||
|
||||
@@ -24,16 +24,20 @@ class ErrorMessage
|
||||
|
||||
/** @var bool $log_error global flag to log error level message */
|
||||
private bool $log_error = false;
|
||||
/** @var bool $log_warning global flat to log warning level messages */
|
||||
private bool $log_warning = false;
|
||||
|
||||
/**
|
||||
* init ErrorMessage
|
||||
*
|
||||
* @param \CoreLibs\Logging\Logging $log
|
||||
* @param null|bool $log_error [=null], defaults to false if log is not level debug
|
||||
* @param null|bool $log_warning [=null], defaults to false if log is not level debug
|
||||
*/
|
||||
public function __construct(
|
||||
\CoreLibs\Logging\Logging $log,
|
||||
?bool $log_error = null
|
||||
?bool $log_error = null,
|
||||
?bool $log_warning = null
|
||||
) {
|
||||
$this->log = $log;
|
||||
// if log default logging is debug then log_error is default set to true
|
||||
@@ -43,6 +47,13 @@ class ErrorMessage
|
||||
$log_error = $log_error ?? false;
|
||||
}
|
||||
$this->log_error = $log_error;
|
||||
// if log default logging is debug then log_warning is default set to true
|
||||
if ($this->log->loggingLevelIsDebug() && $log_warning === null) {
|
||||
$log_warning = true;
|
||||
} else {
|
||||
$log_warning = $log_warning ?? false;
|
||||
}
|
||||
$this->log_warning = $log_warning;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,6 +92,8 @@ class ErrorMessage
|
||||
* @param array<mixed> $context Additionl info for abort/crash messages
|
||||
* @param bool|null $log_error [=null] log level 'error' to error, if null use global,
|
||||
* else set for this call only
|
||||
* @param bool|null $log_warning [=null] log level 'warning' to warning, if null use global,
|
||||
* else set for this call only
|
||||
*/
|
||||
public function setErrorMsg(
|
||||
string $error_id,
|
||||
@@ -93,10 +106,14 @@ class ErrorMessage
|
||||
?string $message = null,
|
||||
array $context = [],
|
||||
?bool $log_error = null,
|
||||
?bool $log_warning = null,
|
||||
): void {
|
||||
if ($log_error === null) {
|
||||
$log_error = $this->log_error;
|
||||
}
|
||||
if ($log_warning === null) {
|
||||
$log_warning = $this->log_warning;
|
||||
}
|
||||
$original_level = $level;
|
||||
$level = MessageLevel::fromName($level)->name;
|
||||
// if not string set, write message string if set, else level/error id
|
||||
@@ -121,6 +138,14 @@ class ErrorMessage
|
||||
'level' => $original_level,
|
||||
], $context));
|
||||
break;
|
||||
case 'warn':
|
||||
if ($log_warning) {
|
||||
$this->log->warning($message ?? $str, array_merge([
|
||||
'id' => $error_id,
|
||||
'level' => $original_level,
|
||||
], $context));
|
||||
}
|
||||
break;
|
||||
case 'error':
|
||||
if ($log_error) {
|
||||
$this->log->error($message ?? $str, array_merge([
|
||||
@@ -169,6 +194,8 @@ class ErrorMessage
|
||||
* @param array<mixed> $context Additionl info for abort/crash messages
|
||||
* @param bool|null $log_error [=null] log level 'error' to error, if null use global,
|
||||
* else set for this call only
|
||||
* @param bool|null $log_warning [=null] log level 'warning' to warning, if null use global,
|
||||
* else set for this call only
|
||||
*/
|
||||
public function setMessage(
|
||||
string $level,
|
||||
@@ -181,6 +208,7 @@ class ErrorMessage
|
||||
?string $message = null,
|
||||
array $context = [],
|
||||
?bool $log_error = null,
|
||||
?bool $log_warning = null,
|
||||
): void {
|
||||
$this->setErrorMsg(
|
||||
$error_id ?? '',
|
||||
@@ -192,7 +220,8 @@ class ErrorMessage
|
||||
$jump_target,
|
||||
$message,
|
||||
$context,
|
||||
$log_error
|
||||
$log_error,
|
||||
$log_warning
|
||||
);
|
||||
}
|
||||
|
||||
@@ -314,6 +343,27 @@ class ErrorMessage
|
||||
{
|
||||
return $this->log_error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the log warning flag
|
||||
*
|
||||
* @param bool $flag True to log level warning too, False for do not (Default)
|
||||
* @return void
|
||||
*/
|
||||
public function setFlagLogWarning(bool $flag): void
|
||||
{
|
||||
$this->log_warning = $flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current log error flag
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getFlagLogWarning(): bool
|
||||
{
|
||||
return $this->log_warning;
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace CoreLibs\Logging\Logger;
|
||||
enum MessageLevel: int
|
||||
{
|
||||
case ok = 100;
|
||||
case success = 150; // special for file uploads
|
||||
case info = 200;
|
||||
case notice = 250;
|
||||
case warn = 300;
|
||||
@@ -30,6 +31,7 @@ enum MessageLevel: int
|
||||
{
|
||||
return match (strtolower($name)) {
|
||||
'ok' => self::ok,
|
||||
'success' => self::success,
|
||||
'info' => self::info,
|
||||
'notice' => self::notice,
|
||||
'warn', 'warning' => self::warn,
|
||||
|
||||
@@ -194,13 +194,13 @@ class Elements
|
||||
"/(mailto:)?(\>)?\b([\w\.-]+)@([\w\.\-]+)\.([a-zA-Z]{2,4})\b(\|([^\||^#]+)(#([^\|]+))?\|)?/",
|
||||
function ($matches) {
|
||||
return self::createEmail(
|
||||
$matches[1] ?? '',
|
||||
$matches[2] ?? '',
|
||||
$matches[3] ?? '',
|
||||
$matches[4] ?? '',
|
||||
$matches[5] ?? '',
|
||||
$matches[1],
|
||||
$matches[2],
|
||||
$matches[3],
|
||||
$matches[4],
|
||||
$matches[5],
|
||||
$matches[7] ?? '',
|
||||
$matches[9] ?? ''
|
||||
$matches[9] ?? '',
|
||||
);
|
||||
},
|
||||
$output
|
||||
|
||||
@@ -474,7 +474,7 @@ class Generate
|
||||
$page_name_camel_case
|
||||
);
|
||||
try {
|
||||
/** @var TableArrays\Interface\TableArraysInterface|false $class */
|
||||
/** @var TableArrays\Interface\TableArraysInterface $class */
|
||||
$class = new $class_string($this);
|
||||
} catch (\Throwable $t) {
|
||||
$this->log->critical('CLASS LOADING: Failed loading: ' . $class_string . ' => ' . $t->getMessage());
|
||||
@@ -826,27 +826,28 @@ class Generate
|
||||
$pk_selected = $res[$this->int_pk_name];
|
||||
}
|
||||
$t_string = '';
|
||||
foreach ($this->field_array as $i => $field_array) {
|
||||
foreach ($this->field_array as $field_array) {
|
||||
if ($t_string) {
|
||||
$t_string .= ', ';
|
||||
}
|
||||
if (isset($field_array['before_value'])) {
|
||||
$t_string .= $field_array['before_value'];
|
||||
if (!empty($field_array['before_value'])) {
|
||||
$t_string .= $this->l->__($field_array['before_value']);
|
||||
}
|
||||
// must have res element set
|
||||
if (
|
||||
isset($field_array['name']) &&
|
||||
!empty($field_array['name']) &&
|
||||
isset($res[$field_array['name']])
|
||||
) {
|
||||
if (isset($field_array['binary'])) {
|
||||
if (isset($field_array['binary'][0])) {
|
||||
$t_string .= $field_array['binary'][0];
|
||||
} elseif (isset($field_array['binary'][1])) {
|
||||
$t_string .= $field_array['binary'][1];
|
||||
}
|
||||
$_t_value = '';
|
||||
// if we have a binary set, where 0 = YES and 1 = NO
|
||||
if (!empty($field_array['binary'])) {
|
||||
$_t_value = !empty($res[$field_array['name']]) ?
|
||||
($field_array['binary'][0] ?? 'Yes') :
|
||||
($field_array['binary'][1] ?? 'No');
|
||||
} else {
|
||||
$t_string .= $res[$field_array['name']];
|
||||
$_t_value = $res[$field_array['name']];
|
||||
}
|
||||
$t_string .= $this->l->__($_t_value);
|
||||
}
|
||||
}
|
||||
$pk_names[] = $t_string;
|
||||
@@ -1506,7 +1507,7 @@ class Generate
|
||||
}
|
||||
if (
|
||||
!empty($this->reference_array[$key]['mandatory']) &&
|
||||
!$this->reference_array[$key]['selected'][0]
|
||||
empty($this->reference_array[$key]['selected'][0])
|
||||
) {
|
||||
$this->msg .= sprintf(
|
||||
$this->l->__('Please select at least one Element from field <b>%s</b>!<br>'),
|
||||
@@ -1756,15 +1757,10 @@ class Generate
|
||||
$this->dba->setTableArrayEntry($this->dba->getTableArray()[$key]['preset'], $key, 'value');
|
||||
}
|
||||
}
|
||||
if (is_array($this->reference_array)) {
|
||||
if (!is_array($this->reference_array)) {
|
||||
$this->reference_array = [];
|
||||
}
|
||||
reset($this->reference_array);
|
||||
foreach ($this->reference_array as $key => $value) {
|
||||
unset($this->reference_array[$key]['selected']);
|
||||
}
|
||||
}
|
||||
$this->warning = 1;
|
||||
$this->msg = $this->l->__('Cleared for new Dataset!');
|
||||
}
|
||||
@@ -1786,11 +1782,7 @@ class Generate
|
||||
$this->dba->unsetTableArrayEntry($key, 'input_value');
|
||||
}
|
||||
|
||||
if (is_array($this->reference_array)) {
|
||||
// load each reference_table
|
||||
if (!is_array($this->reference_array)) {
|
||||
$this->reference_array = [];
|
||||
}
|
||||
reset($this->reference_array);
|
||||
foreach ($this->reference_array as $key => $value) {
|
||||
unset($this->reference_array[$key]['selected']);
|
||||
@@ -1801,7 +1793,6 @@ class Generate
|
||||
$this->reference_array[$key]['selected'][] = $res[$this->reference_array[$key]['other_table_pk']];
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->warning = 1;
|
||||
$this->msg = $this->l->__('Dataset has been loaded!<br>');
|
||||
}
|
||||
@@ -1978,10 +1969,6 @@ class Generate
|
||||
// write the object
|
||||
$this->dba->dbWrite($addslashes, [], true);
|
||||
// write reference array (s) if necessary
|
||||
if (is_array($this->reference_array)) {
|
||||
if (!is_array($this->reference_array)) {
|
||||
$this->reference_array = [];
|
||||
}
|
||||
reset($this->reference_array);
|
||||
foreach ($this->reference_array as $reference_array) {
|
||||
$q = 'DELETE FROM ' . $reference_array['table_name']
|
||||
@@ -1995,7 +1982,6 @@ class Generate
|
||||
$this->dba->dbExec($q . $t_q);
|
||||
}
|
||||
} // foreach reference arrays
|
||||
} // if reference arrays
|
||||
// write element list
|
||||
if (!empty($this->element_list)) {
|
||||
$type = [];
|
||||
@@ -2229,17 +2215,12 @@ class Generate
|
||||
public function formDeleteTableArray()
|
||||
{
|
||||
// remove any reference arrays
|
||||
if (is_array($this->reference_array)) {
|
||||
if (!is_array($this->reference_array)) {
|
||||
$this->reference_array = [];
|
||||
}
|
||||
reset($this->reference_array);
|
||||
foreach ($this->reference_array as $reference_array) {
|
||||
$q = 'DELETE FROM ' . $reference_array['table_name']
|
||||
. ' WHERE ' . $this->int_pk_name . ' = ' . $this->dba->getTableArray()[$this->int_pk_name]['value'];
|
||||
$this->dba->dbExec($q);
|
||||
}
|
||||
}
|
||||
// remove any element list references
|
||||
if (!empty($this->element_list)) {
|
||||
reset($this->element_list);
|
||||
|
||||
@@ -53,6 +53,7 @@ class EditPages implements Interface\TableArraysInterface
|
||||
'value' => $_POST['name'] ?? '',
|
||||
'output_name' => 'Page name',
|
||||
'mandatory' => 1,
|
||||
'error_check' => 'unique',
|
||||
'type' => 'text'
|
||||
],
|
||||
'order_number' => [
|
||||
|
||||
@@ -135,30 +135,6 @@ class EditUsers implements Interface\TableArraysInterface
|
||||
'min_edit_acl' => '100',
|
||||
'min_show_acl' => '100',
|
||||
],
|
||||
'debug' => [
|
||||
'value' => $_POST['debug'] ?? '',
|
||||
'output_name' => 'Debug',
|
||||
'type' => 'binary',
|
||||
'int' => 1,
|
||||
'element_list' => [
|
||||
'1' => 'Yes',
|
||||
'0' => 'No'
|
||||
],
|
||||
'min_edit_acl' => '100',
|
||||
'min_show_acl' => '100',
|
||||
],
|
||||
'db_debug' => [
|
||||
'value' => $_POST['db_debug'] ?? '',
|
||||
'output_name' => 'DB Debug',
|
||||
'type' => 'binary',
|
||||
'int' => 1,
|
||||
'element_list' => [
|
||||
'1' => 'Yes',
|
||||
'0' => 'No'
|
||||
],
|
||||
'min_edit_acl' => '100',
|
||||
'min_show_acl' => '100',
|
||||
],
|
||||
'email' => [
|
||||
'value' => $_POST['email'] ?? '',
|
||||
'output_name' => 'E-Mail',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/*
|
||||
* sets a form token in the _SESSION variable
|
||||
* session must be started for this to work
|
||||
* session must be started and running for this to work
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
@@ -204,11 +204,11 @@ class Image
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
// NOTE: we need to depracte this
|
||||
$cache_folder = BASE . LAYOUT . CONTENT_PATH . CACHE . IMAGES;
|
||||
$cache_folder = BASE . CONTENT_PATH . LAYOUT . CACHE . IMAGES;
|
||||
$web_folder = LAYOUT . CACHE . IMAGES;
|
||||
if (!is_dir($cache_folder)) {
|
||||
if (false === mkdir($cache_folder)) {
|
||||
$cache_folder = BASE . LAYOUT . CONTENT_PATH . CACHE;
|
||||
$cache_folder = BASE . CONTENT_PATH . LAYOUT . CACHE;
|
||||
$web_folder = LAYOUT . CACHE;
|
||||
}
|
||||
}
|
||||
@@ -256,8 +256,8 @@ class Image
|
||||
}
|
||||
// check resize parameters
|
||||
if ($inc_width > $thumb_width || $inc_height > $thumb_height) {
|
||||
$thumb_width_r = 0;
|
||||
$thumb_height_r = 0;
|
||||
$thumb_width_r = 1;
|
||||
$thumb_height_r = 1;
|
||||
// we need to keep the aspect ration on longest side
|
||||
if (
|
||||
($inc_height > $inc_width &&
|
||||
@@ -288,6 +288,12 @@ class Image
|
||||
!file_exists($thumbnail_write_path . $thumbnail)
|
||||
) {
|
||||
// image, copy source image, offset in image, source x/y, new size, source image size
|
||||
if ($thumb_width_r < 1) {
|
||||
$thumb_width_r = 1;
|
||||
}
|
||||
if ($thumb_height_r < 1) {
|
||||
$thumb_height_r = 1;
|
||||
}
|
||||
$thumb = imagecreatetruecolor($thumb_width_r, $thumb_height_r);
|
||||
if ($thumb === false) {
|
||||
throw new \RuntimeException(
|
||||
@@ -380,9 +386,7 @@ class Image
|
||||
}
|
||||
}
|
||||
// add output path
|
||||
if ($thumbnail !== false) {
|
||||
$thumbnail = $thumbnail_web_path . $thumbnail;
|
||||
}
|
||||
} elseif ($create_dummy === true) {
|
||||
// create dummy image in the thumbnail size
|
||||
// if one side is missing, use the other side to create a square
|
||||
@@ -399,10 +403,10 @@ class Image
|
||||
!file_exists($thumbnail_write_path . $thumbnail)
|
||||
) {
|
||||
// if both are unset, set to 250
|
||||
if ($thumb_height == 0) {
|
||||
if ($thumb_height < 1) {
|
||||
$thumb_height = 250;
|
||||
}
|
||||
if ($thumb_width == 0) {
|
||||
if ($thumb_width < 1) {
|
||||
$thumb_width = 250;
|
||||
}
|
||||
$thumb = imagecreatetruecolor($thumb_width, $thumb_height);
|
||||
|
||||
408
src/Security/AsymmetricAnonymousEncryption.php
Normal file
408
src/Security/AsymmetricAnonymousEncryption.php
Normal file
@@ -0,0 +1,408 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* very simple asymmetric encryption
|
||||
* Better use:
|
||||
* https://paragonie.com/project/halite
|
||||
* https://github.com/paragonie/halite
|
||||
*
|
||||
* current code is just to encrypt and decrypt
|
||||
*
|
||||
* must use a valid encryption key created with
|
||||
* Secruty\CreateKey class
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Security;
|
||||
|
||||
use CoreLibs\Security\CreateKey;
|
||||
use SodiumException;
|
||||
|
||||
class AsymmetricAnonymousEncryption
|
||||
{
|
||||
/** @var AsymmetricAnonymousEncryption self instance */
|
||||
private static AsymmetricAnonymousEncryption $instance;
|
||||
|
||||
/** @var ?string key pair which holds secret and public key, needed for encryption */
|
||||
private ?string $key_pair = null;
|
||||
/** @var ?string public key, needed for decryption
|
||||
* if not set but key_pair set, this will be extracted from key pair */
|
||||
private ?string $public_key = null;
|
||||
|
||||
/**
|
||||
* init class
|
||||
* if key not passed, key must be set with createKey
|
||||
*
|
||||
* @param string|null $key_pair
|
||||
* @param string|null $public_key
|
||||
*/
|
||||
public function __construct(
|
||||
#[\SensitiveParameter]
|
||||
string|null $key_pair = null,
|
||||
string|null $public_key = null
|
||||
) {
|
||||
if ($public_key !== null) {
|
||||
$this->setPublicKey($public_key);
|
||||
}
|
||||
if ($key_pair !== null) {
|
||||
$this->setKeyPair($key_pair);
|
||||
if (empty($public_key)) {
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
$this->setPublicKey($public_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singleton self object.
|
||||
* For function wrapper use
|
||||
*
|
||||
* @param string|null $key_pair
|
||||
* @param string|null $public_key
|
||||
* @return AsymmetricAnonymousEncryption object
|
||||
*/
|
||||
public static function getInstance(
|
||||
#[\SensitiveParameter]
|
||||
string|null $key_pair = null,
|
||||
string|null $public_key = null
|
||||
): self {
|
||||
// new if no instsance or key is different
|
||||
if (
|
||||
empty(self::$instance) ||
|
||||
self::$instance->key_pair != $key_pair ||
|
||||
self::$instance->public_key != $public_key
|
||||
) {
|
||||
self::$instance = new self($key_pair, $public_key);
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* clean up
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (empty($this->key_pair)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// would set it to null, but we we do not want to make key null
|
||||
sodium_memzero($this->key_pair);
|
||||
return;
|
||||
} catch (SodiumException) {
|
||||
// empty catch
|
||||
}
|
||||
if (is_null($this->key_pair)) {
|
||||
return;
|
||||
}
|
||||
$zero = str_repeat("\0", mb_strlen($this->key_pair, '8bit'));
|
||||
$this->key_pair = $this->key_pair ^ (
|
||||
$zero ^ $this->key_pair
|
||||
);
|
||||
unset($zero);
|
||||
unset($this->key_pair); /** @phan-suppress-current-line PhanTypeObjectUnsetDeclaredProperty */
|
||||
}
|
||||
|
||||
/* ************************************************************************
|
||||
* MARK: PRIVATE
|
||||
* *************************************************************************/
|
||||
|
||||
/**
|
||||
* Create the internal key pair in binary
|
||||
*
|
||||
* @param ?string $key_pair
|
||||
* @return string
|
||||
* @throws \UnexpectedValueException key pair empty
|
||||
* @throws \UnexpectedValueException invalid hex key pair
|
||||
* @throws \RangeException key pair not correct size
|
||||
*/
|
||||
private function createKeyPair(
|
||||
#[\SensitiveParameter]
|
||||
?string $key_pair
|
||||
): string {
|
||||
if (empty($key_pair)) {
|
||||
throw new \UnexpectedValueException('Key pair cannot be empty');
|
||||
}
|
||||
try {
|
||||
$key_pair = CreateKey::hex2bin($key_pair);
|
||||
} catch (SodiumException $e) {
|
||||
sodium_memzero($key_pair);
|
||||
throw new \UnexpectedValueException('Invalid hex key pair: ' . $e->getMessage());
|
||||
}
|
||||
if (mb_strlen($key_pair, '8bit') !== SODIUM_CRYPTO_BOX_KEYPAIRBYTES) {
|
||||
sodium_memzero($key_pair);
|
||||
throw new \RangeException(
|
||||
'Key pair is not the correct size (must be '
|
||||
. SODIUM_CRYPTO_BOX_KEYPAIRBYTES . ' bytes long).'
|
||||
);
|
||||
}
|
||||
return $key_pair;
|
||||
}
|
||||
|
||||
/**
|
||||
* create the internal public key in binary
|
||||
*
|
||||
* @param ?string $public_key
|
||||
* @return string
|
||||
* @throws \UnexpectedValueException public key empty
|
||||
* @throws \UnexpectedValueException invalid hex key
|
||||
* @throws \RangeException invalid key length
|
||||
*/
|
||||
private function createPublicKey(?string $public_key): string
|
||||
{
|
||||
if (empty($public_key)) {
|
||||
throw new \UnexpectedValueException('Public key cannot be empty');
|
||||
}
|
||||
try {
|
||||
$public_key = CreateKey::hex2bin($public_key);
|
||||
} catch (SodiumException $e) {
|
||||
sodium_memzero($public_key);
|
||||
throw new \UnexpectedValueException('Invalid hex public key: ' . $e->getMessage());
|
||||
}
|
||||
if (mb_strlen($public_key, '8bit') !== SODIUM_CRYPTO_BOX_PUBLICKEYBYTES) {
|
||||
sodium_memzero($public_key);
|
||||
throw new \RangeException(
|
||||
'Public key is not the correct size (must be '
|
||||
. SODIUM_CRYPTO_BOX_PUBLICKEYBYTES . ' bytes long).'
|
||||
);
|
||||
}
|
||||
return $public_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* encrypt a message asymmetric with a bpulic key
|
||||
*
|
||||
* @param string $message
|
||||
* @param ?string $public_key
|
||||
* @return string
|
||||
* @throws \UnexpectedValueException create encryption failed
|
||||
* @throws \UnexpectedValueException convert to base64 failed
|
||||
*/
|
||||
private function asymmetricEncryption(
|
||||
#[\SensitiveParameter]
|
||||
string $message,
|
||||
?string $public_key
|
||||
): string {
|
||||
$public_key = $this->createPublicKey($public_key);
|
||||
try {
|
||||
$encrypted = sodium_crypto_box_seal($message, $public_key);
|
||||
} catch (SodiumException $e) {
|
||||
sodium_memzero($message);
|
||||
throw new \UnexpectedValueException("Create encrypted message failed: " . $e->getMessage());
|
||||
}
|
||||
sodium_memzero($message);
|
||||
try {
|
||||
$result = sodium_bin2base64($encrypted, SODIUM_BASE64_VARIANT_ORIGINAL);
|
||||
} catch (SodiumException $e) {
|
||||
sodium_memzero($encrypted);
|
||||
throw new \UnexpectedValueException("bin2base64 failed: " . $e->getMessage());
|
||||
}
|
||||
sodium_memzero($encrypted);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* decrypt a message that is asymmetric encrypted with a key pair
|
||||
*
|
||||
* @param string $message
|
||||
* @param ?string $key_pair
|
||||
* @return string
|
||||
* @throws \UnexpectedValueException message string empty
|
||||
* @throws \UnexpectedValueException base64 decoding failed
|
||||
* @throws \UnexpectedValueException decryption failed
|
||||
* @throws \UnexpectedValueException could not decrypt message
|
||||
*/
|
||||
private function asymmetricDecryption(
|
||||
#[\SensitiveParameter]
|
||||
string $message,
|
||||
#[\SensitiveParameter]
|
||||
?string $key_pair
|
||||
): string {
|
||||
if (empty($message)) {
|
||||
throw new \UnexpectedValueException('Encrypted string cannot be empty');
|
||||
}
|
||||
$key_pair = $this->createKeyPair($key_pair);
|
||||
try {
|
||||
$result = sodium_base642bin($message, SODIUM_BASE64_VARIANT_ORIGINAL);
|
||||
} catch (SodiumException $e) {
|
||||
sodium_memzero($message);
|
||||
sodium_memzero($key_pair);
|
||||
throw new \UnexpectedValueException("base642bin failed: " . $e->getMessage());
|
||||
}
|
||||
sodium_memzero($message);
|
||||
$plaintext = false;
|
||||
try {
|
||||
$plaintext = sodium_crypto_box_seal_open($result, $key_pair);
|
||||
} catch (SodiumException $e) {
|
||||
sodium_memzero($message);
|
||||
sodium_memzero($key_pair);
|
||||
sodium_memzero($result);
|
||||
throw new \UnexpectedValueException("Decrypting message failed: " . $e->getMessage());
|
||||
}
|
||||
sodium_memzero($key_pair);
|
||||
sodium_memzero($result);
|
||||
if (!is_string($plaintext)) {
|
||||
throw new \UnexpectedValueException('Invalid key pair');
|
||||
}
|
||||
return $plaintext;
|
||||
}
|
||||
|
||||
/* ************************************************************************
|
||||
* MARK: PUBLIC
|
||||
* *************************************************************************/
|
||||
|
||||
/**
|
||||
* sets the private key for encryption
|
||||
*
|
||||
* @param string $key_pair Key pair in hex
|
||||
* @return AsymmetricAnonymousEncryption
|
||||
* @throws \UnexpectedValueException key pair empty
|
||||
*/
|
||||
public function setKeyPair(
|
||||
#[\SensitiveParameter]
|
||||
string $key_pair
|
||||
): AsymmetricAnonymousEncryption {
|
||||
if (empty($key_pair)) {
|
||||
throw new \UnexpectedValueException('Key pair cannot be empty');
|
||||
}
|
||||
// check if valid;
|
||||
$this->createKeyPair($key_pair);
|
||||
// set new key pair
|
||||
$this->key_pair = $key_pair;
|
||||
sodium_memzero($key_pair);
|
||||
// set public key if not set
|
||||
if (empty($this->public_key)) {
|
||||
$this->public_key = CreateKey::getPublicKey($this->key_pair);
|
||||
// check if valid
|
||||
$this->createPublicKey($this->public_key);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if set key pair matches given one
|
||||
*
|
||||
* @param string $key_pair
|
||||
* @return bool
|
||||
*/
|
||||
public function compareKeyPair(
|
||||
#[\SensitiveParameter]
|
||||
string $key_pair
|
||||
): bool {
|
||||
return $this->key_pair === $key_pair;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the current set key pair, null if not set
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getKeyPair(): ?string
|
||||
{
|
||||
return $this->key_pair;
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the public key for decryption
|
||||
* if only key pair exists Security\Create::getPublicKey() can be used to
|
||||
* extract the public key from the key pair
|
||||
*
|
||||
* @param string $public_key Public Key in hex
|
||||
* @return AsymmetricAnonymousEncryption
|
||||
* @throws \UnexpectedValueException public key empty
|
||||
*/
|
||||
public function setPublicKey(string $public_key): AsymmetricAnonymousEncryption
|
||||
{
|
||||
if (empty($public_key)) {
|
||||
throw new \UnexpectedValueException('Public key cannot be empty');
|
||||
}
|
||||
// check if valid
|
||||
$this->createPublicKey($public_key);
|
||||
$this->public_key = $public_key;
|
||||
sodium_memzero($public_key);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the set public key matches the given one
|
||||
*
|
||||
* @param string $public_key
|
||||
* @return bool
|
||||
*/
|
||||
public function comparePublicKey(string $public_key): bool
|
||||
{
|
||||
return $this->public_key === $public_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the current set public key, null if not set
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getPublicKey(): ?string
|
||||
{
|
||||
return $this->public_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a message with a public key
|
||||
* static version
|
||||
*
|
||||
* @param string $message Message to encrypt
|
||||
* @param string $public_key Public key in hex to encrypt message with
|
||||
* @return string Encrypted message as hex string
|
||||
*/
|
||||
public static function encryptKey(
|
||||
#[\SensitiveParameter]
|
||||
string $message,
|
||||
string $public_key
|
||||
): string {
|
||||
return self::getInstance()->asymmetricEncryption($message, $public_key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a message
|
||||
*
|
||||
* @param string $message Message to ecnrypt
|
||||
* @return string Encrypted message as hex string
|
||||
*/
|
||||
public function encrypt(
|
||||
#[\SensitiveParameter]
|
||||
string $message
|
||||
): string {
|
||||
return $this->asymmetricEncryption($message, $this->public_key);
|
||||
}
|
||||
|
||||
/**
|
||||
* decrypt a message with a key pair
|
||||
* static version
|
||||
*
|
||||
* @param string $message Message to decrypt in hex
|
||||
* @param string $key_pair Key pair in hex to decrypt the message with
|
||||
* @return string Decrypted message
|
||||
*/
|
||||
public static function decryptKey(
|
||||
#[\SensitiveParameter]
|
||||
string $message,
|
||||
#[\SensitiveParameter]
|
||||
string $key_pair
|
||||
): string {
|
||||
return self::getInstance()->asymmetricDecryption($message, $key_pair);
|
||||
}
|
||||
|
||||
/**
|
||||
* decrypt a message
|
||||
*
|
||||
* @param string $message Message to decrypt in hex
|
||||
* @return string Decrypted message
|
||||
*/
|
||||
public function decrypt(
|
||||
#[\SensitiveParameter]
|
||||
string $message
|
||||
): string {
|
||||
return $this->asymmetricDecryption($message, $this->key_pair);
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
@@ -35,14 +35,39 @@ class CreateKey
|
||||
return random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a sodium cyptobox keypair as hex string
|
||||
*
|
||||
* @return string hex string for the keypair
|
||||
*/
|
||||
public static function createKeyPair(): string
|
||||
{
|
||||
return self::bin2hex(sodium_crypto_box_keypair());
|
||||
}
|
||||
|
||||
/**
|
||||
* extracts the public key and returns it as hex string from the hex keypari
|
||||
*
|
||||
* @param string $hex_keypair hex encoded keypair
|
||||
* @return string hex encoded public key
|
||||
*/
|
||||
public static function getPublicKey(
|
||||
#[\SensitiveParameter]
|
||||
string $hex_keypair
|
||||
): string {
|
||||
return self::bin2hex(sodium_crypto_box_publickey(self::hex2bin($hex_keypair)));
|
||||
}
|
||||
|
||||
/**
|
||||
* convert binary key to hex string
|
||||
*
|
||||
* @param string $hex_key Convert binary key string to hex
|
||||
* @return string
|
||||
*/
|
||||
public static function bin2hex(string $hex_key): string
|
||||
{
|
||||
public static function bin2hex(
|
||||
#[\SensitiveParameter]
|
||||
string $hex_key
|
||||
): string {
|
||||
return sodium_bin2hex($hex_key);
|
||||
}
|
||||
|
||||
@@ -52,8 +77,10 @@ class CreateKey
|
||||
* @param string $string_key Convery hex key string to binary
|
||||
* @return string
|
||||
*/
|
||||
public static function hex2bin(string $string_key): string
|
||||
{
|
||||
public static function hex2bin(
|
||||
#[\SensitiveParameter]
|
||||
string $string_key
|
||||
): string {
|
||||
return sodium_hex2bin($string_key);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ class Password
|
||||
* @param string $password password
|
||||
* @return string hashed password
|
||||
*/
|
||||
public static function passwordSet(string $password): string
|
||||
{
|
||||
public static function passwordSet(
|
||||
#[\SensitiveParameter]
|
||||
string $password
|
||||
): string {
|
||||
// always use the PHP default for the password
|
||||
// password options ca be set in the password init,
|
||||
// but should be kept as default
|
||||
@@ -31,8 +33,11 @@ class Password
|
||||
* @param string $hash password hash
|
||||
* @return bool true or false
|
||||
*/
|
||||
public static function passwordVerify(string $password, string $hash): bool
|
||||
{
|
||||
public static function passwordVerify(
|
||||
#[\SensitiveParameter]
|
||||
string $password,
|
||||
string $hash
|
||||
): bool {
|
||||
if (password_verify($password, $hash)) {
|
||||
return true;
|
||||
} else {
|
||||
|
||||
@@ -21,78 +21,293 @@ use SodiumException;
|
||||
|
||||
class SymmetricEncryption
|
||||
{
|
||||
/** @var SymmetricEncryption self instance */
|
||||
private static SymmetricEncryption $instance;
|
||||
|
||||
/** @var ?string bin hex key */
|
||||
private ?string $key = null;
|
||||
|
||||
/**
|
||||
* Encrypt a message
|
||||
* init class
|
||||
* if key not passed, key must be set with createKey
|
||||
*
|
||||
* @param string $message Message to encrypt
|
||||
* @param string $key Encryption key (as hex string)
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
* @throws \RangeException
|
||||
* @param string|null $key encryption key
|
||||
*/
|
||||
public static function encrypt(string $message, string $key): string
|
||||
public function __construct(
|
||||
?string $key = null
|
||||
) {
|
||||
if ($key !== null) {
|
||||
$this->setKey($key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singleton self object.
|
||||
* For function wrapper use
|
||||
*
|
||||
* @param string|null $key encryption key
|
||||
* @return SymmetricEncryption object
|
||||
*/
|
||||
public static function getInstance(?string $key = null): self
|
||||
{
|
||||
// new if no instsance or key is different
|
||||
if (
|
||||
empty(self::$instance) ||
|
||||
self::$instance->key != $key
|
||||
) {
|
||||
self::$instance = new self($key);
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* clean up
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __deconstruct()
|
||||
{
|
||||
if (empty($this->key)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// would set it to null, but we we do not want to make key null
|
||||
sodium_memzero($this->key);
|
||||
return;
|
||||
} catch (SodiumException) {
|
||||
// empty catch
|
||||
}
|
||||
if (is_null($this->key)) {
|
||||
return;
|
||||
}
|
||||
$zero = str_repeat("\0", mb_strlen($this->key, '8bit'));
|
||||
$this->key = $this->key ^ (
|
||||
$zero ^ $this->key
|
||||
);
|
||||
unset($zero);
|
||||
unset($this->key); /** @phan-suppress-current-line PhanTypeObjectUnsetDeclaredProperty */
|
||||
}
|
||||
|
||||
/* ************************************************************************
|
||||
* MARK: PRIVATE
|
||||
* *************************************************************************/
|
||||
|
||||
/**
|
||||
* create key and check validity
|
||||
*
|
||||
* @param ?string $key The key from which the binary key will be created
|
||||
* @return string Binary key string
|
||||
* @throws \UnexpectedValueException empty key
|
||||
* @throws \UnexpectedValueException invalid hex key
|
||||
* @throws \RangeException invalid length
|
||||
*/
|
||||
private function createKey(
|
||||
#[\SensitiveParameter]
|
||||
?string $key
|
||||
): string {
|
||||
if (empty($key)) {
|
||||
throw new \UnexpectedValueException('Key cannot be empty');
|
||||
}
|
||||
try {
|
||||
$key = CreateKey::hex2bin($key);
|
||||
} catch (SodiumException $e) {
|
||||
throw new \UnexpectedValueException('Invalid hex key');
|
||||
throw new \UnexpectedValueException('Invalid hex key: ' . $e->getMessage());
|
||||
}
|
||||
if (mb_strlen($key, '8bit') !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
|
||||
throw new \RangeException(
|
||||
'Key is not the correct size (must be '
|
||||
. 'SODIUM_CRYPTO_SECRETBOX_KEYBYTES bytes long).'
|
||||
. SODIUM_CRYPTO_SECRETBOX_KEYBYTES . ' bytes long).'
|
||||
);
|
||||
}
|
||||
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
|
||||
return $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decryption call
|
||||
*
|
||||
* @param string $encrypted Text to decrypt
|
||||
* @param ?string $key Mandatory encryption key, will throw exception if empty
|
||||
* @return string Plain text
|
||||
* @throws \UnexpectedValueException key cannot be empty
|
||||
* @throws \UnexpectedValueException decipher message failed
|
||||
* @throws \UnexpectedValueException invalid key
|
||||
*/
|
||||
private function decryptData(
|
||||
#[\SensitiveParameter]
|
||||
string $encrypted,
|
||||
#[\SensitiveParameter]
|
||||
?string $key
|
||||
): string {
|
||||
if (empty($encrypted)) {
|
||||
throw new \UnexpectedValueException('Encrypted string cannot be empty');
|
||||
}
|
||||
$key = $this->createKey($key);
|
||||
$decoded = base64_decode($encrypted);
|
||||
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
|
||||
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
|
||||
|
||||
$plaintext = false;
|
||||
try {
|
||||
$plaintext = sodium_crypto_secretbox_open(
|
||||
$ciphertext,
|
||||
$nonce,
|
||||
$key
|
||||
);
|
||||
} catch (SodiumException $e) {
|
||||
sodium_memzero($ciphertext);
|
||||
sodium_memzero($key);
|
||||
throw new \UnexpectedValueException('Decipher message failed: ' . $e->getMessage());
|
||||
}
|
||||
sodium_memzero($ciphertext);
|
||||
sodium_memzero($key);
|
||||
if (!is_string($plaintext)) {
|
||||
throw new \UnexpectedValueException('Invalid Key');
|
||||
}
|
||||
return $plaintext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a message
|
||||
*
|
||||
* @param string $message Message to encrypt
|
||||
* @param ?string $key Mandatory encryption key, will throw exception if empty
|
||||
* @return string Ciphered text
|
||||
* @throws \UnexpectedValueException create message failed
|
||||
*/
|
||||
private function encryptData(
|
||||
#[\SensitiveParameter]
|
||||
string $message,
|
||||
#[\SensitiveParameter]
|
||||
?string $key
|
||||
): string {
|
||||
$key = $this->createKey($key);
|
||||
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
|
||||
try {
|
||||
$cipher = base64_encode(
|
||||
$nonce
|
||||
. sodium_crypto_secretbox(
|
||||
$message,
|
||||
$nonce,
|
||||
$key
|
||||
$key,
|
||||
)
|
||||
);
|
||||
} catch (SodiumException $e) {
|
||||
sodium_memzero($message);
|
||||
sodium_memzero($key);
|
||||
throw new \UnexpectedValueException("Create encrypted message failed: " . $e->getMessage());
|
||||
}
|
||||
sodium_memzero($message);
|
||||
sodium_memzero($key);
|
||||
return $cipher;
|
||||
}
|
||||
|
||||
/* ************************************************************************
|
||||
* MARK: PUBLIC
|
||||
* *************************************************************************/
|
||||
|
||||
/**
|
||||
* set a new key for encryption
|
||||
*
|
||||
* @param string $key
|
||||
* @return SymmetricEncryption
|
||||
* @throws \UnexpectedValueException key cannot be empty
|
||||
*/
|
||||
public function setKey(
|
||||
#[\SensitiveParameter]
|
||||
string $key
|
||||
): SymmetricEncryption {
|
||||
if (empty($key)) {
|
||||
throw new \UnexpectedValueException('Key cannot be empty');
|
||||
}
|
||||
// check that this is a valid key
|
||||
$this->createKey($key);
|
||||
// set key
|
||||
$this->key = $key;
|
||||
sodium_memzero($key);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if set key is equal to parameter key
|
||||
*
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function compareKey(
|
||||
#[\SensitiveParameter]
|
||||
string $key
|
||||
): bool {
|
||||
return $key === $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the current set key, null if not set
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getKey(): ?string
|
||||
{
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a message
|
||||
* static version
|
||||
*
|
||||
* @param string $encrypted Message encrypted with safeEncrypt()
|
||||
* @param string $key Encryption key (as hex string)
|
||||
* @return string
|
||||
*/
|
||||
public static function decryptKey(
|
||||
#[\SensitiveParameter]
|
||||
string $encrypted,
|
||||
#[\SensitiveParameter]
|
||||
string $key
|
||||
): string {
|
||||
return self::getInstance()->decryptData($encrypted, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a message
|
||||
*
|
||||
* @param string $encrypted Message encrypted with safeEncrypt()
|
||||
* @return string
|
||||
*/
|
||||
public function decrypt(
|
||||
#[\SensitiveParameter]
|
||||
string $encrypted
|
||||
): string {
|
||||
return $this->decryptData($encrypted, $this->key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt a message
|
||||
* static version
|
||||
*
|
||||
* @param string $message Message to encrypt
|
||||
* @param string $key Encryption key (as hex string)
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function decrypt(string $encrypted, string $key): string
|
||||
{
|
||||
try {
|
||||
$key = CreateKey::hex2bin($key);
|
||||
} catch (SodiumException $e) {
|
||||
throw new \Exception('Invalid hex key');
|
||||
public static function encryptKey(
|
||||
#[\SensitiveParameter]
|
||||
string $message,
|
||||
#[\SensitiveParameter]
|
||||
string $key
|
||||
): string {
|
||||
return self::getInstance()->encryptData($message, $key);
|
||||
}
|
||||
$decoded = base64_decode($encrypted);
|
||||
$nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
|
||||
$ciphertext = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
|
||||
|
||||
$plain = false;
|
||||
try {
|
||||
$plain = sodium_crypto_secretbox_open(
|
||||
$ciphertext,
|
||||
$nonce,
|
||||
$key
|
||||
);
|
||||
} catch (SodiumException $e) {
|
||||
throw new \UnexpectedValueException('Invalid ciphertext (too short)');
|
||||
}
|
||||
if (!is_string($plain)) {
|
||||
throw new \UnexpectedValueException('Invalid Key');
|
||||
}
|
||||
sodium_memzero($ciphertext);
|
||||
sodium_memzero($key);
|
||||
return $plain;
|
||||
/**
|
||||
* Encrypt a message
|
||||
*
|
||||
* @param string $message Message to encrypt
|
||||
* @return string
|
||||
*/
|
||||
public function encrypt(
|
||||
#[\SensitiveParameter]
|
||||
string $message
|
||||
): string {
|
||||
return $this->encryptData($message, $this->key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ use CoreLibs\Template\HtmlBuilder\General\HtmlBuilderExcpetion;
|
||||
class Block
|
||||
{
|
||||
/**
|
||||
* Undocumented function
|
||||
* Create Element
|
||||
*
|
||||
* @param string $tag
|
||||
* @param string $id
|
||||
@@ -86,7 +86,7 @@ class Block
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
* Add multiple elements to the base element
|
||||
*
|
||||
* @param array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>} $base
|
||||
* @param array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>} ...$attach
|
||||
@@ -101,7 +101,7 @@ class Block
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
* Add multiple sub elements to the base element
|
||||
*
|
||||
* @param array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>} $element
|
||||
* @param array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>} $sub
|
||||
@@ -117,7 +117,7 @@ class Block
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
* Remove all sub element entries
|
||||
*
|
||||
* @param array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>} $element
|
||||
* @return array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>}
|
||||
@@ -131,7 +131,7 @@ class Block
|
||||
// CSS Elements
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
* Add css entry to the css entries
|
||||
*
|
||||
* @param array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>} $element
|
||||
* @param string ...$css
|
||||
@@ -144,7 +144,7 @@ class Block
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
* Remove a css entry entry from the css array
|
||||
*
|
||||
* @param array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>} $element
|
||||
* @param string ...$css
|
||||
@@ -157,7 +157,7 @@ class Block
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
* Switch CSS entries
|
||||
* scssel (switch) is not supported
|
||||
* use rcssel -> acssel
|
||||
*
|
||||
@@ -175,7 +175,7 @@ class Block
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
* Build HTML from the content tree
|
||||
* alias phfo
|
||||
*
|
||||
* @param array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>} $tree
|
||||
@@ -231,7 +231,19 @@ class Block
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
* Alias for phfo
|
||||
*
|
||||
* @param array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>} $tree
|
||||
* @param bool $add_nl [default=false]
|
||||
* @return string
|
||||
*/
|
||||
public static function phfo(array $tree, bool $add_nl = false): string
|
||||
{
|
||||
return self::buildHtml($tree, $add_nl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build HTML elements from an array of elements
|
||||
* alias phfa
|
||||
*
|
||||
* @param array<array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>}> $list
|
||||
@@ -248,8 +260,7 @@ class Block
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
* wrapper for buildHtmlFromList
|
||||
* alias for buildHtmlFromList
|
||||
*
|
||||
* @param array<array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>}> $list array of Elements to build string from
|
||||
* @param bool $add_nl [default=false] Optional output string line break
|
||||
|
||||
@@ -401,7 +401,7 @@ class Element
|
||||
* @param bool $add_nl [default=false] Optional output string line breaks
|
||||
* @return string HTML as string
|
||||
*/
|
||||
public function buildHtml(Element $tree = null, bool $add_nl = false): string
|
||||
public function buildHtml(?Element $tree = null, bool $add_nl = false): string
|
||||
{
|
||||
// print "D01: " . microtime(true) . "<br>";
|
||||
if ($tree === null) {
|
||||
@@ -533,7 +533,7 @@ class Element
|
||||
* @return string build html as string
|
||||
* @deprecated Do not use, use Element->buildHtml() instead
|
||||
*/
|
||||
public static function printHtmlFromObject(Element $tree = null, bool $add_nl = false): string
|
||||
public static function printHtmlFromObject(?Element $tree = null, bool $add_nl = false): string
|
||||
{
|
||||
// nothing ->bad
|
||||
if ($tree === null) {
|
||||
|
||||
@@ -185,7 +185,7 @@ class SmartyExtend extends \Smarty
|
||||
// call basic smarty
|
||||
// or Smarty::__construct();
|
||||
parent::__construct();
|
||||
// iinit lang
|
||||
// init lang
|
||||
$this->l10n = $l10n;
|
||||
// parse and read, legacy stuff
|
||||
$locale = $this->l10n->getLocaleAsArray();
|
||||
@@ -203,7 +203,8 @@ class SmartyExtend extends \Smarty
|
||||
_bind_textdomain_codeset($this->domain, $this->encoding);
|
||||
|
||||
// register smarty variable
|
||||
$this->registerPlugin('modifier', 'getvar', [&$this, 'getTemplateVars']);
|
||||
// $this->registerPlugin(\Smarty\Smarty::PLUGIN_MODIFIER, 'getvar', [&$this, 'getTemplateVars']);
|
||||
$this->registerPlugin(self::PLUGIN_MODIFIER, 'getvar', [&$this, 'getTemplateVars']);
|
||||
|
||||
$this->page_name = \CoreLibs\Get\System::getPageName();
|
||||
|
||||
|
||||
1041
src/UrlRequests/Curl.php
Normal file
1041
src/UrlRequests/Curl.php
Normal file
File diff suppressed because it is too large
Load Diff
128
src/UrlRequests/CurlTrait.php
Normal file
128
src/UrlRequests/CurlTrait.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2024/10/29
|
||||
* DESCRIPTION:
|
||||
* Curl Client Trait for get/post/put/delete requests through the php curl inteface
|
||||
*
|
||||
* For anything more complex use guzzlehttp/http
|
||||
* https://docs.guzzlephp.org/en/stable/index.html
|
||||
*/
|
||||
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\UrlRequests;
|
||||
|
||||
trait CurlTrait
|
||||
{
|
||||
/**
|
||||
* combined set call for any type of request with options type parameters
|
||||
* The following options can be set:
|
||||
* header: as array string:string
|
||||
* query as string or array string:string
|
||||
* body as string or array of any type
|
||||
*
|
||||
* @param string $type What type of request we send, will throw exception if not a valid one
|
||||
* @param string $url The url to send
|
||||
* @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array<string,string|array<string>>,query?:null|array<string,string>,body?:null|string|array<mixed>,http_errors?:null|bool} $options Request options
|
||||
* @return array{code:string,headers:array<string,array<string>>,content:string} [default=[]] Result code, headers and content as array, content is json
|
||||
* @throws \UnexpectedValueException on missing body data when body data is needed
|
||||
*/
|
||||
abstract public function request(string $type, string $url, array $options = []): array;
|
||||
|
||||
/**
|
||||
* Makes an request to the target url via curl: GET
|
||||
* Returns result as string (json)
|
||||
*
|
||||
* @param string $url The URL being requested,
|
||||
* including domain and protocol
|
||||
* @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array<string,string|array<string>>,query?:null|array<string,string>,body?:null|string|array<mixed>,http_errors?:null|bool} $options Options to set
|
||||
* @return array{code:string,headers:array<string,array<string>>,content:string} [default=[]] Result code, headers and content as array, content is json
|
||||
*/
|
||||
public function get(string $url, array $options = []): array
|
||||
{
|
||||
return $this->request(
|
||||
"get",
|
||||
$url,
|
||||
$options,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an request to the target url via curl: POST
|
||||
* Returns result as string (json)
|
||||
*
|
||||
* @param string $url The URL being requested,
|
||||
* including domain and protocol
|
||||
* @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array<string,string|array<string>>,query?:null|array<string,string>,body?:null|string|array<mixed>,http_errors?:null|bool} $options Options to set
|
||||
* @return array{code:string,headers:array<string,array<string>>,content:string} Result code, headers and content as array, content is json
|
||||
*/
|
||||
public function post(string $url, array $options): array
|
||||
{
|
||||
return $this->request(
|
||||
"post",
|
||||
$url,
|
||||
$options,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an request to the target url via curl: PUT
|
||||
* Returns result as string (json)
|
||||
*
|
||||
* @param string $url The URL being requested,
|
||||
* including domain and protocol
|
||||
* @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array<string,string|array<string>>,query?:null|array<string,string>,body?:null|string|array<mixed>,http_errors?:null|bool} $options Options to set
|
||||
* @return array{code:string,headers:array<string,array<string>>,content:string} Result code, headers and content as array, content is json
|
||||
*/
|
||||
public function put(string $url, array $options): array
|
||||
{
|
||||
return $this->request(
|
||||
"put",
|
||||
$url,
|
||||
$options,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an request to the target url via curl: PATCH
|
||||
* Returns result as string (json)
|
||||
*
|
||||
* @param string $url The URL being requested,
|
||||
* including domain and protocol
|
||||
* @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array<string,string|array<string>>,query?:null|array<string,string>,body?:null|string|array<mixed>,http_errors?:null|bool} $options Options to set
|
||||
* @return array{code:string,headers:array<string,array<string>>,content:string} Result code, headers and content as array, content is json
|
||||
*/
|
||||
public function patch(string $url, array $options): array
|
||||
{
|
||||
return $this->request(
|
||||
"patch",
|
||||
$url,
|
||||
$options,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an request to the target url via curl: DELETE
|
||||
* Returns result as string (json)
|
||||
* Note that DELETE body is optional
|
||||
*
|
||||
* @param string $url The URL being requested,
|
||||
* including domain and protocol
|
||||
* @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array<string,string|array<string>>,query?:null|array<string,string>,body?:null|string|array<mixed>,http_errors?:null|bool} $options Options to set
|
||||
* @return array{code:string,headers:array<string,array<string>>,content:string} [default=[]] Result code, headers and content as array, content is json
|
||||
*/
|
||||
public function delete(string $url, array $options = []): array
|
||||
{
|
||||
return $this->request(
|
||||
"delete",
|
||||
$url,
|
||||
$options,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
83
src/UrlRequests/Interface/RequestsInterface.php
Normal file
83
src/UrlRequests/Interface/RequestsInterface.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2024/9/20
|
||||
* DESCRIPTION:
|
||||
* URL Requests client interface
|
||||
*/
|
||||
|
||||
namespace CoreLibs\UrlRequests\Interface;
|
||||
|
||||
interface RequestsInterface
|
||||
{
|
||||
/**
|
||||
* get the config array with all settings
|
||||
*
|
||||
* @return array<string,mixed> all current config settings
|
||||
*/
|
||||
public function getConfig(): array;
|
||||
|
||||
/**
|
||||
* Return the full url as it was sent
|
||||
*
|
||||
* @return string url sent
|
||||
*/
|
||||
public function getUrlSent(): string;
|
||||
|
||||
/**
|
||||
* get the parsed url
|
||||
*
|
||||
* @return array{scheme?:string,user?:string,host?:string,port?:string,path?:string,query?:string,fragment?:string,pass?:string}
|
||||
*/
|
||||
public function getUrlParsedSent(): array;
|
||||
|
||||
/**
|
||||
* Return the full headers as they where sent
|
||||
*
|
||||
* @return array<string,string>
|
||||
*/
|
||||
public function getHeadersSent(): array;
|
||||
|
||||
/**
|
||||
* set, add or overwrite header
|
||||
* On default this will overwrite header, and not set
|
||||
*
|
||||
* @param array<string,string|array<string>> $header
|
||||
* @param bool $add [default=false] if set will add header to existing value
|
||||
* @return void
|
||||
*/
|
||||
public function setHeaders(array $header, bool $add = false): void;
|
||||
|
||||
/**
|
||||
* remove header entry
|
||||
* if key is only set then match only key, if both are set both sides must match
|
||||
*
|
||||
* @param array<string,string> $remove_headers
|
||||
* @return void
|
||||
*/
|
||||
public function removeHeaders(array $remove_headers): void;
|
||||
|
||||
/**
|
||||
* Update the base url set, if empty will unset the base url
|
||||
*
|
||||
* @param string $base_uri
|
||||
* @return void
|
||||
*/
|
||||
public function setBaseUri(string $base_uri): void;
|
||||
|
||||
/**
|
||||
* combined set call for any type of request with options type parameters
|
||||
*
|
||||
* phpcs:disable Generic.Files.LineLength
|
||||
* @param string $type
|
||||
* @param string $url
|
||||
* @param array{auth?:null|array{0:string,1:string,2:string},headers?:null|array<string,string|array<string>>,query?:null|array<string,string>,body?:null|string|array<mixed>,http_errors?:null|bool} $options
|
||||
* @return array{code:string,headers:array<string,array<string>>,content:string} Result code, headers and content as array, content is json
|
||||
* @throws \UnexpectedValueException on missing body data when body data is needed
|
||||
* phpcs:enable Generic.Files.LineLength
|
||||
*/
|
||||
public function request(string $type, string $url, array $options = []): array;
|
||||
}
|
||||
|
||||
// __END__
|
||||
@@ -1,2 +1,2 @@
|
||||
base="/storage/var/www/html/developers/clemens/core_data/composer-packages/CoreLibs-Composer-All/";
|
||||
vendor/bin/phan --progress-bar -C --analyze-twice
|
||||
tools/phan --progress-bar -C --analyze-twice
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
base="/storage/var/www/html/developers/clemens/core_data/composer-packages/CoreLibs-Composer-All/";
|
||||
vendor/bin/phpstan
|
||||
tools/phpstan
|
||||
|
||||
@@ -15,25 +15,27 @@ php_bin="";
|
||||
if [ ! -z "${1}" ]; then
|
||||
case "${1}" in
|
||||
# "7.3") php_bin="/usr/bin/php7.3 "; ;;
|
||||
"7.4") php_bin="/usr/bin/php7.4 "; ;;
|
||||
"8.0") php_bin="/usr/bin/php8.0 "; ;;
|
||||
"8.1") php_bin="/usr/bin/php8.1 "; ;;
|
||||
#"7.4") php_bin="/usr/bin/php7.4 "; ;;
|
||||
#"8.0") php_bin="/usr/bin/php8.0 "; ;;
|
||||
#"8.1") php_bin="/usr/bin/php8.1 "; ;;
|
||||
"8.2") php_bin="/usr/bin/php8.2 "; ;;
|
||||
"8.3") php_bin="/usr/bin/php8.3 "; ;;
|
||||
*) echo "Not support PHP: ${1}"; exit; ;;
|
||||
esac;
|
||||
fi;
|
||||
if [ ! -z "${2}" ] && [ -z "${php_bin}" ]; then
|
||||
case "${2}" in
|
||||
# "7.3") php_bin="/usr/bin/php7.3 "; ;;
|
||||
"7.4") php_bin="/usr/bin/php7.4 "; ;;
|
||||
"8.0") php_bin="/usr/bin/php8.0 "; ;;
|
||||
"8.1") php_bin="/usr/bin/php8.1 "; ;;
|
||||
#"7.4") php_bin="/usr/bin/php7.4 "; ;;
|
||||
#"8.0") php_bin="/usr/bin/php8.0 "; ;;
|
||||
#"8.1") php_bin="/usr/bin/php8.1 "; ;;
|
||||
"8.2") php_bin="/usr/bin/php8.2 "; ;;
|
||||
"8.3") php_bin="/usr/bin/php8.3 "; ;;
|
||||
*) echo "Not support PHP: ${1}"; exit; ;;
|
||||
esac;
|
||||
fi;
|
||||
|
||||
phpunit_call="${php_bin}${base}vendor/bin/phpunit ${opt_testdox} -c ${base}phpunit.xml ${base}test/phpunit/";
|
||||
phpunit_call="${php_bin}${base}tools/phpunit ${opt_testdox} -c ${base}phpunit.xml ${base}test/phpunit/";
|
||||
|
||||
${phpunit_call};
|
||||
|
||||
|
||||
@@ -100,27 +100,6 @@ define('DEFAULT_ACL_LEVEL', 80);
|
||||
/************* LOGOUT ********************/
|
||||
// logout target
|
||||
define('LOGOUT_TARGET', '');
|
||||
// password change allowed
|
||||
define('PASSWORD_CHANGE', false);
|
||||
define('PASSWORD_FORGOT', false);
|
||||
// min/max password length
|
||||
define('PASSWORD_MIN_LENGTH', 9);
|
||||
define('PASSWORD_MAX_LENGTH', 255);
|
||||
// defines allowed special characters
|
||||
define('PASSWORD_SPECIAL_RANGE', '@$!%*?&');
|
||||
// password must have upper case, lower case, number, special
|
||||
// comment out for not mandatory
|
||||
define('PASSWORD_LOWER', '(?=.*[a-z])');
|
||||
define('PASSWORD_UPPER', '(?=.*[A-Z])');
|
||||
define('PASSWORD_NUMBER', '(?=.*\d)');
|
||||
define('PASSWORD_SPECIAL', "(?=.*[" . PASSWORD_SPECIAL_RANGE . "])");
|
||||
// define full regex
|
||||
define('PASSWORD_REGEX', "/^"
|
||||
. (defined('PASSWORD_LOWER') ? PASSWORD_LOWER : '')
|
||||
. (defined('PASSWORD_UPPER') ? PASSWORD_UPPER : '')
|
||||
. (defined('PASSWORD_NUMBER') ? PASSWORD_NUMBER : '')
|
||||
. (defined('PASSWORD_SPECIAL') ? PASSWORD_SPECIAL : '')
|
||||
. "[A-Za-z\d" . PASSWORD_SPECIAL_RANGE . "]{" . PASSWORD_MIN_LENGTH . "," . PASSWORD_MAX_LENGTH . "}$/");
|
||||
|
||||
/************* AJAX / ACCESS *************/
|
||||
// ajax request type
|
||||
@@ -161,13 +140,6 @@ define('DEFAULT_LOCALE', 'en_US.UTF-8');
|
||||
// default web page encoding setting
|
||||
define('DEFAULT_ENCODING', 'UTF-8');
|
||||
|
||||
/************* LOGGING *******************/
|
||||
// below two can be defined here, but they should be
|
||||
// defined in either the header file or the file itself
|
||||
// as $LOG_FILE_ID which takes presence over LOG_FILE_ID
|
||||
// see Basic class constructor
|
||||
define('LOG_FILE_ID', BASE_NAME);
|
||||
|
||||
/************* QUEUE TABLE *************/
|
||||
// if we have a dev/live system
|
||||
// set_live is a per page/per item
|
||||
@@ -291,22 +263,4 @@ if (file_exists(BASE . CONFIGS . 'config.other.php')) {
|
||||
require BASE . CONFIGS . 'config.other.php';
|
||||
}
|
||||
|
||||
/************* DEBUG *******************/
|
||||
// turn off debug if debug flag is OFF
|
||||
if (defined('DEBUG') && DEBUG == false) {
|
||||
$ECHO_ALL = false;
|
||||
$DEBUG_ALL = false;
|
||||
$PRINT_ALL = false;
|
||||
$DB_DEBUG = false;
|
||||
$ENABLE_ERROR_HANDLING = false;
|
||||
$DEBUG_ALL_OVERRIDE = false;
|
||||
} else {
|
||||
$ECHO_ALL = false;
|
||||
$DEBUG_ALL = true;
|
||||
$PRINT_ALL = true;
|
||||
$DB_DEBUG = true;
|
||||
$ENABLE_ERROR_HANDLING = false;
|
||||
$DEBUG_ALL_OVERRIDE = false;
|
||||
}
|
||||
|
||||
// __END__
|
||||
|
||||
28
test/configs/config.path.php
Normal file
28
test/configs/config.path.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php // phpcs:ignore PSR1.Files.SideEffects
|
||||
|
||||
/********************************************************************
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2018/10/11
|
||||
* SHORT DESCRIPTION:
|
||||
* configuration file for core path settings
|
||||
* CSV target paths, and other download access URLS or paths needed
|
||||
* HISTORY:
|
||||
*********************************************************************/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// find trigger name "admin/" or "frontend/" in the getcwd() folder
|
||||
$folder = '';
|
||||
foreach (['admin', 'frontend'] as $_folder) {
|
||||
if (strstr(getcwd() ?: '', DIRECTORY_SEPARATOR . $_folder)) {
|
||||
$folder = $_folder;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if content path is empty, fallback is default
|
||||
if (empty($folder)) {
|
||||
$folder = 'default';
|
||||
}
|
||||
define('CONTENT_PATH', $folder . DIRECTORY_SEPARATOR);
|
||||
|
||||
// __END__
|
||||
@@ -53,19 +53,6 @@ for (
|
||||
\gullevek\dotEnv\DotEnv::readEnvFile(
|
||||
$__DIR__PATH . $CONFIG_PATH_PREFIX . CONFIG_PATH
|
||||
);
|
||||
// find trigger name "admin/" or "frontend/" in the getcwd() folder
|
||||
$folder = '';
|
||||
foreach (['admin', 'frontend'] as $_folder) {
|
||||
if (strstr(getcwd() ?: '', DIRECTORY_SEPARATOR . $_folder)) {
|
||||
$folder = $_folder;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if content path is empty, fallback is default
|
||||
if (empty($folder)) {
|
||||
$folder = 'default';
|
||||
}
|
||||
define('CONTENT_PATH', $folder . DIRECTORY_SEPARATOR);
|
||||
// load master config file that loads all other config files
|
||||
require $__DIR__PATH . $CONFIG_PATH_PREFIX . CONFIG_PATH . 'config.master.php';
|
||||
break;
|
||||
|
||||
@@ -4,4 +4,7 @@ require "../vendor/autoload.php";
|
||||
|
||||
print "Bytes: " . CoreLibs\Convert\Byte::humanReadableByteFormat(123414) . "<br>";
|
||||
|
||||
$curl = new CoreLibs\UrlRequests\Curl();
|
||||
print "Config: " . print_r($curl->getConfig(), true) . "<br>";
|
||||
|
||||
// __END__
|
||||
|
||||
65
test/phpunit/AAASetupData/requests/http_requests.php
Normal file
65
test/phpunit/AAASetupData/requests/http_requests.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php // phpcs:ignore PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: Ymd
|
||||
* DESCRIPTION:
|
||||
* DescriptionHere
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* build return json
|
||||
*
|
||||
* @param array<string,mixed> $http_headers
|
||||
* @param ?string $body
|
||||
* @return string
|
||||
*/
|
||||
function buildContent(array $http_headers, ?string $body): string
|
||||
{
|
||||
if (is_string($body) && !empty($body)) {
|
||||
$_body = json_decode($body, true);
|
||||
if (!is_array($_body)) {
|
||||
$body = [$body];
|
||||
} else {
|
||||
$body = $_body;
|
||||
}
|
||||
} elseif (is_string($body)) {
|
||||
$body = [];
|
||||
}
|
||||
return json_encode([
|
||||
'HEADERS' => $http_headers,
|
||||
"REQUEST_TYPE" => $_SERVER['REQUEST_METHOD'],
|
||||
"PARAMS" => $_GET,
|
||||
"BODY" => $body,
|
||||
]);
|
||||
}
|
||||
|
||||
$http_headers = array_filter($_SERVER, function ($value, $key) {
|
||||
if (str_starts_with($key, 'HTTP_')) {
|
||||
return true;
|
||||
}
|
||||
}, ARRAY_FILTER_USE_BOTH);
|
||||
|
||||
header("Content-Type: application/json; charset=UTF-8");
|
||||
|
||||
// if the header has Authorization and RunAuthTest then exit with 401
|
||||
if (!empty($http_headers['HTTP_AUTHORIZATION']) && !empty($http_headers['HTTP_RUNAUTHTEST'])) {
|
||||
header("HTTP/1.1 401 Unauthorized");
|
||||
print buildContent($http_headers, '{"code": 401, "content": {"Error": "Not Authorized"}}');
|
||||
exit;
|
||||
}
|
||||
|
||||
// if server request type is get set file_get to null -> no body
|
||||
if ($_SERVER['REQUEST_METHOD'] == "GET") {
|
||||
$file_get = null;
|
||||
} elseif (($file_get = file_get_contents('php://input')) === false) {
|
||||
header("HTTP/1.1 404 Not Found");
|
||||
print buildContent($http_headers, '{"code": 404, "content": {"Error": "file_get_contents failed"}}');
|
||||
exit;
|
||||
}
|
||||
|
||||
print buildContent($http_headers, $file_get);
|
||||
|
||||
// __END__
|
||||
@@ -22,8 +22,12 @@ Not yet covered tests:
|
||||
*/
|
||||
final class CoreLibsACLLoginTest extends TestCase
|
||||
{
|
||||
private static $db;
|
||||
private static $log;
|
||||
private static \CoreLibs\DB\IO $db;
|
||||
private static \CoreLibs\Logging\Logging $log;
|
||||
|
||||
private static string $edit_access_cuid;
|
||||
private static string $edit_user_cuid;
|
||||
private static string $edit_user_cuuid;
|
||||
|
||||
/**
|
||||
* start DB conneciton, setup DB, etc
|
||||
@@ -108,14 +112,40 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
self::$db->dbSetMaxQueryCall(-1);
|
||||
// insert additional content for testing (locked user, etc)
|
||||
$queries = [
|
||||
"INSERT INTO edit_access_data "
|
||||
. "(edit_access_id, name, value, enabled) VALUES "
|
||||
. "((SELECT edit_access_id FROM edit_access WHERE uid = 'AdminAccess'), "
|
||||
. "'test', 'value', 1)"
|
||||
<<<SQL
|
||||
INSERT INTO edit_access_data (
|
||||
edit_access_id, name, value, enabled
|
||||
) VALUES (
|
||||
(SELECT edit_access_id FROM edit_access WHERE uid = 'AdminAccess'),
|
||||
'test', 'value', 1
|
||||
)
|
||||
SQL
|
||||
];
|
||||
foreach ($queries as $query) {
|
||||
self::$db->dbExec($query);
|
||||
}
|
||||
// read edit access cuid, edit user cuid and edit user cuuid
|
||||
$row = self::$db->dbReturnRowParams(
|
||||
"SELECT cuid FROM edit_access WHERE uid = $1",
|
||||
["AdminAccess"]
|
||||
);
|
||||
self::$edit_access_cuid = $row['cuid'] ?? '';
|
||||
if (empty(self::$edit_access_cuid)) {
|
||||
self::markTestIncomplete(
|
||||
'Cannot read edit access cuid for "AdminAccess".'
|
||||
);
|
||||
}
|
||||
$row = self::$db->dbReturnRowParams(
|
||||
"SELECT cuid, cuuid FROM edit_user WHERE username = $1",
|
||||
["admin"]
|
||||
);
|
||||
self::$edit_user_cuid = $row['cuid'] ?? '';
|
||||
self::$edit_user_cuuid = $row['cuuid'] ?? '';
|
||||
if (empty(self::$edit_user_cuid) || empty(self::$edit_user_cuuid)) {
|
||||
self::markTestIncomplete(
|
||||
'Cannot read edit user cuid or cuuid for "admin".'
|
||||
);
|
||||
}
|
||||
|
||||
// define mandatory constant
|
||||
// must set
|
||||
@@ -235,22 +265,25 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
'ajax_post_action' => 'login',
|
||||
],
|
||||
],
|
||||
'load, session euid set only, php error' => [
|
||||
'load, session eucuuid set only, php error' => [
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
],
|
||||
[],
|
||||
[],
|
||||
[
|
||||
'EUID' => 1,
|
||||
'LOGIN_EUID' => 1,
|
||||
'LOGIN_EUCUID' => 'abc',
|
||||
'LOGIN_EUCUUID' => '1233456-1234-1234-1234-123456789012',
|
||||
],
|
||||
2,
|
||||
[],
|
||||
],
|
||||
'load, session euid set, all set' => [
|
||||
'load, session eucuuid set, all set' => [
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'edit_access_uid' => 'AdminAccess',
|
||||
'edit_access_data' => 'test',
|
||||
'base_access' => 'list',
|
||||
@@ -259,20 +292,23 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[],
|
||||
[],
|
||||
[
|
||||
'EUID' => 1,
|
||||
'USER_NAME' => '',
|
||||
'GROUP_NAME' => '',
|
||||
'ADMIN' => 1,
|
||||
'GROUP_ACL_LEVEL' => -1,
|
||||
'PAGES_ACL_LEVEL' => [],
|
||||
'USER_ACL_LEVEL' => -1,
|
||||
'USER_ADDITIONAL_ACL' => [],
|
||||
'GROUP_ADDITIONAL_ACL' => [],
|
||||
'UNIT_UID' => [
|
||||
'AdminAccess' => 1,
|
||||
'LOGIN_EUID' => 1,
|
||||
'LOGIN_EUCUID' => 'abc',
|
||||
'LOGIN_EUCUUID' => 'SET_EUCUUID_IN_TEST',
|
||||
'LOGIN_USER_NAME' => '',
|
||||
'LOGIN_GROUP_NAME' => '',
|
||||
'LOGIN_ADMIN' => 1,
|
||||
'LOGIN_GROUP_ACL_LEVEL' => -1,
|
||||
'LOGIN_PAGES_ACL_LEVEL' => [],
|
||||
'LOGIN_USER_ACL_LEVEL' => -1,
|
||||
'LOGIN_USER_ADDITIONAL_ACL' => [],
|
||||
'LOGIN_GROUP_ADDITIONAL_ACL' => [],
|
||||
'LOGIN_UNIT_UID' => [
|
||||
'AdminAccess' => '123456789012',
|
||||
],
|
||||
'UNIT' => [
|
||||
1 => [
|
||||
'LOGIN_UNIT' => [
|
||||
'123456789012' => [
|
||||
'id' => 1,
|
||||
'acl_level' => 80,
|
||||
'name' => 'Admin Access',
|
||||
'uid' => 'AdminAccess',
|
||||
@@ -284,8 +320,8 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
'additional_acl' => []
|
||||
],
|
||||
],
|
||||
// 'UNIT_DEFAULT' => '',
|
||||
// 'DEFAULT_ACL_LIST' => [],
|
||||
// 'LOGIN_UNIT_DEFAULT' => '',
|
||||
// 'LOGIN_DEFAULT_ACL_LIST' => [],
|
||||
],
|
||||
0,
|
||||
[
|
||||
@@ -293,6 +329,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
'admin_flag' => true,
|
||||
'check_access' => true,
|
||||
'check_access_id' => 1,
|
||||
'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'check_access_data' => 'value',
|
||||
'base_access' => true,
|
||||
'page_access' => true,
|
||||
@@ -412,6 +449,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'base_access' => 'list',
|
||||
'page_access' => 'list',
|
||||
'test_deleted' => true
|
||||
@@ -437,6 +475,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'base_access' => 'list',
|
||||
'page_access' => 'list',
|
||||
'test_enabled' => true
|
||||
@@ -462,6 +501,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'base_access' => 'list',
|
||||
'page_access' => 'list',
|
||||
'test_locked' => true
|
||||
@@ -487,6 +527,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'base_access' => 'list',
|
||||
'page_access' => 'list',
|
||||
'test_get_locked' => true,
|
||||
@@ -511,6 +552,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'base_access' => 'list',
|
||||
'page_access' => 'list',
|
||||
'test_locked_period_until' => 'on'
|
||||
@@ -536,6 +578,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'edit_access_uid' => 'AdminAccess',
|
||||
'edit_access_data' => 'test',
|
||||
'base_access' => 'list',
|
||||
@@ -555,6 +598,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
'admin_flag' => true,
|
||||
'check_access' => true,
|
||||
'check_access_id' => 1,
|
||||
'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'check_access_data' => 'value',
|
||||
'base_access' => true,
|
||||
'page_access' => true,
|
||||
@@ -565,6 +609,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'base_access' => 'list',
|
||||
'page_access' => 'list',
|
||||
'test_locked_period_after' => 'on'
|
||||
@@ -590,6 +635,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'base_access' => 'list',
|
||||
'page_access' => 'list',
|
||||
'test_locked_period_until' => 'on',
|
||||
@@ -616,6 +662,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'base_access' => 'list',
|
||||
'page_access' => 'list',
|
||||
'test_login_user_id_locked' => true
|
||||
@@ -641,6 +688,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'edit_access_uid' => 'AdminAccess',
|
||||
'edit_access_data' => 'test',
|
||||
'base_access' => 'list',
|
||||
@@ -659,6 +707,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
'admin_flag' => true,
|
||||
'check_access' => true,
|
||||
'check_access_id' => 1,
|
||||
'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'check_access_data' => 'value',
|
||||
'base_access' => true,
|
||||
'page_access' => true,
|
||||
@@ -669,6 +718,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'edit_access_uid' => 'AdminAccess',
|
||||
'edit_access_data' => 'test',
|
||||
'base_access' => 'list',
|
||||
@@ -688,6 +738,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
'admin_flag' => true,
|
||||
'check_access' => true,
|
||||
'check_access_id' => 1,
|
||||
'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'check_access_data' => 'value',
|
||||
'base_access' => true,
|
||||
'page_access' => true,
|
||||
@@ -698,6 +749,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'edit_access_uid' => 'AdminAccess',
|
||||
'edit_access_data' => 'test',
|
||||
'base_access' => 'list',
|
||||
@@ -717,6 +769,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
'admin_flag' => true,
|
||||
'check_access' => true,
|
||||
'check_access_id' => 1,
|
||||
'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'check_access_data' => 'value',
|
||||
'base_access' => true,
|
||||
'page_access' => true,
|
||||
@@ -727,6 +780,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'edit_access_uid' => 'AdminAccess',
|
||||
'edit_access_data' => 'test',
|
||||
'base_access' => 'list',
|
||||
@@ -746,6 +800,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
'admin_flag' => true,
|
||||
'check_access' => true,
|
||||
'check_access_id' => 1,
|
||||
'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'check_access_data' => 'value',
|
||||
'base_access' => true,
|
||||
'page_access' => true,
|
||||
@@ -777,6 +832,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'edit_access_uid' => 'AdminAccess',
|
||||
'edit_access_data' => 'test',
|
||||
'base_access' => 'list',
|
||||
@@ -800,6 +856,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
'admin_flag' => true,
|
||||
'check_access' => true,
|
||||
'check_access_id' => 1,
|
||||
'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'check_access_data' => 'value',
|
||||
'base_access' => true,
|
||||
'page_access' => true,
|
||||
@@ -810,6 +867,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'edit_access_uid' => 'AdminAccess',
|
||||
'edit_access_data' => 'test',
|
||||
'base_access' => 'list',
|
||||
@@ -833,6 +891,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
'admin_flag' => true,
|
||||
'check_access' => true,
|
||||
'check_access_id' => 1,
|
||||
'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'check_access_data' => 'value',
|
||||
'base_access' => true,
|
||||
'page_access' => true,
|
||||
@@ -843,6 +902,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'base_access' => 'list',
|
||||
'page_access' => 'list',
|
||||
'test_login_user_id_revalidate_after' => 'on',
|
||||
@@ -869,6 +929,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'edit_access_uid' => 'AdminAccess',
|
||||
'edit_access_data' => 'test',
|
||||
'base_access' => 'list',
|
||||
@@ -889,6 +950,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
'admin_flag' => true,
|
||||
'check_access' => true,
|
||||
'check_access_id' => 1,
|
||||
'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'check_access_data' => 'value',
|
||||
'base_access' => true,
|
||||
'page_access' => true,
|
||||
@@ -899,6 +961,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'base_access' => 'list',
|
||||
'page_access' => 'list',
|
||||
'test_login_user_id_valid_from' => 'on',
|
||||
@@ -925,6 +988,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'edit_access_uid' => 'AdminAccess',
|
||||
'edit_access_data' => 'test',
|
||||
'base_access' => 'list',
|
||||
@@ -945,6 +1009,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
'admin_flag' => true,
|
||||
'check_access' => true,
|
||||
'check_access_id' => 1,
|
||||
'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'check_access_data' => 'value',
|
||||
'base_access' => true,
|
||||
'page_access' => true,
|
||||
@@ -955,6 +1020,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'base_access' => 'list',
|
||||
'page_access' => 'list',
|
||||
'test_login_user_id_valid_until' => 'on',
|
||||
@@ -981,6 +1047,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'base_access' => 'list',
|
||||
'page_access' => 'list',
|
||||
'test_login_user_id_valid_from' => 'on',
|
||||
@@ -1008,6 +1075,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
[
|
||||
'page_name' => 'edit_users.php',
|
||||
'edit_access_id' => 1,
|
||||
'edit_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'edit_access_uid' => 'AdminAccess',
|
||||
'edit_access_data' => 'test',
|
||||
'base_access' => 'list',
|
||||
@@ -1038,6 +1106,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
'admin_flag' => true,
|
||||
'check_access' => true,
|
||||
'check_access_id' => 1,
|
||||
'check_access_cuid' => 'SET_EDIT_ACCESS_CUID_IN_TEST',
|
||||
'check_access_data' => 'value',
|
||||
'base_access' => true,
|
||||
'page_access' => true,
|
||||
@@ -1085,9 +1154,9 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
/** @var \CoreLibs\Create\Session&MockObject */
|
||||
$session_mock = $this->createPartialMock(
|
||||
\CoreLibs\Create\Session::class,
|
||||
['startSession', 'checkActiveSession', 'sessionDestroy']
|
||||
['getSessionId', 'checkActiveSession', 'sessionDestroy']
|
||||
);
|
||||
$session_mock->method('startSession')->willReturn('ACLLOGINTEST12');
|
||||
$session_mock->method('getSessionId')->willReturn('ACLLOGINTEST12');
|
||||
$session_mock->method('checkActiveSession')->willReturn(true);
|
||||
$session_mock->method('sessionDestroy')->will(
|
||||
$this->returnCallback(function () {
|
||||
@@ -1107,11 +1176,15 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
$_POST[$post_var] = $post_value;
|
||||
}
|
||||
|
||||
// set ingoing session cuuid if requested
|
||||
if (isset($session['LOGIN_EUCUUID']) && $session['LOGIN_EUCUUID'] == 'SET_EUCUUID_IN_TEST') {
|
||||
$session['LOGIN_EUCUUID'] = self::$edit_user_cuuid;
|
||||
}
|
||||
|
||||
// set _SESSION data
|
||||
foreach ($session as $session_var => $session_value) {
|
||||
$_SESSION[$session_var] = $session_value;
|
||||
}
|
||||
|
||||
/** @var \CoreLibs\ACL\Login&MockObject */
|
||||
$login_mock = $this->getMockBuilder(\CoreLibs\ACL\Login::class)
|
||||
->setConstructorArgs([
|
||||
@@ -1130,7 +1203,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
. 'locale' . DIRECTORY_SEPARATOR,
|
||||
]
|
||||
])
|
||||
->onlyMethods(['loginTerminate', 'loginReadPageName', 'loginPrintLogin'])
|
||||
->onlyMethods(['loginTerminate', 'loginReadPageName', 'loginPrintLogin', 'loginEnhanceHttpSecurity'])
|
||||
->getMock();
|
||||
$login_mock->expects($this->any())
|
||||
->method('loginTerminate')
|
||||
@@ -1148,6 +1221,10 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
->method('loginPrintLogin')
|
||||
->willReturnCallback(function () {
|
||||
});
|
||||
$login_mock->expects($this->any())
|
||||
->method('loginEnhanceHttpSecurity')
|
||||
->willReturnCallback(function () {
|
||||
});
|
||||
|
||||
// if mock_settings: enabled OFF
|
||||
// run DB update and set off
|
||||
@@ -1365,6 +1442,19 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
|
||||
// run test
|
||||
try {
|
||||
// preset, we cannot set that in the provider
|
||||
if (
|
||||
isset($expected['check_access_cuid']) &&
|
||||
$expected['check_access_cuid'] == 'SET_EDIT_ACCESS_CUID_IN_TEST'
|
||||
) {
|
||||
$expected['check_access_cuid'] = self::$edit_access_cuid;
|
||||
}
|
||||
if (
|
||||
isset($mock_settings['edit_access_cuid']) &&
|
||||
$mock_settings['edit_access_cuid'] == 'SET_EDIT_ACCESS_CUID_IN_TEST'
|
||||
) {
|
||||
$mock_settings['edit_access_cuid'] = self::$edit_access_cuid;
|
||||
}
|
||||
// if ajax call
|
||||
// check if parameter, or globals (old type)
|
||||
// else normal call
|
||||
@@ -1423,6 +1513,25 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
$login_mock->loginCheckAccessPage($mock_settings['page_access']),
|
||||
'Assert page access'
|
||||
);
|
||||
// - loginCheckEditAccessCuid
|
||||
$this->assertEquals(
|
||||
$expected['check_access'],
|
||||
$login_mock->loginCheckEditAccessCuid($mock_settings['edit_access_cuid']),
|
||||
'Assert check access'
|
||||
);
|
||||
// - loginCheckEditAccessValidCuid
|
||||
$this->assertEquals(
|
||||
$expected['check_access_cuid'],
|
||||
$login_mock->loginCheckEditAccessValidCuid($mock_settings['edit_access_cuid']),
|
||||
'Assert check access cuid valid'
|
||||
);
|
||||
// - loginGetEditAccessCuidFromUid
|
||||
$this->assertEquals(
|
||||
$expected['check_access_cuid'],
|
||||
$login_mock->loginGetEditAccessCuidFromUid($mock_settings['edit_access_uid']),
|
||||
'Assert check access uid to cuid valid'
|
||||
);
|
||||
// Deprecated
|
||||
// - loginCheckEditAccess
|
||||
$this->assertEquals(
|
||||
$expected['check_access'],
|
||||
@@ -1445,7 +1554,7 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
$this->assertEquals(
|
||||
$expected['check_access_data'],
|
||||
$login_mock->loginGetEditAccessData(
|
||||
$mock_settings['edit_access_id'],
|
||||
$mock_settings['edit_access_uid'],
|
||||
$mock_settings['edit_access_data']
|
||||
),
|
||||
'Assert check access id data value valid'
|
||||
@@ -1476,11 +1585,12 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
// - loginCheckPermissions
|
||||
// - loginGetPermissionOkay
|
||||
} catch (\Exception $e) {
|
||||
// print "[E]: " . $e->getCode() . ", ERROR: " . $login_mock->loginGetLastErrorCode() . "/"
|
||||
// . ($expected['login_error'] ?? 0) . "\n";
|
||||
// print "AJAX: " . $login_mock->loginGetAjaxFlag() . "\n";
|
||||
// print "AJAX GLOBAL: " . ($GLOBALS['AJAX_PAGE'] ?? '{f}') . "\n";
|
||||
// print "Login error expext: " . ($expected['login_error'] ?? '{0}') . "\n";
|
||||
/* print "[E]: " . $e->getCode() . ", ERROR: " . $login_mock->loginGetLastErrorCode() . "/"
|
||||
. ($expected['login_error'] ?? 0) . "\n";
|
||||
print "AJAX: " . $login_mock->loginGetAjaxFlag() . "\n";
|
||||
print "AJAX GLOBAL: " . ($GLOBALS['AJAX_PAGE'] ?? '{f}') . "\n";
|
||||
print "Login error expext: " . ($expected['login_error'] ?? '{0}') . "\n";
|
||||
print "POST exit: " . ($_POST['login_exit'] ?? '{0}') . "\n"; */
|
||||
// if this is 100, then we do further error checks
|
||||
if (
|
||||
$e->getCode() == 100 ||
|
||||
@@ -1788,9 +1898,9 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
/** @var \CoreLibs\Create\Session&MockObject */
|
||||
$session_mock = $this->createPartialMock(
|
||||
\CoreLibs\Create\Session::class,
|
||||
['startSession', 'checkActiveSession', 'sessionDestroy']
|
||||
['getSessionId', 'checkActiveSession', 'sessionDestroy']
|
||||
);
|
||||
$session_mock->method('startSession')->willReturn('ACLLOGINTEST34');
|
||||
$session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34');
|
||||
$session_mock->method('checkActiveSession')->willReturn(true);
|
||||
$session_mock->method('sessionDestroy')->will(
|
||||
$this->returnCallback(function () {
|
||||
@@ -1902,9 +2012,9 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
/** @var \CoreLibs\Create\Session&MockObject */
|
||||
$session_mock = $this->createPartialMock(
|
||||
\CoreLibs\Create\Session::class,
|
||||
['startSession', 'checkActiveSession', 'sessionDestroy']
|
||||
['getSessionId', 'checkActiveSession', 'sessionDestroy']
|
||||
);
|
||||
$session_mock->method('startSession')->willReturn('ACLLOGINTEST34');
|
||||
$session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34');
|
||||
$session_mock->method('checkActiveSession')->willReturn(true);
|
||||
$session_mock->method('sessionDestroy')->will(
|
||||
$this->returnCallback(function () {
|
||||
@@ -1990,9 +2100,9 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
/** @var \CoreLibs\Create\Session&MockObject */
|
||||
$session_mock = $this->createPartialMock(
|
||||
\CoreLibs\Create\Session::class,
|
||||
['startSession', 'checkActiveSession', 'sessionDestroy']
|
||||
['getSessionId', 'checkActiveSession', 'sessionDestroy']
|
||||
);
|
||||
$session_mock->method('startSession')->willReturn('ACLLOGINTEST34');
|
||||
$session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34');
|
||||
$session_mock->method('checkActiveSession')->willReturn(true);
|
||||
$session_mock->method('sessionDestroy')->will(
|
||||
$this->returnCallback(function () {
|
||||
@@ -2086,9 +2196,9 @@ final class CoreLibsACLLoginTest extends TestCase
|
||||
/** @var \CoreLibs\Create\Session&MockObject */
|
||||
$session_mock = $this->createPartialMock(
|
||||
\CoreLibs\Create\Session::class,
|
||||
['startSession', 'checkActiveSession', 'sessionDestroy']
|
||||
['getSessionId', 'checkActiveSession', 'sessionDestroy']
|
||||
);
|
||||
$session_mock->method('startSession')->willReturn('ACLLOGINTEST34');
|
||||
$session_mock->method('getSessionId')->willReturn('ACLLOGINTEST34');
|
||||
$session_mock->method('checkActiveSession')->willReturn(true);
|
||||
$session_mock->method('sessionDestroy')->will(
|
||||
$this->returnCallback(function () {
|
||||
|
||||
@@ -30,10 +30,11 @@ DECLARE
|
||||
random_length INT = 12; -- that should be long enough
|
||||
BEGIN
|
||||
IF TG_OP = 'INSERT' THEN
|
||||
NEW.date_created := 'now';
|
||||
NEW.date_created := clock_timestamp();
|
||||
NEW.cuid := random_string(random_length);
|
||||
NEW.cuuid := gen_random_uuid();
|
||||
ELSIF TG_OP = 'UPDATE' THEN
|
||||
NEW.date_updated := 'now';
|
||||
NEW.date_updated := clock_timestamp();
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
@@ -305,6 +306,7 @@ CREATE TABLE temp_files (
|
||||
-- DROP TABLE edit_generic;
|
||||
CREATE TABLE edit_generic (
|
||||
cuid VARCHAR,
|
||||
cuuid UUID,
|
||||
date_created TIMESTAMP WITHOUT TIME ZONE DEFAULT clock_timestamp(),
|
||||
date_updated TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
@@ -319,7 +321,7 @@ CREATE TABLE edit_generic (
|
||||
|
||||
-- DROP TABLE edit_visible_group;
|
||||
CREATE TABLE edit_visible_group (
|
||||
edit_visible_group_id SERIAL PRIMARY KEY,
|
||||
edit_visible_group_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR,
|
||||
flag VARCHAR
|
||||
) INHERITS (edit_generic) WITHOUT OIDS;
|
||||
@@ -334,7 +336,7 @@ CREATE TABLE edit_visible_group (
|
||||
|
||||
-- DROP TABLE edit_menu_group;
|
||||
CREATE TABLE edit_menu_group (
|
||||
edit_menu_group_id SERIAL PRIMARY KEY,
|
||||
edit_menu_group_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR,
|
||||
flag VARCHAR,
|
||||
order_number INT NOT NULL
|
||||
@@ -352,7 +354,7 @@ CREATE TABLE edit_menu_group (
|
||||
|
||||
-- DROP TABLE edit_page;
|
||||
CREATE TABLE edit_page (
|
||||
edit_page_id SERIAL PRIMARY KEY,
|
||||
edit_page_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
content_alias_edit_page_id INT, -- alias for page content, if the page content is defined on a different page, ege for ajax backend pages
|
||||
FOREIGN KEY (content_alias_edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE RESTRICT ON UPDATE CASCADE,
|
||||
filename VARCHAR,
|
||||
@@ -376,7 +378,7 @@ CREATE TABLE edit_page (
|
||||
|
||||
-- DROP TABLE edit_query_string;
|
||||
CREATE TABLE edit_query_string (
|
||||
edit_query_string_id SERIAL PRIMARY KEY,
|
||||
edit_query_string_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
edit_page_id INT NOT NULL,
|
||||
FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
enabled SMALLINT NOT NULL DEFAULT 0,
|
||||
@@ -428,7 +430,7 @@ CREATE TABLE edit_page_menu_group (
|
||||
|
||||
-- DROP TABLE edit_access_right;
|
||||
CREATE TABLE edit_access_right (
|
||||
edit_access_right_id SERIAL PRIMARY KEY,
|
||||
edit_access_right_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR,
|
||||
level SMALLINT,
|
||||
type VARCHAR,
|
||||
@@ -445,7 +447,7 @@ CREATE TABLE edit_access_right (
|
||||
|
||||
-- DROP TABLE edit_scheme;
|
||||
CREATE TABLE edit_scheme (
|
||||
edit_scheme_id SERIAL PRIMARY KEY,
|
||||
edit_scheme_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
enabled SMALLINT NOT NULL DEFAULT 0,
|
||||
name VARCHAR,
|
||||
header_color VARCHAR,
|
||||
@@ -464,7 +466,7 @@ CREATE TABLE edit_scheme (
|
||||
|
||||
-- DROP TABLE edit_language;
|
||||
CREATE TABLE edit_language (
|
||||
edit_language_id SERIAL PRIMARY KEY,
|
||||
edit_language_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
enabled SMALLINT NOT NULL DEFAULT 0,
|
||||
lang_default SMALLINT NOT NULL DEFAULT 0,
|
||||
long_name VARCHAR,
|
||||
@@ -483,7 +485,7 @@ CREATE TABLE edit_language (
|
||||
|
||||
-- DROP TABLE edit_group;
|
||||
CREATE TABLE edit_group (
|
||||
edit_group_id SERIAL PRIMARY KEY,
|
||||
edit_group_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
edit_scheme_id INT,
|
||||
FOREIGN KEY (edit_scheme_id) REFERENCES edit_scheme (edit_scheme_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
edit_access_right_id INT NOT NULL,
|
||||
@@ -505,7 +507,7 @@ CREATE TABLE edit_group (
|
||||
|
||||
-- DROP TABLE edit_page_access;
|
||||
CREATE TABLE edit_page_access (
|
||||
edit_page_access_id SERIAL PRIMARY KEY,
|
||||
edit_page_access_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
edit_group_id INT NOT NULL,
|
||||
FOREIGN KEY (edit_group_id) REFERENCES edit_group (edit_group_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
edit_page_id INT NOT NULL,
|
||||
@@ -528,7 +530,7 @@ CREATE TABLE edit_page_access (
|
||||
|
||||
-- DROP TABLE edit_page_content;
|
||||
CREATE TABLE edit_page_content (
|
||||
edit_page_content_id SERIAL PRIMARY KEY,
|
||||
edit_page_content_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
edit_page_id INT NOT NULL,
|
||||
FOREIGN KEY (edit_page_id) REFERENCES edit_page (edit_page_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
edit_access_right_id INT NOT NULL,
|
||||
@@ -549,7 +551,7 @@ CREATE TABLE edit_page_content (
|
||||
|
||||
-- DROP TABLE edit_user;
|
||||
CREATE TABLE edit_user (
|
||||
edit_user_id SERIAL PRIMARY KEY,
|
||||
edit_user_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
connect_edit_user_id INT, -- possible reference to other user
|
||||
FOREIGN KEY (connect_edit_user_id) REFERENCES edit_user (edit_user_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
edit_language_id INT NOT NULL,
|
||||
@@ -577,11 +579,10 @@ CREATE TABLE edit_user (
|
||||
strict SMALLINT DEFAULT 0,
|
||||
locked SMALLINT DEFAULT 0,
|
||||
protected SMALLINT NOT NULL DEFAULT 0,
|
||||
-- legacy, debug flags
|
||||
debug SMALLINT NOT NULL DEFAULT 0,
|
||||
db_debug SMALLINT NOT NULL DEFAULT 0,
|
||||
-- is admin user
|
||||
admin SMALLINT NOT NULL DEFAULT 0,
|
||||
-- forced logout counter
|
||||
force_logout INT DEFAULT 0,
|
||||
-- last login log
|
||||
last_login TIMESTAMP WITHOUT TIME ZONE,
|
||||
-- login error
|
||||
@@ -618,8 +619,6 @@ COMMENT ON COLUMN edit_user.deleted IS 'Login is deleted (master switch), overri
|
||||
COMMENT ON COLUMN edit_user.strict IS 'If too many failed logins user will be locked, default off';
|
||||
COMMENT ON COLUMN edit_user.locked IS 'Locked from too many wrong password logins';
|
||||
COMMENT ON COLUMN edit_user.protected IS 'User can only be chnaged by admin user';
|
||||
COMMENT ON COLUMN edit_user.debug IS 'Turn debug flag on (legacy)';
|
||||
COMMENT ON COLUMN edit_user.db_debug IS 'Turn DB debug flag on (legacy)';
|
||||
COMMENT ON COLUMN edit_user.admin IS 'If set, this user is SUPER admin';
|
||||
COMMENT ON COLUMN edit_user.last_login IS 'Last succesfull login tiemstamp';
|
||||
COMMENT ON COLUMN edit_user.login_error_count IS 'Number of failed logins, reset on successful login';
|
||||
@@ -650,37 +649,56 @@ COMMENT ON COLUMN edit_user.additional_acl IS 'Additional Access Control List st
|
||||
|
||||
-- DROP TABLE edit_log;
|
||||
CREATE TABLE edit_log (
|
||||
edit_log_id SERIAL PRIMARY KEY,
|
||||
edit_log_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
euid INT, -- this is a foreign key, but I don't nedd to reference to it
|
||||
FOREIGN KEY (euid) REFERENCES edit_user (edit_user_id) MATCH FULL ON UPDATE CASCADE ON DELETE SET NULL,
|
||||
username VARCHAR,
|
||||
password VARCHAR,
|
||||
eucuid VARCHAR,
|
||||
eucuuid UUID, -- this is the one we want to use, full UUIDv4 from the edit user table
|
||||
-- date_created equal, but can be overridden
|
||||
event_date TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
ip VARCHAR,
|
||||
-- session ID if set
|
||||
session_id VARCHAR,
|
||||
-- username
|
||||
username VARCHAR,
|
||||
-- DEPRECATED [password]
|
||||
password VARCHAR,
|
||||
ip_address JSONB, -- REMOTE_IP and all other IPs (X_FORWARD, etc) as JSON block
|
||||
-- DEPRECATED [ip]
|
||||
ip VARCHAR, -- just the REMOTE_IP, full set see ip_address
|
||||
-- string blocks, general
|
||||
error TEXT,
|
||||
event TEXT,
|
||||
-- bytea or string type storage of any data
|
||||
data_binary BYTEA,
|
||||
data TEXT,
|
||||
-- set page name only
|
||||
page VARCHAR,
|
||||
action VARCHAR,
|
||||
action_id VARCHAR,
|
||||
action_yes VARCHAR,
|
||||
action_flag VARCHAR,
|
||||
action_menu VARCHAR,
|
||||
action_loaded VARCHAR,
|
||||
action_value VARCHAR,
|
||||
action_type VARCHAR,
|
||||
action_error VARCHAR,
|
||||
-- various info data sets
|
||||
user_agent VARCHAR,
|
||||
referer VARCHAR,
|
||||
script_name VARCHAR,
|
||||
query_string VARCHAR,
|
||||
request_scheme VARCHAR, -- http or https
|
||||
server_name VARCHAR,
|
||||
http_host VARCHAR,
|
||||
http_accept VARCHAR,
|
||||
http_accept_charset VARCHAR,
|
||||
http_accept_encoding VARCHAR,
|
||||
session_id VARCHAR
|
||||
http_data JSONB,
|
||||
-- DEPRECATED [http*]
|
||||
http_accept VARCHAR, -- in http_data
|
||||
http_accept_charset VARCHAR, -- in http_data
|
||||
http_accept_encoding VARCHAR, -- in http_data
|
||||
-- any action var, -> same set in action_data as JSON
|
||||
action_data JSONB,
|
||||
-- DEPRECATED [action*]
|
||||
action VARCHAR, -- in action_data
|
||||
action_id VARCHAR, -- in action_data
|
||||
action_sub_id VARCHAR, -- in action_data
|
||||
action_yes VARCHAR, -- in action_data
|
||||
action_flag VARCHAR, -- in action_data
|
||||
action_menu VARCHAR, -- in action_data
|
||||
action_loaded VARCHAR, -- in action_data
|
||||
action_value VARCHAR, -- in action_data
|
||||
action_type VARCHAR, -- in action_data
|
||||
action_error VARCHAR -- in action_data
|
||||
) INHERITS (edit_generic) WITHOUT OIDS;
|
||||
-- END: table/edit_log.sql
|
||||
-- START: table/edit_log_overflow.sql
|
||||
@@ -707,7 +725,7 @@ ALTER TABLE edit_log_overflow ADD CONSTRAINT edit_log_overflow_euid_fkey FOREIGN
|
||||
|
||||
-- DROP TABLE edit_access;
|
||||
CREATE TABLE edit_access (
|
||||
edit_access_id SERIAL PRIMARY KEY,
|
||||
edit_access_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
enabled SMALLINT NOT NULL DEFAULT 0,
|
||||
protected SMALLINT DEFAULT 0,
|
||||
deleted SMALLINT DEFAULT 0,
|
||||
@@ -728,7 +746,7 @@ CREATE TABLE edit_access (
|
||||
|
||||
-- DROP TABLE edit_access_user;
|
||||
CREATE TABLE edit_access_user (
|
||||
edit_access_user_id SERIAL PRIMARY KEY,
|
||||
edit_access_user_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
edit_access_id INT NOT NULL,
|
||||
FOREIGN KEY (edit_access_id) REFERENCES edit_access (edit_access_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
edit_user_id INT NOT NULL,
|
||||
@@ -749,7 +767,7 @@ CREATE TABLE edit_access_user (
|
||||
|
||||
-- DROP TABLE edit_access_data;
|
||||
CREATE TABLE edit_access_data (
|
||||
edit_access_data_id SERIAL PRIMARY KEY,
|
||||
edit_access_data_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
edit_access_id INT NOT NULL,
|
||||
FOREIGN KEY (edit_access_id) REFERENCES edit_access (edit_access_id) MATCH FULL ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
enabled SMALLINT NOT NULL DEFAULT 0,
|
||||
@@ -1010,7 +1028,7 @@ INSERT INTO edit_page_access (enabled, edit_group_id, edit_page_id, edit_access_
|
||||
-- edit user
|
||||
-- inserts admin user so basic users can be created
|
||||
DELETE FROM edit_user;
|
||||
INSERT INTO edit_user (username, password, enabled, debug, db_debug, email, protected, admin, edit_language_id, edit_group_id, edit_scheme_id, edit_access_right_id) VALUES ('admin', 'admin', 1, 1, 1, '', 1, 1,
|
||||
INSERT INTO edit_user (username, password, enabled, email, protected, admin, edit_language_id, edit_group_id, edit_scheme_id, edit_access_right_id) VALUES ('admin', 'admin', 1, 'test@tequila.jp', 1, 1,
|
||||
(SELECT edit_language_id FROM edit_language WHERE short_name = 'en_US'),
|
||||
(SELECT edit_group_id FROM edit_group WHERE name = 'Admin'),
|
||||
(SELECT edit_scheme_id FROM edit_scheme WHERE name = 'Admin'),
|
||||
|
||||
@@ -28,10 +28,10 @@ final class CoreLibsCheckFileTest extends TestCase
|
||||
public function filesList(): array
|
||||
{
|
||||
return [
|
||||
['filename.txt', 'txt', 5],
|
||||
['filename.csv', 'csv', 15],
|
||||
['filename.tsv', 'tsv', 0],
|
||||
['file_does_not_exits', '', -1],
|
||||
['filename.txt', 'txt', 5, 'text/plain'],
|
||||
['filename.csv', 'csv', 15, 'text/csv'],
|
||||
['filename.tsv', 'tsv', 0, 'text/plain'],
|
||||
['file_does_not_exits', '', -1, ''],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -63,6 +63,15 @@ final class CoreLibsCheckFileTest extends TestCase
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function mimeTypeProvider(): array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->filesList() as $row) {
|
||||
$list[$row[0] . ' must be mime type ' . $row[3]] = [$row[0], $row[3]];
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if file extension matches
|
||||
*
|
||||
@@ -115,6 +124,51 @@ final class CoreLibsCheckFileTest extends TestCase
|
||||
unlink($this->base_folder . $input);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::getMimeType
|
||||
* @dataProvider mimeTypeProvider
|
||||
* @testdox getMimeType $input must be mime type $expected [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $expected
|
||||
* @return void
|
||||
*/
|
||||
public function testGetMimeType(string $input, string $expected): void
|
||||
{
|
||||
if (!empty($expected)) {
|
||||
$file = $this->base_folder . $input;
|
||||
$fp = fopen($file, 'w');
|
||||
switch ($expected) {
|
||||
case 'text/csv':
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
fwrite($fp, '"This is row","' . $expected . '",' . $i . PHP_EOL);
|
||||
}
|
||||
break;
|
||||
case 'text/tsv':
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
fwrite($fp, "\"This is row\"\t\"" . $expected . "\"\t\"" . $i . PHP_EOL);
|
||||
}
|
||||
break;
|
||||
case 'text/plain':
|
||||
fwrite($fp, 'This is mime type: ' . $expected . PHP_EOL);
|
||||
break;
|
||||
}
|
||||
fclose($fp);
|
||||
} else {
|
||||
$this->expectException(\UnexpectedValueException::class);
|
||||
}
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
\CoreLibs\Check\File::getMimeType($this->base_folder . $input)
|
||||
);
|
||||
// unlink file
|
||||
if (is_file($this->base_folder . $input)) {
|
||||
unlink($this->base_folder . $input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
|
||||
@@ -1201,6 +1201,91 @@ final class CoreLibsCombinedArrayHandlerTest extends TestCase
|
||||
'Find next key in array'
|
||||
);
|
||||
}
|
||||
|
||||
public function providerReturnMatchingKeyOnley(): array
|
||||
{
|
||||
return [
|
||||
'limited entries' => [
|
||||
[
|
||||
'a' => 'foo',
|
||||
'b' => 'bar',
|
||||
'c' => 'foobar'
|
||||
],
|
||||
[
|
||||
'a', 'b'
|
||||
],
|
||||
[
|
||||
'a' => 'foo',
|
||||
'b' => 'bar',
|
||||
],
|
||||
],
|
||||
'limited entries, with one wrong key' => [
|
||||
[
|
||||
'a' => 'foo',
|
||||
'b' => 'bar',
|
||||
'c' => 'foobar'
|
||||
],
|
||||
[
|
||||
'a', 'b', 'f'
|
||||
],
|
||||
[
|
||||
'a' => 'foo',
|
||||
'b' => 'bar',
|
||||
],
|
||||
],
|
||||
'wrong keys only' => [
|
||||
[
|
||||
'a' => 'foo',
|
||||
'b' => 'bar',
|
||||
'c' => 'foobar'
|
||||
],
|
||||
[
|
||||
'f', 'f'
|
||||
],
|
||||
[
|
||||
],
|
||||
],
|
||||
'empty keys' => [
|
||||
[
|
||||
'a' => 'foo',
|
||||
'b' => 'bar',
|
||||
'c' => 'foobar'
|
||||
],
|
||||
[],
|
||||
[
|
||||
'a' => 'foo',
|
||||
'b' => 'bar',
|
||||
'c' => 'foobar'
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::arrayReturnMatchingKeyOnly
|
||||
* @dataProvider providerReturnMatchingKeyOnley
|
||||
* @testdox arrayReturnMatchingKeyOnly get only selected key entries from array [$_dataName]
|
||||
*
|
||||
* @param array $input
|
||||
* @param array $key_list
|
||||
* @param array $expected
|
||||
* @return void
|
||||
*/
|
||||
public function testArrayReturnMatchingKeyOnly(
|
||||
array $input,
|
||||
array $key_list,
|
||||
array $expected
|
||||
): void {
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
\CoreLibs\Combined\ArrayHandler::arrayReturnMatchingKeyOnly(
|
||||
$input,
|
||||
$key_list
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
|
||||
1186
test/phpunit/Convert/CoreLibsConvertColorTest.php
Normal file
1186
test/phpunit/Convert/CoreLibsConvertColorTest.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase;
|
||||
/**
|
||||
* Test class for Convert\Colors
|
||||
* @coversDefaultClass \CoreLibs\Convert\Colors
|
||||
* @testdox \CoreLibs\Convert\Colors method tests
|
||||
* @testdox \CoreLibs\Convert\Colors legacy method tests
|
||||
*/
|
||||
final class CoreLibsConvertColorsTest extends TestCase
|
||||
{
|
||||
@@ -21,7 +21,7 @@ final class CoreLibsConvertColorsTest extends TestCase
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rgb2hexColorProvider(): array
|
||||
public function providerRgb2hexColor(): array
|
||||
{
|
||||
return [
|
||||
'color' => [
|
||||
@@ -88,7 +88,7 @@ final class CoreLibsConvertColorsTest extends TestCase
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function hex2rgbColorProvider(): array
|
||||
public function providerHex2rgbColor(): array
|
||||
{
|
||||
return [
|
||||
'color' => [
|
||||
@@ -215,7 +215,7 @@ final class CoreLibsConvertColorsTest extends TestCase
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rgb2hsbColorProvider(): array
|
||||
public function providerRgb2hsbColor(): array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->rgb2hslAndhsbList() as $name => $values) {
|
||||
@@ -234,7 +234,7 @@ final class CoreLibsConvertColorsTest extends TestCase
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function hsb2rgbColorProvider(): array
|
||||
public function providerHsb2rgbColor(): array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->rgb2hslAndhsbList() as $name => $values) {
|
||||
@@ -253,7 +253,7 @@ final class CoreLibsConvertColorsTest extends TestCase
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rgb2hslColorProvider(): array
|
||||
public function providerRgb2hslColor(): array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->rgb2hslAndhsbList() as $name => $values) {
|
||||
@@ -272,7 +272,7 @@ final class CoreLibsConvertColorsTest extends TestCase
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function hsl2rgbColorProvider(): array
|
||||
public function providerHsl2rgbColor(): array
|
||||
{
|
||||
$list = [];
|
||||
foreach ($this->rgb2hslAndhsbList() as $name => $values) {
|
||||
@@ -291,7 +291,7 @@ final class CoreLibsConvertColorsTest extends TestCase
|
||||
* TODO: add cross convert check
|
||||
*
|
||||
* @covers ::rgb2hex
|
||||
* @dataProvider rgb2hexColorProvider
|
||||
* @dataProvider providerRgb2hexColor
|
||||
* @testdox rgb2hex $input_r,$input_g,$input_b will be $expected [$_dataName]
|
||||
*
|
||||
* @param int $input_r
|
||||
@@ -342,7 +342,7 @@ final class CoreLibsConvertColorsTest extends TestCase
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::hex2rgb
|
||||
* @dataProvider hex2rgbColorProvider
|
||||
* @dataProvider providerHex2rgbColor
|
||||
* @testdox hex2rgb $input will be $expected, $expected_str str[,], $expected_str_sep str[$separator] [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
@@ -385,7 +385,7 @@ final class CoreLibsConvertColorsTest extends TestCase
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::rgb2hsb
|
||||
* @dataProvider rgb2hsbColorProvider
|
||||
* @dataProvider providerRgb2hsbColor
|
||||
* @testdox rgb2hsb $input_r,$input_g,$input_b will be $expected [$_dataName]
|
||||
*
|
||||
* @param integer $input_r
|
||||
@@ -409,7 +409,7 @@ final class CoreLibsConvertColorsTest extends TestCase
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::hsb2rgb
|
||||
* @dataProvider hsb2rgbColorProvider
|
||||
* @dataProvider providerHsb2rgbColor
|
||||
* @testdox hsb2rgb $input_h,$input_s,$input_b will be $expected [$_dataName]
|
||||
*
|
||||
* @param float $input_h
|
||||
@@ -434,7 +434,7 @@ final class CoreLibsConvertColorsTest extends TestCase
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::rgb2hsl
|
||||
* @dataProvider rgb2hslColorProvider
|
||||
* @dataProvider providerRgb2hslColor
|
||||
* @testdox rgb2hsl $input_r,$input_g,$input_b will be $expected [$_dataName]
|
||||
*
|
||||
* @param integer $input_r
|
||||
@@ -458,7 +458,7 @@ final class CoreLibsConvertColorsTest extends TestCase
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::hsl2rgb
|
||||
* @dataProvider hsl2rgbColorProvider
|
||||
* @dataProvider providerHsl2rgbColor
|
||||
* @testdox hsl2rgb $input_h,$input_s,$input_l will be $expected [$_dataName]
|
||||
*
|
||||
* @param integer|float $input_h
|
||||
|
||||
@@ -18,7 +18,7 @@ final class CoreLibsConvertMathTest extends TestCase
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function fceilProvider(): array
|
||||
public function providerFceil(): array
|
||||
{
|
||||
return [
|
||||
'5.5 must be 6' => [5.5, 6],
|
||||
@@ -31,7 +31,7 @@ final class CoreLibsConvertMathTest extends TestCase
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::fceil
|
||||
* @dataProvider fceilProvider
|
||||
* @dataProvider providerFceil
|
||||
* @testdox fceil: Input $input must be $expected
|
||||
*
|
||||
* @param float $input
|
||||
@@ -51,7 +51,7 @@ final class CoreLibsConvertMathTest extends TestCase
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function floorProvider(): array
|
||||
public function providerFloor(): array
|
||||
{
|
||||
return [
|
||||
'5123456 with -3 must be 5123000' => [5123456, -3, 5123000],
|
||||
@@ -63,7 +63,7 @@ final class CoreLibsConvertMathTest extends TestCase
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::floorp
|
||||
* @dataProvider floorProvider
|
||||
* @dataProvider providerFloor
|
||||
* @testdox floor: Input $input with cutoff $cutoff must be $expected
|
||||
*
|
||||
* @param int $input
|
||||
@@ -84,7 +84,7 @@ final class CoreLibsConvertMathTest extends TestCase
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function initNumericProvider(): array
|
||||
public function providerInitNumeric(): array
|
||||
{
|
||||
return [
|
||||
'5 must be 5' => [5, 5, 'int'],
|
||||
@@ -98,7 +98,7 @@ final class CoreLibsConvertMathTest extends TestCase
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::initNumeric
|
||||
* @dataProvider initNumericProvider
|
||||
* @dataProvider providerInitNumeric
|
||||
* @testdox initNumeric: Input $info $input must match $expected [$_dataName]
|
||||
*
|
||||
* @param int|float|string $input
|
||||
@@ -113,6 +113,388 @@ final class CoreLibsConvertMathTest extends TestCase
|
||||
\CoreLibs\Convert\Math::initNumeric($input)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
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],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::cbrt
|
||||
* @dataProvider providerCbrt
|
||||
* @testdox initNumeric: Input $input must match $expected [$_dataName]
|
||||
*
|
||||
* @param float|int $number
|
||||
* @param float $expected
|
||||
* @param int $round_to
|
||||
* @return void
|
||||
*/
|
||||
public function testCbrt(float|int $number, float|string $expected, int $round_to): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
round(\CoreLibs\Convert\Math::cbrt($number), $round_to)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerMultiplyMatrices(): array
|
||||
{
|
||||
return [
|
||||
'[3] x [3] => [3x1]' => [
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
[14]
|
||||
],
|
||||
'[3] x [3x1]' => [
|
||||
[1, 2, 3],
|
||||
[[1], [2], [3]],
|
||||
[14]
|
||||
],
|
||||
'[3] x [3x1]' => [
|
||||
[1, 2, 3],
|
||||
[[1], [2], [3]],
|
||||
[14]
|
||||
],
|
||||
'[1x3L] x [3x1]' => [
|
||||
[[1, 2, 3]],
|
||||
[[1], [2], [3]],
|
||||
[14]
|
||||
],
|
||||
'[1x3] x [3x1]' => [
|
||||
[[1], [2], [3]],
|
||||
[[1], [2], [3]],
|
||||
[1, 2, 3]
|
||||
],
|
||||
'[2x3] x [3] => [3x1]' => [
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3]
|
||||
],
|
||||
[1, 2, 3],
|
||||
[
|
||||
14,
|
||||
14
|
||||
]
|
||||
],
|
||||
'[2x3] x [3x1]' => [
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3]
|
||||
],
|
||||
[[1], [2], [3]],
|
||||
[
|
||||
14,
|
||||
14
|
||||
]
|
||||
],
|
||||
'[2x3] x [2x3] => [3x3]' => [
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
],
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
],
|
||||
[
|
||||
[3, 6, 9],
|
||||
[3, 6, 9]
|
||||
]
|
||||
],
|
||||
'[2x3] x [3x3]' => [
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
],
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
[0, 0, 0],
|
||||
],
|
||||
[
|
||||
[3, 6, 9],
|
||||
[3, 6, 9]
|
||||
]
|
||||
],
|
||||
'[2x3] x [3x2]' => [
|
||||
'a' => [
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
],
|
||||
'b' => [
|
||||
[1, 1],
|
||||
[2, 2],
|
||||
[3, 3],
|
||||
],
|
||||
'prod' => [
|
||||
[14, 14],
|
||||
[14, 14],
|
||||
]
|
||||
],
|
||||
'[3x3] x [3] => [1x3]' => [
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
],
|
||||
[1, 2, 3],
|
||||
[
|
||||
14,
|
||||
14,
|
||||
14
|
||||
]
|
||||
],
|
||||
'[3x3] x [2x3] => [3x3]' => [
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
],
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
],
|
||||
[
|
||||
[3, 6, 9],
|
||||
[3, 6, 9],
|
||||
[3, 6, 9],
|
||||
]
|
||||
],
|
||||
'[3x3] x [3x3]' => [
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
],
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
// [0, 0, 0],
|
||||
],
|
||||
[
|
||||
[3, 6, 9],
|
||||
[3, 6, 9],
|
||||
[3, 6, 9],
|
||||
]
|
||||
],
|
||||
'[3] x [3x3]' => [
|
||||
[1, 2, 3],
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
],
|
||||
[
|
||||
[6, 12, 18],
|
||||
]
|
||||
],
|
||||
'[2x3] x [3x3]' => [
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
],
|
||||
[
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
],
|
||||
[
|
||||
[6, 12, 18],
|
||||
[6, 12, 18],
|
||||
]
|
||||
],
|
||||
'inblanaced [2x2,3] x [3x2]' => [
|
||||
'a' => [
|
||||
[1, 2, 3],
|
||||
[4, 5]
|
||||
],
|
||||
'b' => [
|
||||
[6, 7],
|
||||
[8, 9],
|
||||
[10, 11]
|
||||
],
|
||||
'result' => [
|
||||
[52, 58],
|
||||
[64, 73],
|
||||
]
|
||||
],
|
||||
'inblanaced [2x3] x [3x1,2]' => [
|
||||
'a' => [
|
||||
[1, 2, 3],
|
||||
[4, 5, 7]
|
||||
],
|
||||
'b' => [
|
||||
[7, 8],
|
||||
[9, 10],
|
||||
[11]
|
||||
],
|
||||
'result' => [
|
||||
[58, 28],
|
||||
[150, 82],
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::multiplyMatrices
|
||||
* @dataProvider providerMultiplyMatrices
|
||||
* @testdox initNumeric: Input $input_a x $input_b must match $expected [$_dataName]
|
||||
*
|
||||
* @param array $input_a
|
||||
* @param array $input_b
|
||||
* @param array $expected
|
||||
* @return void
|
||||
*/
|
||||
public function testMultiplyMatrices(array $input_a, array $input_b, array $expected): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
\CoreLibs\Convert\Math::multiplyMatrices($input_a, $input_b)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerEqualWithEpsilon(): array
|
||||
{
|
||||
return [
|
||||
'equal' => [
|
||||
'a' => 0.000000000000000222,
|
||||
'b' => 0.000000000000000222,
|
||||
'epsilon' => PHP_FLOAT_EPSILON,
|
||||
'equal' => true,
|
||||
],
|
||||
'almost equal' => [
|
||||
'a' => 0.000000000000000222,
|
||||
'b' => 0.000000000000000232,
|
||||
'epsilon' => PHP_FLOAT_EPSILON,
|
||||
'equal' => true,
|
||||
],
|
||||
'not equal' => [
|
||||
'a' => 0.000000000000000222,
|
||||
'b' => 0.000000000000004222,
|
||||
'epsilon' => PHP_FLOAT_EPSILON,
|
||||
'equal' => false,
|
||||
],
|
||||
'equal, different epsilon' => [
|
||||
'a' => 0.000000000000000222,
|
||||
'b' => 0.000000000000004222,
|
||||
'epsilon' => 0.0001,
|
||||
'equal' => true,
|
||||
],
|
||||
'not equal, different epsilon' => [
|
||||
'a' => 0.0001,
|
||||
'b' => 0.0002,
|
||||
'epsilon' => 0.0001,
|
||||
'equal' => false,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::equalWithEpsilon
|
||||
* @dataProvider providerEqualWithEpsilon
|
||||
* @testdox equalWithEpsilon with $a and $b and Epsilon: $epsilon must be equal: $equal [$_dataName]
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testEqualWithEpsilon(float $a, float $b, float $epsilon, bool $equal): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
$equal,
|
||||
\CoreLibs\Convert\Math::equalWithEpsilon($a, $b, $epsilon)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerCompareWithEpsilon(): array
|
||||
{
|
||||
return [
|
||||
'smaller, true' => [
|
||||
'value' => 0.0001,
|
||||
'compare' => '<',
|
||||
'limit' => 0.0002,
|
||||
'epsilon' => 0.00001,
|
||||
'match' => true,
|
||||
],
|
||||
'smaller, false' => [
|
||||
'value' => 0.0001,
|
||||
'compare' => '<',
|
||||
'limit' => 0.0001,
|
||||
'epsilon' => 0.00001,
|
||||
'match' => false,
|
||||
],
|
||||
'bigger, true' => [
|
||||
'value' => 0.0002,
|
||||
'compare' => '>',
|
||||
'limit' => 0.0001,
|
||||
'epsilon' => 0.00001,
|
||||
'match' => true,
|
||||
],
|
||||
'bigger, false' => [
|
||||
'value' => 0.0001,
|
||||
'compare' => '>',
|
||||
'limit' => 0.0001,
|
||||
'epsilon' => 0.00001,
|
||||
'match' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::compareWithEpsilon
|
||||
* @dataProvider providerCompareWithEpsilon
|
||||
* @testdox compareWithEpsilon $value $compare $limit with $epsilon must match: $match [$_dataName]
|
||||
*
|
||||
* @param float $value
|
||||
* @param string $compare
|
||||
* @param float $limit
|
||||
* @param float $epslion
|
||||
* @param bool $match
|
||||
* @return void
|
||||
*/
|
||||
public function testCompareWithEpsilon(
|
||||
float $value,
|
||||
string $compare,
|
||||
float $limit,
|
||||
float $epsilon,
|
||||
bool $match
|
||||
): void {
|
||||
$this->assertEquals(
|
||||
$match,
|
||||
\CoreLibs\Convert\Math::compareWithEpsilon($value, $compare, $limit, $epsilon)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
|
||||
@@ -33,15 +33,14 @@ final class CoreLibsConvertMimeEncodeTest extends TestCase
|
||||
'The quick brown fox jumps over the lazy sheep that sleeps in the ravine '
|
||||
. 'and has no idea what is going on here',
|
||||
'UTF-8',
|
||||
'The quick brown fox jumps over the lazy sheep that sleeps in the ravine '
|
||||
. 'and has no idea what is going on here'
|
||||
"The quick brown fox jumps over the lazy sheep that sleeps in the ravine and\r\n"
|
||||
. ' has no idea what is going on here'
|
||||
],
|
||||
'standard with special chars UTF-8' => [
|
||||
'This is ümläßtと漢字もカタカナ!!^$%&',
|
||||
'UTF-8',
|
||||
'This is =?UTF-8?B?w7xtbMOkw59044Go5ryi5a2X44KC44Kr44K/44Kr44OK77yBIV4k?='
|
||||
. "\r\n"
|
||||
. ' =?UTF-8?B?JQ==?=&'
|
||||
"This is =?UTF-8?B?w7xtbMOkw59044Go5ryi5a2X44KC44Kr44K/44Kr44OK77yBIV4k?=\r\n"
|
||||
. ' =?UTF-8?B?JSY=?='
|
||||
],
|
||||
'35 chars and space at the end UTF-8' => [
|
||||
'12345678901234567890123456789012345 '
|
||||
@@ -62,9 +61,8 @@ final class CoreLibsConvertMimeEncodeTest extends TestCase
|
||||
. 'is there a space?',
|
||||
'UTF-8',
|
||||
"=?UTF-8?B?44Kr44K/44Kr44OK44Kr44K/44Kr44OK44GL44Gq44Kr44K/44Kr44OK44Kr?=\r\n"
|
||||
. " =?UTF-8?B?44K/44Kr44OK?=\r\n"
|
||||
. " =?UTF-8?B?44GL44Gq44Kr44K/44Kr44OK44Kr44K/44Kr44OK44GL44Gq44Kr44K/44Kr?=\r\n"
|
||||
. " =?UTF-8?B?44OK44Kr44K/?= is there a =?UTF-8?B?c3BhY2U/?="
|
||||
. " =?UTF-8?B?44K/44Kr44OK44GL44Gq44Kr44K/44Kr44OK44Kr44K/44Kr44OK44GL44Gq?=\r\n"
|
||||
. " =?UTF-8?B?44Kr44K/44Kr44OK44Kr44K/IGlzIHRoZXJlIGEgc3BhY2U/?="
|
||||
]
|
||||
];
|
||||
}
|
||||
@@ -85,16 +83,28 @@ final class CoreLibsConvertMimeEncodeTest extends TestCase
|
||||
// print "MIME: -" . $encoded . "-\n";
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$encoded
|
||||
$encoded,
|
||||
"__mbMimeEncode"
|
||||
);
|
||||
$decoded = mb_decode_mimeheader($encoded);
|
||||
// print "INPUT : " . $input . "\n";
|
||||
// print "DECODED: " . $decoded . "\n";
|
||||
// print "ENCODED: " . $encoded . "\n";
|
||||
// print "INPUT : " . $input . " | " . mb_strlen($input) . "\n";
|
||||
// print "DECODED: " . $decoded . " | " . mb_strlen($decoded) . "\n";
|
||||
// $test_enc = mb_encode_mimeheader($input, $encoding);
|
||||
// $test_dec = mb_decode_mimeheader($test_enc);
|
||||
// print "TEST ENC: " . $test_enc . "\n";
|
||||
// back compare decoded
|
||||
$this->assertEquals(
|
||||
$input,
|
||||
$decoded
|
||||
$decoded,
|
||||
"mb_decode_mimeheader"
|
||||
);
|
||||
|
||||
// $this->assertEquals(
|
||||
// $input,
|
||||
// $test_dec,
|
||||
// 'mb_encode_to_decode'
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ use PHPUnit\Framework\TestCase;
|
||||
*/
|
||||
final class CoreLibsConvertStringsTest extends TestCase
|
||||
{
|
||||
private const DATA_FOLDER = __DIR__ . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR;
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
@@ -256,6 +258,126 @@ final class CoreLibsConvertStringsTest extends TestCase
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* provider for testStripMultiplePathSlashes
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function stripMultiplePathSlashesProvider(): array
|
||||
{
|
||||
return [
|
||||
'no slahses' => [
|
||||
'input' => 'string_abc',
|
||||
'expected' => 'string_abc',
|
||||
],
|
||||
'one slash' => [
|
||||
'input' => 'some/foo',
|
||||
'expected' => 'some/foo',
|
||||
],
|
||||
'two slashes' => [
|
||||
'input' => 'some//foo',
|
||||
'expected' => 'some/foo',
|
||||
],
|
||||
'three slashes' => [
|
||||
'input' => 'some///foo',
|
||||
'expected' => 'some/foo',
|
||||
],
|
||||
'slashes in front' => [
|
||||
'input' => '/foo',
|
||||
'expected' => '/foo',
|
||||
],
|
||||
'two slashes in front' => [
|
||||
'input' => '//foo',
|
||||
'expected' => '/foo',
|
||||
],
|
||||
'thee slashes in front' => [
|
||||
'input' => '///foo',
|
||||
'expected' => '/foo',
|
||||
],
|
||||
'slashes in back' => [
|
||||
'input' => 'foo/',
|
||||
'expected' => 'foo/',
|
||||
],
|
||||
'two slashes in back' => [
|
||||
'input' => 'foo//',
|
||||
'expected' => 'foo/',
|
||||
],
|
||||
'thee slashes in back' => [
|
||||
'input' => 'foo///',
|
||||
'expected' => 'foo/',
|
||||
],
|
||||
'multiple slashes' => [
|
||||
'input' => '/foo//bar///string/end_times',
|
||||
'expected' => '/foo/bar/string/end_times',
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* test multiple slashes clean up
|
||||
*
|
||||
* @covers ::stripMultiplePathSlashes
|
||||
* @dataProvider stripMultiplePathSlashesProvider
|
||||
* @testdox stripMultiplePathSlashes $input will be $expected [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $expected
|
||||
* @return void
|
||||
*/
|
||||
public function testStripMultiplePathSlashes(string $input, string $expected): void
|
||||
{
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
\CoreLibs\Convert\Strings::stripMultiplePathSlashes($input)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerStripUTF8BomBytes(): array
|
||||
{
|
||||
return [
|
||||
"utf8-bom" => [
|
||||
"file" => "UTF8BOM.csv",
|
||||
"expect" => "Asset Type,Epic,File Name\n",
|
||||
],
|
||||
"utf8" => [
|
||||
"file" => "UTF8.csv",
|
||||
"expect" => "Asset Type,Epic,File Name\n",
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* test utf8 bom remove
|
||||
*
|
||||
* @covers ::stripUTF8BomBytes
|
||||
* @dataProvider providerStripUTF8BomBytes
|
||||
* @testdox stripUTF8BomBytes $file will be $expected [$_dataName]
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $expected
|
||||
* @return void
|
||||
*/
|
||||
public function testStripUTF8BomBytes(string $file, string $expected): void
|
||||
{
|
||||
// load sample file
|
||||
if (!is_file(self::DATA_FOLDER . $file)) {
|
||||
$this->markTestSkipped('File: ' . $file . ' could not be opened');
|
||||
}
|
||||
$file = file_get_contents(self::DATA_FOLDER . $file);
|
||||
if ($file === false) {
|
||||
$this->markTestSkipped('File: ' . $file . ' could not be read');
|
||||
}
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
\CoreLibs\Convert\Strings::stripUTF8BomBytes($file)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
|
||||
1
test/phpunit/Convert/data/UTF8.csv
Normal file
1
test/phpunit/Convert/data/UTF8.csv
Normal file
@@ -0,0 +1 @@
|
||||
Asset Type,Epic,File Name
|
||||
|
1
test/phpunit/Convert/data/UTF8BOM.csv
Normal file
1
test/phpunit/Convert/data/UTF8BOM.csv
Normal file
@@ -0,0 +1 @@
|
||||
Asset Type,Epic,File Name
|
||||
|
@@ -22,7 +22,6 @@ final class CoreLibsCreateSessionTest extends TestCase
|
||||
public function sessionProvider(): array
|
||||
{
|
||||
// 0: session name as parameter or for GLOBAL value
|
||||
// 1: type p: parameter, g: global, d: php.ini default
|
||||
// 2: mock data as array
|
||||
// checkCliStatus: true/false,
|
||||
// getSessionStatus: PHP_SESSION_DISABLED for abort,
|
||||
@@ -31,13 +30,10 @@ final class CoreLibsCreateSessionTest extends TestCase
|
||||
// checkActiveSession: true/false, [1st call, 2nd call]
|
||||
// getSessionId: string or false
|
||||
// 3: exepcted name (session)]
|
||||
// 4: Exception thrown on error
|
||||
// 5: exception code, null for none
|
||||
// 6: expected error string
|
||||
// 4: auto write close flag
|
||||
return [
|
||||
'session parameter' => [
|
||||
'sessionNameParameter',
|
||||
'p',
|
||||
[
|
||||
'checkCliStatus' => false,
|
||||
'getSessionStatus' => PHP_SESSION_NONE,
|
||||
@@ -47,12 +43,9 @@ final class CoreLibsCreateSessionTest extends TestCase
|
||||
],
|
||||
'sessionNameParameter',
|
||||
null,
|
||||
null,
|
||||
'',
|
||||
],
|
||||
'session globals' => [
|
||||
'sessionNameGlobals',
|
||||
'g',
|
||||
[
|
||||
'checkCliStatus' => false,
|
||||
'getSessionStatus' => PHP_SESSION_NONE,
|
||||
@@ -61,13 +54,12 @@ final class CoreLibsCreateSessionTest extends TestCase
|
||||
'getSessionId' => '1234abcd4567'
|
||||
],
|
||||
'sessionNameGlobals',
|
||||
null,
|
||||
null,
|
||||
'',
|
||||
[
|
||||
'auto_write_close' => false,
|
||||
],
|
||||
'session name default' => [
|
||||
'',
|
||||
'd',
|
||||
],
|
||||
'auto write close' => [
|
||||
'sessionNameAutoWriteClose',
|
||||
[
|
||||
'checkCliStatus' => false,
|
||||
'getSessionStatus' => PHP_SESSION_NONE,
|
||||
@@ -75,109 +67,10 @@ final class CoreLibsCreateSessionTest extends TestCase
|
||||
'checkActiveSession' => [false, true],
|
||||
'getSessionId' => '1234abcd4567'
|
||||
],
|
||||
'',
|
||||
null,
|
||||
null,
|
||||
'',
|
||||
],
|
||||
// error checks
|
||||
// 1: we are in cli
|
||||
'on cli error' => [
|
||||
'',
|
||||
'd',
|
||||
'sessionNameAutoWriteClose',
|
||||
[
|
||||
'checkCliStatus' => true,
|
||||
'getSessionStatus' => PHP_SESSION_NONE,
|
||||
'setSessionName' => true,
|
||||
'checkActiveSession' => [false, true],
|
||||
'getSessionId' => '1234abcd4567'
|
||||
'auto_write_close' => true,
|
||||
],
|
||||
'',
|
||||
'RuntimeException',
|
||||
1,
|
||||
'[SESSION] No sessions in php cli'
|
||||
],
|
||||
// 2: session disabled
|
||||
'session disabled error' => [
|
||||
'',
|
||||
'd',
|
||||
[
|
||||
'checkCliStatus' => false,
|
||||
'getSessionStatus' => PHP_SESSION_DISABLED,
|
||||
'setSessionName' => true,
|
||||
'checkActiveSession' => [false, true],
|
||||
'getSessionId' => '1234abcd4567'
|
||||
],
|
||||
'',
|
||||
'RuntimeException',
|
||||
2,
|
||||
'[SESSION] Sessions are disabled'
|
||||
],
|
||||
// 3: invalid session name: string
|
||||
'invalid name chars error' => [
|
||||
'1invalid$session#;',
|
||||
'p',
|
||||
[
|
||||
'checkCliStatus' => false,
|
||||
'getSessionStatus' => PHP_SESSION_NONE,
|
||||
'setSessionName' => false,
|
||||
'checkActiveSession' => [false, true],
|
||||
'getSessionId' => '1234abcd4567'
|
||||
],
|
||||
'',
|
||||
'UnexpectedValueException',
|
||||
3,
|
||||
'[SESSION] Invalid session name: 1invalid$session#;'
|
||||
],
|
||||
// 3: invalid session name: only numbers
|
||||
'invalid name numbers only error' => [
|
||||
'123',
|
||||
'p',
|
||||
[
|
||||
'checkCliStatus' => false,
|
||||
'getSessionStatus' => PHP_SESSION_NONE,
|
||||
'setSessionName' => false,
|
||||
'checkActiveSession' => [false, true],
|
||||
'getSessionId' => '1234abcd4567'
|
||||
],
|
||||
'',
|
||||
'UnexpectedValueException',
|
||||
3,
|
||||
'[SESSION] Invalid session name: 123'
|
||||
],
|
||||
// 3: invalid session name: invalid name short
|
||||
// 3: invalid session name: too long (128)
|
||||
// 4: failed to start session (2nd false on check active session)
|
||||
'invalid name numbers only error' => [
|
||||
'',
|
||||
'd',
|
||||
[
|
||||
'checkCliStatus' => false,
|
||||
'getSessionStatus' => PHP_SESSION_NONE,
|
||||
'setSessionName' => true,
|
||||
'checkActiveSession' => [false, false],
|
||||
'getSessionId' => '1234abcd4567'
|
||||
],
|
||||
'',
|
||||
'RuntimeException',
|
||||
4,
|
||||
'[SESSION] Failed to activate session'
|
||||
],
|
||||
// 5: get session id return false
|
||||
'invalid name numbers only error' => [
|
||||
'',
|
||||
'd',
|
||||
[
|
||||
'checkCliStatus' => false,
|
||||
'getSessionStatus' => PHP_SESSION_NONE,
|
||||
'setSessionName' => true,
|
||||
'checkActiveSession' => [false, true],
|
||||
'getSessionId' => false
|
||||
],
|
||||
'',
|
||||
'UnexpectedValueException',
|
||||
5,
|
||||
'[SESSION] getSessionId did not return a session id'
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -190,32 +83,24 @@ final class CoreLibsCreateSessionTest extends TestCase
|
||||
* @testdox startSession $input name for $type will be $expected (error: $expected_error) [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $type
|
||||
* @param array<mixed> $mock_data
|
||||
* @param string $expected
|
||||
* @param string|null $exception
|
||||
* @param string $expected_error
|
||||
* @param array<string,mixed> $options
|
||||
* @return void
|
||||
*/
|
||||
public function testStartSession(
|
||||
string $input,
|
||||
string $type,
|
||||
array $mock_data,
|
||||
string $expected,
|
||||
?string $exception,
|
||||
?int $exception_code,
|
||||
string $expected_error
|
||||
?array $options,
|
||||
): void {
|
||||
// override expected
|
||||
if ($type == 'd') {
|
||||
$expected = ini_get('session.name');
|
||||
}
|
||||
/** @var \CoreLibs\Create\Session&MockObject $session_mock */
|
||||
$session_mock = $this->createPartialMock(
|
||||
\CoreLibs\Create\Session::class,
|
||||
[
|
||||
'checkCliStatus', 'getSessionStatus', 'checkActiveSession',
|
||||
'setSessionName', 'startSessionCall', 'getSessionId',
|
||||
'checkCliStatus',
|
||||
'getSessionStatus', 'checkActiveSession',
|
||||
'getSessionId',
|
||||
'getSessionName'
|
||||
]
|
||||
);
|
||||
@@ -234,12 +119,8 @@ final class CoreLibsCreateSessionTest extends TestCase
|
||||
$mock_data['checkActiveSession'][0],
|
||||
$mock_data['checkActiveSession'][1],
|
||||
);
|
||||
// dummy set for session name
|
||||
$session_mock->method('setSessionName')->with($input)->willReturn($mock_data['setSessionName']);
|
||||
// set session name & return bsed on request data
|
||||
$session_mock->method('getSessionName')->willReturn($expected);
|
||||
// will not return anything
|
||||
$session_mock->method('startSessionCall');
|
||||
// in test case only return string
|
||||
// false: will return false
|
||||
$session_mock->method('getSessionId')->willReturn($mock_data['getSessionId']);
|
||||
@@ -247,25 +128,7 @@ final class CoreLibsCreateSessionTest extends TestCase
|
||||
// regex for session id
|
||||
$ression_id_regex = "/^\w+$/";
|
||||
|
||||
if ($exception !== null) {
|
||||
$this->expectException($exception);
|
||||
$this->expectExceptionCode($exception_code);
|
||||
}
|
||||
|
||||
unset($GLOBALS['SET_SESSION_NAME']);
|
||||
$session_id = '';
|
||||
switch ($type) {
|
||||
case 'p':
|
||||
$session_id = $session_mock->startSession($input);
|
||||
break;
|
||||
case 'g':
|
||||
$GLOBALS['SET_SESSION_NAME'] = $input;
|
||||
$session_id = $session_mock->startSession();
|
||||
break;
|
||||
case 'd':
|
||||
$session_id = $session_mock->startSession();
|
||||
break;
|
||||
}
|
||||
$session_id = $session_mock->getSessionId();
|
||||
// asert checks
|
||||
if (!empty($session_id)) {
|
||||
$this->assertMatchesRegularExpression(
|
||||
@@ -284,6 +147,79 @@ final class CoreLibsCreateSessionTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerSessionException(): array
|
||||
{
|
||||
return [
|
||||
'not cli' => [
|
||||
'TEST_EXCEPTION',
|
||||
\RuntimeException::class,
|
||||
1,
|
||||
'/^\[SESSION\] No sessions in php cli$/',
|
||||
],
|
||||
/* 'session disabled ' => [
|
||||
'TEST_EXCEPTION',
|
||||
\RuntimeException::class,
|
||||
2,
|
||||
'/^\[SESSION\] Sessions are disabled/'
|
||||
],
|
||||
'invalid session name' => [
|
||||
'--#as^-292p-',
|
||||
\UnexpectedValueException::class,
|
||||
3,
|
||||
'/^\[SESSION\] Invalid session name: /'
|
||||
],
|
||||
'failed to activate session' => [
|
||||
'TEST_EXCEPTION',
|
||||
\RuntimeException::class,
|
||||
4,
|
||||
'/^\[SESSION\] Failed to activate session/'
|
||||
],
|
||||
'expired session' => [
|
||||
\RuntimeException::class,
|
||||
5,
|
||||
'/^\[SESSION\] Expired session found/'
|
||||
],
|
||||
'not a valid session id returned' => [
|
||||
\UnexpectedValueException::class,
|
||||
6,
|
||||
'/^\[SESSION\] getSessionId did not return a session id/'
|
||||
], */
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* exception checks
|
||||
*
|
||||
* @covers ::initSession
|
||||
* @dataProvider providerSessionException
|
||||
* @testdox create session $session_name with exception $exception ($exception_code) [$_dataName]
|
||||
*
|
||||
* @param string $session_name
|
||||
* @param string $exception
|
||||
* @param int $exception_code
|
||||
* @param string $expected_error
|
||||
* @return void
|
||||
*/
|
||||
public function testSessionException(
|
||||
string $session_name,
|
||||
string $exception,
|
||||
int $exception_code,
|
||||
string $expected_error,
|
||||
): void {
|
||||
//
|
||||
// throws only on new Object creation
|
||||
$this->expectException($exception);
|
||||
$this->expectExceptionCode($exception_code);
|
||||
$this->expectExceptionMessageMatches($expected_error);
|
||||
// cannot set ini after header sent, plus we are on command line there are no headers
|
||||
new \CoreLibs\Create\Session($session_name, ['session_strict' => false]);
|
||||
}
|
||||
|
||||
/**
|
||||
* provider for session name check
|
||||
*
|
||||
@@ -347,109 +283,147 @@ final class CoreLibsCreateSessionTest extends TestCase
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sessionDataProvider(): array
|
||||
public function providerSessionData(): array
|
||||
{
|
||||
return [
|
||||
'test' => [
|
||||
'foo',
|
||||
'bar',
|
||||
'bar',
|
||||
null,
|
||||
],
|
||||
'int key test' => [
|
||||
123,
|
||||
'bar',
|
||||
'bar',
|
||||
\UnexpectedValueException::class
|
||||
],
|
||||
// more complex value tests
|
||||
'array values' => [
|
||||
'array',
|
||||
[1, 2, 3],
|
||||
[1, 2, 3],
|
||||
null,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
// NOTE: with auto start session, we cannot test this in the command line
|
||||
|
||||
/**
|
||||
* method call test
|
||||
*
|
||||
* @covers ::setS
|
||||
* @covers ::getS
|
||||
* @covers ::issetS
|
||||
* @covers ::unsetS
|
||||
* @dataProvider sessionDataProvider
|
||||
* @testdox setS/getS/issetS/unsetS $name with $input is $expected [$_dataName]
|
||||
* @covers ::set
|
||||
* @covers ::get
|
||||
* @covers ::isset
|
||||
* @covers ::unset
|
||||
* @dataProvider providerSessionData
|
||||
* @testdox set/get/isset/unset $name with $input is $expected ($exception) [$_dataName]
|
||||
*
|
||||
* @param string|int $name
|
||||
* @param mixed $input
|
||||
* @param mixed $expected
|
||||
* @param ?mixed $exception
|
||||
* @return void
|
||||
*/
|
||||
public function testMethodSetGet($name, $input, $expected): void
|
||||
public function testMethodSetGet($name, $input, $expected, $exception): void
|
||||
{
|
||||
$session = new \CoreLibs\Create\Session();
|
||||
$session->setS($name, $input);
|
||||
if (\CoreLibs\Get\System::checkCLI()) {
|
||||
$this->markTestSkipped('Cannot run testMethodSetGet in CLI');
|
||||
}
|
||||
$session = new \CoreLibs\Create\Session('TEST_METHOD');
|
||||
if ($expected !== null) {
|
||||
$this->expectException($exception);
|
||||
}
|
||||
$session->set($name, $input);
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$session->getS($name),
|
||||
$session->get($name),
|
||||
'method set assert'
|
||||
);
|
||||
// isset true
|
||||
$this->assertTrue(
|
||||
$session->issetS($name),
|
||||
$session->isset($name),
|
||||
'method isset assert ok'
|
||||
);
|
||||
$session->unsetS($name);
|
||||
$session->unset($name);
|
||||
$this->assertEquals(
|
||||
'',
|
||||
$session->getS($name),
|
||||
$session->get($name),
|
||||
'method unset assert'
|
||||
);
|
||||
// iset false
|
||||
// isset false
|
||||
$this->assertFalse(
|
||||
$session->issetS($name),
|
||||
$session->isset($name),
|
||||
'method isset assert false'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* magic call test
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::__set
|
||||
* @covers ::__get
|
||||
* @covers ::__isset
|
||||
* @covers ::__unset
|
||||
* @dataProvider sessionDataProvider
|
||||
* @testdox __set/__get/__iseet/__unset $name with $input is $expected [$_dataName]
|
||||
* @return array
|
||||
*/
|
||||
public function providerSessionDataMany(): array
|
||||
{
|
||||
return [
|
||||
'valid set' => [
|
||||
[
|
||||
'foo 1' => 'bar 1',
|
||||
'foo 2' => 'bar 1',
|
||||
],
|
||||
[
|
||||
'foo 1' => 'bar 1',
|
||||
'foo 2' => 'bar 1',
|
||||
],
|
||||
null,
|
||||
],
|
||||
'invalid entry' => [
|
||||
[
|
||||
'foo 1' => 'bar 1',
|
||||
123 => 'bar 1',
|
||||
],
|
||||
[
|
||||
'foo 1' => 'bar 1',
|
||||
],
|
||||
\UnexpectedValueException::class
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param string|int $name
|
||||
* @param mixed $input
|
||||
* @param mixed $expected
|
||||
* @covers ::setMany
|
||||
* @covers ::getMany
|
||||
* @dataProvider providerSessionDataMany
|
||||
* @testdox setMany/getMany/unsetMany $set is $expected ($exception) [$_dataName]
|
||||
*
|
||||
* @param array<string|int,mixed> $set
|
||||
* @param array<string,mixed> $expected
|
||||
* @param ?mixed $exception
|
||||
* @return void
|
||||
*/
|
||||
public function testMagicSetGet($name, $input, $expected): void
|
||||
public function testMany($set, $expected, $exception): void
|
||||
{
|
||||
$session = new \CoreLibs\Create\Session();
|
||||
$session->$name = $input;
|
||||
if (\CoreLibs\Get\System::checkCLI()) {
|
||||
$this->markTestSkipped('Cannot run testMethodSetGet in CLI');
|
||||
}
|
||||
$session = new \CoreLibs\Create\Session('TEST_METHOD');
|
||||
if ($expected !== null) {
|
||||
$this->expectException($exception);
|
||||
}
|
||||
$session->setMany($set);
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$session->$name,
|
||||
'magic set assert'
|
||||
$session->getMany(array_keys($set)),
|
||||
'set many failed'
|
||||
);
|
||||
// isset true
|
||||
$this->assertTrue(
|
||||
isset($session->$name),
|
||||
'magic isset assert ok'
|
||||
);
|
||||
unset($session->$name);
|
||||
$session->unsetMany(array_keys($set));
|
||||
$this->assertEquals(
|
||||
'',
|
||||
$session->$name,
|
||||
'magic unset assert'
|
||||
);
|
||||
// isset true
|
||||
$this->assertFalse(
|
||||
isset($session->$name),
|
||||
'magic isset assert false'
|
||||
[],
|
||||
$session->getMany(array_keys($set)),
|
||||
'unset many failed'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -463,27 +437,30 @@ final class CoreLibsCreateSessionTest extends TestCase
|
||||
*/
|
||||
public function testUnsetAll(): void
|
||||
{
|
||||
if (\CoreLibs\Get\System::checkCLI()) {
|
||||
$this->markTestSkipped('Cannot run testUnsetAll in CLI');
|
||||
}
|
||||
$test_values = [
|
||||
'foo' => 'abc',
|
||||
'bar' => '123'
|
||||
];
|
||||
$session = new \CoreLibs\Create\Session();
|
||||
$session = new \CoreLibs\Create\Session('TEST_UNSET');
|
||||
foreach ($test_values as $name => $value) {
|
||||
$session->setS($name, $value);
|
||||
$session->set($name, $value);
|
||||
// confirm set
|
||||
$this->assertEquals(
|
||||
$value,
|
||||
$session->getS($name),
|
||||
$session->get($name),
|
||||
'set assert: ' . $name
|
||||
);
|
||||
}
|
||||
// unset all
|
||||
$session->unsetAllS();
|
||||
$session->clear();
|
||||
// check unset
|
||||
foreach (array_keys($test_values) as $name) {
|
||||
$this->assertEquals(
|
||||
'',
|
||||
$session->getS($name),
|
||||
$session->get($name),
|
||||
'unsert assert: ' . $name
|
||||
);
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ final class CoreLibsCreateUidsTest extends TestCase
|
||||
* must match 7e78fe0d-59b8-4637-af7f-e88d221a7d1e
|
||||
*
|
||||
* @covers ::uuidv4
|
||||
* @covers ::validateUuidv4
|
||||
* @testdox uuidv4 check that return is matching regex [$_dataName]
|
||||
*
|
||||
* @return void
|
||||
@@ -129,13 +130,18 @@ final class CoreLibsCreateUidsTest extends TestCase
|
||||
{
|
||||
$uuid = \CoreLibs\Create\Uids::uuidv4();
|
||||
$this->assertMatchesRegularExpression(
|
||||
'/^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$/',
|
||||
$uuid
|
||||
'/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/',
|
||||
$uuid,
|
||||
'Failed regex check'
|
||||
);
|
||||
$this->assertTrue(
|
||||
\CoreLibs\Create\Uids::validateUuuidv4($uuid),
|
||||
'Failed validate regex method'
|
||||
);
|
||||
$this->assertFalse(
|
||||
\CoreLibs\Create\Uids::validateUuuidv4('not-a-uuidv4'),
|
||||
'Failed wrong uuid validated as true'
|
||||
);
|
||||
// $this->assertStringMatchesFormat(
|
||||
// '%4s%4s-%4s-%4s-%4s-%4s%4s%4s',
|
||||
// $uuid
|
||||
// );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,6 @@ use PHPUnit\Framework\TestCase;
|
||||
* Test class for DB\Extended\ArrayIO
|
||||
* This will only test the PgSQL parts
|
||||
* @coversDefaultClass \CoreLibs\DB\Extended\ArrayIO
|
||||
* @coversDefaultClass \CoreLibs\DB\Extended\ArrayIO
|
||||
* @testdox \CoreLibs\Extended\ArrayIO method tests for extended DB interface
|
||||
*/
|
||||
final class CoreLibsDBExtendedArrayIOTest extends TestCase
|
||||
|
||||
@@ -17,7 +17,7 @@ Table with Primary Key: table_with_primary_key
|
||||
Table without Primary Key: table_without_primary_key
|
||||
|
||||
Table with primary key has additional row:
|
||||
row_primary_key SERIAL PRIMARY KEY,
|
||||
row_primary_key INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
Each table has the following rows
|
||||
row_int INT,
|
||||
row_numeric NUMERIC,
|
||||
@@ -37,8 +37,9 @@ namespace tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use CoreLibs\Logging\Logger\Level;
|
||||
use CoreLibs\Logging;
|
||||
use CoreLibs\DB\Options\Convert;
|
||||
use CoreLibs\DB\Support\ConvertPlaceholder;
|
||||
|
||||
/**
|
||||
* Test class for DB\IO + DB\SQL\PgSQL
|
||||
@@ -117,7 +118,7 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
);
|
||||
}
|
||||
// define basic connection set valid and one invalid
|
||||
self::$log = new \CoreLibs\Logging\Logging([
|
||||
self::$log = new Logging\Logging([
|
||||
// 'log_folder' => __DIR__ . DIRECTORY_SEPARATOR . 'log',
|
||||
'log_folder' => DIRECTORY_SEPARATOR . 'tmp',
|
||||
'log_file_id' => 'CoreLibs-DB-IO-Test',
|
||||
@@ -161,13 +162,9 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
// primary key name is table + '_id'
|
||||
<<<SQL
|
||||
CREATE TABLE table_with_primary_key (
|
||||
table_with_primary_key_id SERIAL PRIMARY KEY,
|
||||
table_with_primary_key_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
$base_table
|
||||
SQL
|
||||
/* "CREATE TABLE table_with_primary_key ("
|
||||
// primary key name is table + '_id'
|
||||
. "table_with_primary_key_id SERIAL PRIMARY KEY, "
|
||||
. $base_table */
|
||||
);
|
||||
$db->dbExec(
|
||||
<<<SQL
|
||||
@@ -570,11 +567,11 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
);
|
||||
$db->dbClose();
|
||||
// second conenction with log set NOT debug
|
||||
$log = new \CoreLibs\Logging\Logging([
|
||||
$log = new Logging\Logging([
|
||||
// 'log_folder' => __DIR__ . DIRECTORY_SEPARATOR . 'log',
|
||||
'log_folder' => DIRECTORY_SEPARATOR . 'tmp',
|
||||
'log_file_id' => 'CoreLibs-DB-IO-Test',
|
||||
'log_level' => \CoreLibs\Logging\Logger\Level::Notice,
|
||||
'log_level' => Logging\Logger\Level::Notice,
|
||||
]);
|
||||
$db = new \CoreLibs\DB\IO(
|
||||
self::$db_config[$connection],
|
||||
@@ -3293,6 +3290,7 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
'query' => 'INSERT INTO table_with_primary_key (row_int, uid) '
|
||||
. 'VALUES ($1, $2) RETURNING table_with_primary_key_id',
|
||||
'returning_id' => true,
|
||||
'placeholder_converted' => [],
|
||||
],
|
||||
],
|
||||
// update
|
||||
@@ -3327,6 +3325,7 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
'query' => 'UPDATE table_with_primary_key SET row_int = $1, '
|
||||
. 'row_varchar = $2 WHERE uid = $3',
|
||||
'returning_id' => false,
|
||||
'placeholder_converted' => [],
|
||||
],
|
||||
],
|
||||
// select
|
||||
@@ -3356,6 +3355,7 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
'count' => 1,
|
||||
'query' => 'SELECT row_int, uid FROM table_with_primary_key WHERE uid = $1',
|
||||
'returning_id' => false,
|
||||
'placeholder_converted' => [],
|
||||
],
|
||||
],
|
||||
// any query but with no parameters
|
||||
@@ -3388,6 +3388,7 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
'count' => 0,
|
||||
'query' => 'SELECT row_int, uid FROM table_with_primary_key',
|
||||
'returning_id' => false,
|
||||
'placeholder_converted' => [],
|
||||
],
|
||||
],
|
||||
// no statement name (25)
|
||||
@@ -3411,6 +3412,7 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
'count' => 0,
|
||||
'query' => '',
|
||||
'returning_id' => false,
|
||||
'placeholder_converted' => [],
|
||||
],
|
||||
],
|
||||
// no query (prepare 11)
|
||||
@@ -3435,6 +3437,7 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
'count' => 0,
|
||||
'query' => '',
|
||||
'returning_id' => false,
|
||||
'placeholder_converted' => [],
|
||||
],
|
||||
],
|
||||
// no db connection (prepare/execute 16)
|
||||
@@ -3464,6 +3467,7 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
'count' => 0,
|
||||
'query' => 'SELECT row_int, uid FROM table_with_primary_key',
|
||||
'returning_id' => false,
|
||||
'placeholder_converted' => [],
|
||||
],
|
||||
],
|
||||
// prepare with different statement name
|
||||
@@ -3489,6 +3493,7 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
'count' => 0,
|
||||
'query' => 'SELECT row_int, uid FROM table_with_primary_key',
|
||||
'returning_id' => false,
|
||||
'placeholder_converted' => [],
|
||||
],
|
||||
],
|
||||
// insert wrong data count compared to needed (execute 23)
|
||||
@@ -3514,10 +3519,12 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
'query' => 'INSERT INTO table_with_primary_key (row_int, uid) VALUES '
|
||||
. '($1, $2) RETURNING table_with_primary_key_id',
|
||||
'returning_id' => true,
|
||||
'placeholder_converted' => [],
|
||||
],
|
||||
],
|
||||
// execute does not return a result (22)
|
||||
// TODO execute does not return a result
|
||||
// TODO prepared statement with placeholder params auto convert
|
||||
];
|
||||
}
|
||||
|
||||
@@ -3662,7 +3669,7 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
}
|
||||
|
||||
// check dbGetPrepareCursorValue
|
||||
foreach (['pk_name', 'count', 'query', 'returning_id'] as $key) {
|
||||
foreach (['pk_name', 'count', 'query', 'returning_id', 'placeholder_converted'] as $key) {
|
||||
$this->assertEquals(
|
||||
$prepare_cursor[$key],
|
||||
$db->dbGetPrepareCursorValue($stm_name, $key),
|
||||
@@ -5031,8 +5038,233 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
$db->dbClose();
|
||||
}
|
||||
|
||||
// query placeholder convert
|
||||
// MARK: QUERY PLACEHOLDERS
|
||||
|
||||
// test query placeholder detection for all possible sets
|
||||
// ::dbPrepare
|
||||
|
||||
/**
|
||||
* placeholder sql
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerDbCountQueryParams(): array
|
||||
{
|
||||
return [
|
||||
'one place holder' => [
|
||||
'query' => 'SELECT row_varchar FROM table_with_primary_key WHERE row_varchar = $1',
|
||||
'count' => 1,
|
||||
'convert' => false,
|
||||
],
|
||||
'one place holder, json call' => [
|
||||
'query' => "SELECT row_varchar FROM table_with_primary_key WHERE row_jsonb->>'data' = $1",
|
||||
'count' => 1,
|
||||
'convert' => false,
|
||||
],
|
||||
'one place holder, <> compare' => [
|
||||
'query' => "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar <> $1",
|
||||
'count' => 1,
|
||||
'convert' => false,
|
||||
],
|
||||
'one place holder, named' => [
|
||||
'query' => "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar <> :row_varchar",
|
||||
'count' => 1,
|
||||
'convert' => true,
|
||||
],
|
||||
'no replacement' => [
|
||||
'query' => "SELECT row_varchar FROM table_with_primary_key WHERE row_varchar = '$1'",
|
||||
'count' => 0,
|
||||
'convert' => false,
|
||||
],
|
||||
'insert' => [
|
||||
'query' => "INSERT INTO table_with_primary_key (row_varchar, row_jsonb, row_int) VALUES ($1, $2, $3)",
|
||||
'count' => 3,
|
||||
'convert' => false,
|
||||
],
|
||||
'update' => [
|
||||
'query' => "UPDATE table_with_primary_key SET row_varchar = $1, row_jsonb = $2, row_int = $3 WHERE row_numeric = $4",
|
||||
'count' => 4,
|
||||
'convert' => false,
|
||||
],
|
||||
'multiple, multline' => [
|
||||
'query' => <<<SQL
|
||||
SELECT
|
||||
row_varchar
|
||||
FROM
|
||||
table_with_primary_key
|
||||
WHERE
|
||||
row_varchar = $1 AND row_int = $2
|
||||
AND row_numeric = ANY($3)
|
||||
SQL,
|
||||
'count' => 3,
|
||||
'convert' => false,
|
||||
],
|
||||
'two digit numbers' => [
|
||||
'query' => <<<SQL
|
||||
INSERT INTO table_with_primary_key (
|
||||
row_int, row_numeric, row_varchar, row_varchar_literal, row_json,
|
||||
row_jsonb, row_bytea, row_timestamp, row_date, row_interval
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5,
|
||||
$6, $7, $8, $9, $10
|
||||
)
|
||||
SQL,
|
||||
'count' => 10,
|
||||
'convert' => false,
|
||||
],
|
||||
'things in brackets' => [
|
||||
'query' => <<<SQL
|
||||
SELECT row_varchar
|
||||
FROM table_with_primary_key
|
||||
WHERE
|
||||
row_varchar = $1 AND
|
||||
(row_int = ANY($2) OR row_int = $3)
|
||||
AND row_varchar_literal = $4
|
||||
SQL,
|
||||
'count' => 4,
|
||||
'convert' => false,
|
||||
],
|
||||
'number compare' => [
|
||||
'query' => <<<SQL
|
||||
SELECT row_varchar
|
||||
FROM table_with_primary_key
|
||||
WHERE
|
||||
row_int >= $1 OR row_int <= $2 OR
|
||||
row_int > $3 OR row_int < $4
|
||||
OR row_int = $5 OR row_int <> $6
|
||||
SQL,
|
||||
'count' => 6,
|
||||
'convert' => false,
|
||||
],
|
||||
'comments in insert' => [
|
||||
'query' => <<<SQL
|
||||
INSERT INTO table_with_primary_key (
|
||||
row_int, row_numeric, row_varchar, row_varchar_literal
|
||||
) VALUES (
|
||||
-- comment 1 かな
|
||||
$1, $2,
|
||||
-- comment 2 -
|
||||
$3
|
||||
-- comment 3
|
||||
, $4
|
||||
)
|
||||
SQL,
|
||||
'count' => 4,
|
||||
'convert' => false
|
||||
],
|
||||
'comment in update' => [
|
||||
'query' => <<<SQL
|
||||
UPDATE table_with_primary_key SET
|
||||
row_int =
|
||||
-- COMMENT 1
|
||||
$1,
|
||||
row_numeric =
|
||||
$2 -- COMMENT 2
|
||||
,
|
||||
row_varchar -- COMMENT 3
|
||||
= $3
|
||||
WHERE
|
||||
row_varchar = $4
|
||||
SQL,
|
||||
'count' => 4,
|
||||
'convert' => false,
|
||||
],
|
||||
// Note some are not set
|
||||
'a complete set of possible' => [
|
||||
'query' => <<<SQL
|
||||
UPDATE table_with_primary_key SET
|
||||
-- ROW
|
||||
row_varchar = $1
|
||||
WHERE
|
||||
row_varchar = ANY($2) AND row_varchar <> $3
|
||||
AND row_varchar > $4 AND row_varchar < $5
|
||||
AND row_varchar >= $6 AND row_varchar <=$7
|
||||
AND row_jsonb->'a' = $8 AND row_jsonb->>$9 = 'a'
|
||||
AND row_jsonb<@$10 AND row_jsonb@>$11
|
||||
AND row_varchar ^@ $12
|
||||
SQL,
|
||||
'count' => 12,
|
||||
'convert' => false,
|
||||
],
|
||||
// all the same
|
||||
'all the same numbered' => [
|
||||
'query' => <<<SQL
|
||||
UPDATE table_with_primary_key SET
|
||||
row_int = $1::INT, row_numeric = $1::NUMERIC, row_varchar = $1
|
||||
WHERE
|
||||
row_varchar = $1
|
||||
SQL,
|
||||
'count' => 1,
|
||||
'convert' => false,
|
||||
],
|
||||
'update with case' => [
|
||||
'query' => <<<SQL
|
||||
UPDATE table_with_primary_key SET
|
||||
row_int = $1::INT,
|
||||
row_varchar = CASE WHEN row_int = 1 THEN $2 ELSE 'bar'::VARCHAR END
|
||||
WHERE
|
||||
row_varchar = $3
|
||||
SQL,
|
||||
'count' => 3,
|
||||
'convert' => false,
|
||||
],
|
||||
'select with case' => [
|
||||
'query' => <<<SQL
|
||||
SELECT row_int
|
||||
FROM table_with_primary_key
|
||||
WHERE
|
||||
row_varchar = CASE WHEN row_int = 1 THEN $1 ELSE $2 END
|
||||
SQL,
|
||||
'count' => 2,
|
||||
'convert' => false,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Placeholder check and convert tests
|
||||
*
|
||||
* @covers ::dbPrepare
|
||||
* @covers ::__dbCountQueryParams
|
||||
* @onvers ::convertPlaceholderInQuery
|
||||
* @dataProvider providerDbCountQueryParams
|
||||
* @testdox Query replacement count test [$_dataName]
|
||||
*
|
||||
* @param string $query
|
||||
* @param int $count
|
||||
* @return void
|
||||
*/
|
||||
public function testDbCountQueryParams(string $query, int $count, bool $convert): void
|
||||
{
|
||||
$db = new \CoreLibs\DB\IO(
|
||||
self::$db_config['valid'],
|
||||
self::$log
|
||||
);
|
||||
$id = sha1($query);
|
||||
$db->dbSetConvertPlaceholder($convert);
|
||||
$db->dbPrepare($id, $query);
|
||||
// print "\n**\n";
|
||||
// print "PCount: " . $db->dbGetPrepareCursorValue($id, 'count') . "\n";
|
||||
// print "\n**\n";
|
||||
$this->assertEquals(
|
||||
$count,
|
||||
$db->dbGetPrepareCursorValue($id, 'count'),
|
||||
'DB count params'
|
||||
);
|
||||
$placeholder = ConvertPlaceholder::convertPlaceholderInQuery($query, null, 'pg');
|
||||
// print "RES: " . print_r($placeholder, true) . "\n";
|
||||
$this->assertEquals(
|
||||
$count,
|
||||
$placeholder['needed'],
|
||||
'convert params'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* query placeholder convert
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function queryPlaceholderReplaceProvider(): array
|
||||
{
|
||||
// WHERE row_varchar = $1
|
||||
@@ -5076,7 +5308,9 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
WHERE row_varchar = $1
|
||||
SQL,
|
||||
'expected_params' => ['string a'],
|
||||
]
|
||||
],
|
||||
// TODO: test with multiple entries
|
||||
// TODO: test with same entry ($1, $1, :var, :var)
|
||||
];
|
||||
}
|
||||
|
||||
@@ -5178,6 +5412,8 @@ final class CoreLibsDBIOTest extends TestCase
|
||||
// - data debug
|
||||
// dbDumpData
|
||||
|
||||
// MARK: ASYNC
|
||||
|
||||
// ASYNC at the end because it has 1s timeout
|
||||
// - asynchronous executions
|
||||
// dbExecAsync, dbCheckAsync
|
||||
|
||||
@@ -568,6 +568,9 @@ final class CoreLibsDebugSupportTest extends TestCase
|
||||
'assert expected 12'
|
||||
);
|
||||
break;
|
||||
default:
|
||||
$this->assertTrue(true, 'Default fallback as true');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -216,6 +216,29 @@ final class CoreLibsGetSystemTest extends TestCase
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::getIpAddresses
|
||||
* @testdox getIpAddresses check
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testGetIpAddresses()
|
||||
{
|
||||
// response must have "REMOTE_ADDR" entry, others are optional
|
||||
// NOTE: we have no IP addresses on command line
|
||||
$this->assertTrue(
|
||||
true,
|
||||
"We do not have REMOTE_ADDR on command line"
|
||||
);
|
||||
// $this->assertContains(
|
||||
// 'REMOTE_ADDR',
|
||||
// array_keys(\CoreLibs\Get\System::getIpAddresses()),
|
||||
// 'failed REMOTE_ADDR assert'
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
|
||||
@@ -21,341 +21,6 @@ final class CoreLibsLanguageGetLocaleTest extends TestCase
|
||||
. 'includes' . DIRECTORY_SEPARATOR
|
||||
. 'locale' . DIRECTORY_SEPARATOR;
|
||||
|
||||
/**
|
||||
* set all constant variables that must be set before call
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
// default web page encoding setting
|
||||
/* if (!defined('DEFAULT_ENCODING')) {
|
||||
define('DEFAULT_ENCODING', 'UTF-8');
|
||||
}
|
||||
if (!defined('DEFAULT_LOCALE')) {
|
||||
// default lang + encoding
|
||||
define('DEFAULT_LOCALE', 'en_US.UTF-8');
|
||||
}
|
||||
// site
|
||||
if (!defined('SITE_ENCODING')) {
|
||||
define('SITE_ENCODING', DEFAULT_ENCODING);
|
||||
}
|
||||
if (!defined('SITE_LOCALE')) {
|
||||
define('SITE_LOCALE', DEFAULT_LOCALE);
|
||||
} */
|
||||
// just set
|
||||
/* if (!defined('BASE')) {
|
||||
define('BASE', str_replace('/configs', '', __DIR__) . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
if (!defined('INCLUDES')) {
|
||||
define('INCLUDES', 'includes' . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
if (!defined('LANG')) {
|
||||
define('LANG', 'lang' . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
if (!defined('LOCALE')) {
|
||||
define('LOCALE', 'locale' . DIRECTORY_SEPARATOR);
|
||||
}
|
||||
if (!defined('CONTENT_PATH')) {
|
||||
define('CONTENT_PATH', 'frontend' . DIRECTORY_SEPARATOR);
|
||||
} */
|
||||
// array session
|
||||
$_SESSION = [];
|
||||
global $_SESSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* all the test data
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
/* public function setLocaleProvider(): array
|
||||
{
|
||||
return [
|
||||
// 0: locale
|
||||
// 1: domain
|
||||
// 2: encoding
|
||||
// 3: path
|
||||
// 4: SESSION: DEFAULT_LOCALE
|
||||
// 5: SESSION: DEFAULT_CHARSET
|
||||
// 6: expected array
|
||||
// 7: deprecation message
|
||||
'no params, all default constants' => [
|
||||
// lang, domain, encoding, path
|
||||
null, null, null, null,
|
||||
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
|
||||
null, null,
|
||||
// return array
|
||||
[
|
||||
'locale' => 'en_US.UTF-8',
|
||||
'lang' => 'en_US',
|
||||
'domain' => 'frontend',
|
||||
'encoding' => 'UTF-8',
|
||||
'path' => "/^\/(.*\/)?includes\/locale\/$/",
|
||||
],
|
||||
'setLocale: Unset $locale or unset SESSION locale is deprecated',
|
||||
],
|
||||
'no params, session charset and lang' => [
|
||||
// lang, domain, encoding, path
|
||||
null, null, null, null,
|
||||
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
|
||||
'ja_JP', 'UTF-8',
|
||||
// return array
|
||||
[
|
||||
'locale' => 'ja_JP',
|
||||
'lang' => 'ja_JP',
|
||||
'domain' => 'frontend',
|
||||
'encoding' => 'UTF-8',
|
||||
'path' => "/^\/(.*\/)?includes\/locale\/$/",
|
||||
],
|
||||
'setLocale: Unset $domain is deprecated'
|
||||
],
|
||||
'no params, session charset and lang short' => [
|
||||
// lang, domain, encoding, path
|
||||
null, null, null, null,
|
||||
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
|
||||
'ja', 'UTF-8',
|
||||
// return array
|
||||
[
|
||||
'locale' => 'ja',
|
||||
'lang' => 'ja',
|
||||
'domain' => 'frontend',
|
||||
'encoding' => 'UTF-8',
|
||||
'path' => "/^\/(.*\/)?includes\/locale\/$/",
|
||||
],
|
||||
'setLocale: Unset $domain is deprecated',
|
||||
],
|
||||
// param lang (no sessions)
|
||||
'locale param only, no sessions' => [
|
||||
// lang, domain, encoding, path
|
||||
'ja.UTF-8', null, null, null,
|
||||
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
|
||||
null, null,
|
||||
// return array
|
||||
[
|
||||
'locale' => 'ja.UTF-8',
|
||||
'lang' => 'ja',
|
||||
'domain' => 'frontend',
|
||||
'encoding' => 'UTF-8',
|
||||
'path' => "/^\/(.*\/)?includes\/locale\/$/",
|
||||
],
|
||||
'setLocale: Unset $domain is deprecated',
|
||||
],
|
||||
// different locale setting
|
||||
'locale complex param only, no sessions' => [
|
||||
// lang, domain, encoding, path
|
||||
'ja_JP.SJIS', null, null, null,
|
||||
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
|
||||
null, null,
|
||||
// return array
|
||||
[
|
||||
'locale' => 'ja_JP.SJIS',
|
||||
'lang' => 'ja_JP',
|
||||
'domain' => 'frontend',
|
||||
'encoding' => 'SJIS',
|
||||
'path' => "/^\/(.*\/)?includes\/locale\/$/",
|
||||
],
|
||||
'setLocale: Unset $domain is deprecated',
|
||||
],
|
||||
// param lang and domain (no override)
|
||||
'locale, domain params, no sessions' => [
|
||||
// lang, domain, encoding, path
|
||||
'ja.UTF-8', 'admin', null, null,
|
||||
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
|
||||
null, null,
|
||||
// return array
|
||||
[
|
||||
'locale' => 'ja.UTF-8',
|
||||
'lang' => 'ja',
|
||||
'domain' => 'admin',
|
||||
'encoding' => 'UTF-8',
|
||||
'path' => "/^\/(.*\/)?includes\/locale\/$/",
|
||||
],
|
||||
'setLocale: Unset $path is deprecated',
|
||||
],
|
||||
// param lang and domain (no override)
|
||||
'locale, domain, encoding params, no sessions' => [
|
||||
// lang, domain, encoding, path
|
||||
'ja.UTF-8', 'admin', 'UTF-8', null,
|
||||
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
|
||||
null, null,
|
||||
// return array
|
||||
[
|
||||
'locale' => 'ja.UTF-8',
|
||||
'lang' => 'ja',
|
||||
'domain' => 'admin',
|
||||
'encoding' => 'UTF-8',
|
||||
'path' => "/^\/(.*\/)?includes\/locale\/$/",
|
||||
],
|
||||
'setLocale: Unset $path is deprecated'
|
||||
],
|
||||
// lang, domain, path (no override)
|
||||
'locale, domain and path, no sessions' => [
|
||||
// lang, domain, encoding, path
|
||||
'ja.UTF-8', 'admin', '', __DIR__ . '/locale_other/',
|
||||
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
|
||||
null, null,
|
||||
// return array
|
||||
[
|
||||
'locale' => 'ja.UTF-8',
|
||||
'lang' => 'ja',
|
||||
'domain' => 'admin',
|
||||
'encoding' => 'UTF-8',
|
||||
'path' => "/^\/(.*\/)?locale_other\/$/",
|
||||
],
|
||||
null
|
||||
],
|
||||
// all params set (no override)
|
||||
'all parameter, no sessions' => [
|
||||
// lang, domain, encoding, path
|
||||
'ja', 'admin', 'UTF-8', __DIR__ . '/locale_other/',
|
||||
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
|
||||
null, null,
|
||||
// return array
|
||||
[
|
||||
'locale' => 'ja',
|
||||
'lang' => 'ja',
|
||||
'domain' => 'admin',
|
||||
'encoding' => 'UTF-8',
|
||||
'path' => "/^\/(.*\/)?locale_other\/$/",
|
||||
],
|
||||
null
|
||||
],
|
||||
// param lang and domain (no override)
|
||||
'long locale, domain, encoding params, no sessions' => [
|
||||
// lang, domain, encoding, path
|
||||
'de_CH.UTF-8@euro', 'admin', 'UTF-8', null,
|
||||
// SESSION DEFAULT_LOCALE, SESSION: DEFAULT_CHARSET
|
||||
null, null,
|
||||
// return array
|
||||
[
|
||||
'locale' => 'de_CH.UTF-8@euro',
|
||||
'lang' => 'de_CH',
|
||||
'domain' => 'admin',
|
||||
'encoding' => 'UTF-8',
|
||||
'path' => "/^\/(.*\/)?includes\/locale\/$/",
|
||||
],
|
||||
'setLocale: Unset $path is deprecated',
|
||||
],
|
||||
// TODO invalid params (bad path) (no override)
|
||||
// TODO param calls, but with override set
|
||||
];
|
||||
} */
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::setLocale
|
||||
* @dataProvider setLocaleProvider
|
||||
* @testdox lang settings lang $language, domain $domain, encoding $encoding, path $path; session lang: $SESSION_DEFAULT_LOCALE, session char: $SESSION_DEFAULT_CHARSET [$_dataName]
|
||||
*
|
||||
* @param string|null $language
|
||||
* @param string|null $domain
|
||||
* @param string|null $encoding
|
||||
* @param string|null $path
|
||||
* @param string|null $SESSION_DEFAULT_LOCALE
|
||||
* @param string|null $SESSION_DEFAULT_CHARSET
|
||||
* @param array<mixed> $expected
|
||||
* @param string|null $deprecation_message
|
||||
* @return void
|
||||
*/
|
||||
/* public function testsetLocale(
|
||||
?string $language,
|
||||
?string $domain,
|
||||
?string $encoding,
|
||||
?string $path,
|
||||
?string $SESSION_DEFAULT_LOCALE,
|
||||
?string $SESSION_DEFAULT_CHARSET,
|
||||
array $expected,
|
||||
?string $deprecation_message
|
||||
): void {
|
||||
$return_lang_settings = [];
|
||||
global $_SESSION;
|
||||
// set override
|
||||
if ($SESSION_DEFAULT_LOCALE !== null) {
|
||||
$_SESSION['DEFAULT_LOCALE'] = $SESSION_DEFAULT_LOCALE;
|
||||
}
|
||||
if ($SESSION_DEFAULT_CHARSET !== null) {
|
||||
$_SESSION['DEFAULT_CHARSET'] = $SESSION_DEFAULT_CHARSET;
|
||||
}
|
||||
if ($deprecation_message !== null) {
|
||||
set_error_handler(
|
||||
static function (int $errno, string $errstr): never {
|
||||
throw new \Exception($errstr, $errno);
|
||||
},
|
||||
E_USER_DEPRECATED
|
||||
);
|
||||
// catch this with the message
|
||||
$this->expectExceptionMessage($deprecation_message);
|
||||
}
|
||||
// function call
|
||||
if (
|
||||
$language === null && $domain === null &&
|
||||
$encoding === null && $path === null
|
||||
) {
|
||||
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale();
|
||||
} elseif (
|
||||
$language !== null && $domain === null &&
|
||||
$encoding === null && $path === null
|
||||
) {
|
||||
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale(
|
||||
$language
|
||||
);
|
||||
} elseif (
|
||||
$language !== null && $domain !== null &&
|
||||
$encoding === null && $path === null
|
||||
) {
|
||||
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale(
|
||||
$language,
|
||||
$domain
|
||||
);
|
||||
} elseif (
|
||||
$language !== null && $domain !== null &&
|
||||
$encoding !== null && $path === null
|
||||
) {
|
||||
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale(
|
||||
$language,
|
||||
$domain,
|
||||
$encoding
|
||||
);
|
||||
} else {
|
||||
$return_lang_settings = \CoreLibs\Language\GetLocale::setLocale(
|
||||
$language,
|
||||
$domain,
|
||||
$encoding,
|
||||
$path
|
||||
);
|
||||
}
|
||||
restore_error_handler();
|
||||
// print "RETURN: " . print_r($return_lang_settings, true) . "\n";
|
||||
|
||||
foreach (
|
||||
[
|
||||
'locale', 'lang', 'domain', 'encoding', 'path'
|
||||
] as $key
|
||||
) {
|
||||
$value = $expected[$key];
|
||||
if (strpos($value, "/") === 0) {
|
||||
// this is regex
|
||||
$this->assertMatchesRegularExpression(
|
||||
$value,
|
||||
$return_lang_settings[$key],
|
||||
'assert regex failed for ' . $key
|
||||
);
|
||||
} else {
|
||||
// assert equal
|
||||
$this->assertEquals(
|
||||
$value,
|
||||
$return_lang_settings[$key],
|
||||
'assert equal failed for ' . $key
|
||||
);
|
||||
}
|
||||
}
|
||||
// unset all vars
|
||||
$_SESSION = [];
|
||||
unset($GLOBALS['OVERRIDE_LANG']);
|
||||
} */
|
||||
|
||||
/**
|
||||
* all the test data
|
||||
*
|
||||
|
||||
2
test/phpunit/Language/locale_other/.gitignore
vendored
Normal file
2
test/phpunit/Language/locale_other/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -10,7 +10,7 @@ use CoreLibs\Logging\Logger\Level;
|
||||
/**
|
||||
* Test class for Logging
|
||||
* @coversDefaultClass \CoreLibs\Logging\ErrorMessages
|
||||
* @testdox \CoreLibs\Logging\ErrorMEssages method tests
|
||||
* @testdox \CoreLibs\Logging\ErrorMessages method tests
|
||||
*/
|
||||
final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
{
|
||||
@@ -39,6 +39,11 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
'str' => 'OK',
|
||||
'expected' => 'ok',
|
||||
],
|
||||
'success' => [
|
||||
'level' => 'success',
|
||||
'str' => 'SUCCESS',
|
||||
'expected' => 'success',
|
||||
],
|
||||
'info' => [
|
||||
'level' => 'info',
|
||||
'str' => 'INFO',
|
||||
@@ -225,6 +230,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
'str' => 'ERROR MESSAGE',
|
||||
'message' => null,
|
||||
'log_error' => null,
|
||||
'log_warning' => null,
|
||||
'expected' => '<ERROR> ERROR MESSAGE',
|
||||
],
|
||||
'error, logged' => [
|
||||
@@ -233,6 +239,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
'str' => 'ERROR MESSAGE',
|
||||
'message' => null,
|
||||
'log_error' => true,
|
||||
'log_warning' => null,
|
||||
'expected' => '<ERROR> ERROR MESSAGE',
|
||||
],
|
||||
'error, logged, message' => [
|
||||
@@ -241,14 +248,43 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
'str' => 'ERROR MESSAGE',
|
||||
'message' => 'OTHER ERROR MESSAGE',
|
||||
'log_error' => true,
|
||||
'log_warning' => null,
|
||||
'expected' => '<ERROR> OTHER ERROR MESSAGE',
|
||||
],
|
||||
'warn, not logged' => [
|
||||
'id' => '300',
|
||||
'level' => 'warn',
|
||||
'str' => 'WARNING MESSAGE',
|
||||
'message' => null,
|
||||
'log_error' => null,
|
||||
'log_warning' => null,
|
||||
'expected' => '<WARNING> WARNING MESSAGE',
|
||||
],
|
||||
'warn, logged' => [
|
||||
'id' => '300',
|
||||
'level' => 'warn',
|
||||
'str' => 'WARNING MESSAGE',
|
||||
'message' => null,
|
||||
'log_error' => null,
|
||||
'log_warning' => true,
|
||||
'expected' => '<WARNING> WARNING MESSAGE',
|
||||
],
|
||||
'warn, logged, message' => [
|
||||
'id' => '300',
|
||||
'level' => 'warn',
|
||||
'str' => 'WARNING MESSAGE',
|
||||
'message' => 'OTHER WARNING MESSAGE',
|
||||
'log_error' => null,
|
||||
'log_warning' => true,
|
||||
'expected' => '<WARNING> OTHER WARNING MESSAGE',
|
||||
],
|
||||
'notice' => [
|
||||
'id' => '100',
|
||||
'level' => 'notice',
|
||||
'str' => 'NOTICE MESSAGE',
|
||||
'message' => null,
|
||||
'log_error' => null,
|
||||
'log_warning' => null,
|
||||
'expected' => '<NOTICE> NOTICE MESSAGE',
|
||||
],
|
||||
'notice, message' => [
|
||||
@@ -257,6 +293,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
'str' => 'NOTICE MESSAGE',
|
||||
'message' => 'OTHER NOTICE MESSAGE',
|
||||
'log_error' => null,
|
||||
'log_warning' => null,
|
||||
'expected' => '<NOTICE> OTHER NOTICE MESSAGE',
|
||||
],
|
||||
'crash' => [
|
||||
@@ -265,6 +302,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
'str' => 'CRASH MESSAGE',
|
||||
'message' => null,
|
||||
'log_error' => null,
|
||||
'log_warning' => null,
|
||||
'expected' => '<ALERT> CRASH MESSAGE',
|
||||
],
|
||||
'crash, message' => [
|
||||
@@ -273,6 +311,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
'str' => 'CRASH MESSAGE',
|
||||
'message' => 'OTHER CRASH MESSAGE',
|
||||
'log_error' => null,
|
||||
'log_warning' => null,
|
||||
'expected' => '<ALERT> OTHER CRASH MESSAGE',
|
||||
],
|
||||
'abort' => [
|
||||
@@ -281,6 +320,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
'str' => 'ABORT MESSAGE',
|
||||
'message' => null,
|
||||
'log_error' => null,
|
||||
'log_warning' => null,
|
||||
'expected' => '<CRITICAL> ABORT MESSAGE',
|
||||
],
|
||||
'abort, message' => [
|
||||
@@ -289,6 +329,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
'str' => 'ABORT MESSAGE',
|
||||
'message' => 'OTHER ABORT MESSAGE',
|
||||
'log_error' => null,
|
||||
'log_warning' => null,
|
||||
'expected' => '<CRITICAL> OTHER ABORT MESSAGE',
|
||||
],
|
||||
'unknown' => [
|
||||
@@ -297,6 +338,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
'str' => 'WRONG LEVEL MESSAGE',
|
||||
'message' => null,
|
||||
'log_error' => null,
|
||||
'log_warning' => null,
|
||||
'expected' => '<EMERGENCY> WRONG LEVEL MESSAGE',
|
||||
],
|
||||
'unknown, message' => [
|
||||
@@ -305,6 +347,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
'str' => 'WRONG LEVEL MESSAGE',
|
||||
'message' => 'OTHER WRONG LEVEL MESSAGE',
|
||||
'log_error' => null,
|
||||
'log_warning' => null,
|
||||
'expected' => '<EMERGENCY> OTHER WRONG LEVEL MESSAGE',
|
||||
],
|
||||
];
|
||||
@@ -321,6 +364,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
* @param string $str
|
||||
* @param string|null $message
|
||||
* @param bool|null $log_error
|
||||
* @param bool|null $log_warning
|
||||
* @param string $expected
|
||||
* @return void
|
||||
*/
|
||||
@@ -330,6 +374,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
string $str,
|
||||
?string $message,
|
||||
?bool $log_error,
|
||||
?bool $log_warning,
|
||||
string $expected
|
||||
): void {
|
||||
$log = new \CoreLibs\Logging\Logging([
|
||||
@@ -344,7 +389,8 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
$level,
|
||||
$str,
|
||||
message: $message,
|
||||
log_error: $log_error
|
||||
log_error: $log_error,
|
||||
log_warning: $log_warning
|
||||
);
|
||||
$file_content = '';
|
||||
if (is_file($log->getLogFolder() . $log->getLogFile())) {
|
||||
@@ -358,6 +404,11 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
$expected,
|
||||
$file_content
|
||||
);
|
||||
} elseif ($level == 'warn' && ($log_warning === null || $log_warning === false)) {
|
||||
$this->assertStringNotContainsString(
|
||||
$expected,
|
||||
$file_content
|
||||
);
|
||||
} else {
|
||||
$this->assertStringContainsString(
|
||||
$expected,
|
||||
@@ -377,6 +428,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
* @param string $str
|
||||
* @param string|null $message
|
||||
* @param bool|null $log_error
|
||||
* @param bool|null $log_warning
|
||||
* @param string $expected
|
||||
* @return void
|
||||
*/
|
||||
@@ -386,6 +438,7 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
string $str,
|
||||
?string $message,
|
||||
?bool $log_error,
|
||||
?bool $log_warning,
|
||||
string $expected
|
||||
): void {
|
||||
$log = new \CoreLibs\Logging\Logging([
|
||||
@@ -400,7 +453,8 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
$level,
|
||||
$str,
|
||||
message: $message,
|
||||
log_error: $log_error
|
||||
log_error: $log_error,
|
||||
log_warning: $log_warning
|
||||
);
|
||||
$file_content = '';
|
||||
if (is_file($log->getLogFolder() . $log->getLogFile())) {
|
||||
@@ -414,6 +468,11 @@ final class CoreLibsLoggingErrorMessagesTest extends TestCase
|
||||
$expected,
|
||||
$file_content
|
||||
);
|
||||
} elseif ($level == 'warn' && $log_warning === false) {
|
||||
$this->assertStringNotContainsString(
|
||||
$expected,
|
||||
$file_content
|
||||
);
|
||||
} else {
|
||||
$this->assertStringContainsString(
|
||||
$expected,
|
||||
|
||||
@@ -0,0 +1,838 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use CoreLibs\Security\CreateKey;
|
||||
use CoreLibs\Security\AsymmetricAnonymousEncryption;
|
||||
|
||||
/**
|
||||
* Test class for Security\AsymmetricAnonymousEncryption and Security\CreateKey
|
||||
* @coversDefaultClass \CoreLibs\Security\AsymmetricAnonymousEncryption
|
||||
* @testdox \CoreLibs\Security\AsymmetricAnonymousEncryption method tests
|
||||
*/
|
||||
final class CoreLibsSecurityAsymmetricAnonymousEncryptionTest extends TestCase
|
||||
{
|
||||
// MARK: key set and compare
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::getKeyPair
|
||||
* @covers ::compareKeyPair
|
||||
* @covers ::getPublicKey
|
||||
* @covers ::comparePublicKey
|
||||
* @testdox Check if init class set key pair matches to created key pair and public key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testKeyPairInitGetCompare(): void
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
$crypt = new AsymmetricAnonymousEncryption($key_pair);
|
||||
$this->assertTrue(
|
||||
$crypt->compareKeyPair($key_pair),
|
||||
'set key pair not equal to original key pair'
|
||||
);
|
||||
$this->assertTrue(
|
||||
$crypt->comparePublicKey($public_key),
|
||||
'automatic set public key not equal to original public key'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$key_pair,
|
||||
$crypt->getKeyPair(),
|
||||
'set key pair returned not equal to original key pair'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$public_key,
|
||||
$crypt->getPublicKey(),
|
||||
'automatic set public key returned not equal to original public key'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::getKeyPair
|
||||
* @covers ::compareKeyPair
|
||||
* @covers ::getPublicKey
|
||||
* @covers ::comparePublicKey
|
||||
* @testdox Check if init class set key pair and public key matches to created key pair and public key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testKeyPairPublicKeyInitGetCompare(): void
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
$crypt = new AsymmetricAnonymousEncryption($key_pair, $public_key);
|
||||
$this->assertTrue(
|
||||
$crypt->compareKeyPair($key_pair),
|
||||
'set key pair not equal to original key pair'
|
||||
);
|
||||
$this->assertTrue(
|
||||
$crypt->comparePublicKey($public_key),
|
||||
'set public key not equal to original public key'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$key_pair,
|
||||
$crypt->getKeyPair(),
|
||||
'set key pair returned not equal to original key pair'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$public_key,
|
||||
$crypt->getPublicKey(),
|
||||
'set public key returned not equal to original public key'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::getKeyPair
|
||||
* @covers ::getPublicKey
|
||||
* @covers ::comparePublicKey
|
||||
* @testdox Check if init class set public key matches to created public key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testPublicKeyInitGetCompare(): void
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
$crypt = new AsymmetricAnonymousEncryption(public_key:$public_key);
|
||||
$this->assertTrue(
|
||||
$crypt->comparePublicKey($public_key),
|
||||
'set public key not equal to original public key'
|
||||
);
|
||||
$this->assertEquals(
|
||||
null,
|
||||
$crypt->getKeyPair(),
|
||||
'unset set key pair returned not equal to original key pair'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$public_key,
|
||||
$crypt->getPublicKey(),
|
||||
'set public key returned not equal to original public key'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::setKeyPair
|
||||
* @covers ::getKeyPair
|
||||
* @covers ::compareKeyPair
|
||||
* @covers ::getPublicKey
|
||||
* @covers ::comparePublicKey
|
||||
* @testdox Check if set key pair after class init matches to created key pair and public key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testKeyPairSetGetCompare(): void
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
$crypt = new AsymmetricAnonymousEncryption();
|
||||
$crypt->setKeyPair($key_pair);
|
||||
$this->assertTrue(
|
||||
$crypt->compareKeyPair($key_pair),
|
||||
'post class init set key pair not equal to original key pair'
|
||||
);
|
||||
$this->assertTrue(
|
||||
$crypt->comparePublicKey($public_key),
|
||||
'post class init automatic set public key not equal to original public key'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$key_pair,
|
||||
$crypt->getKeyPair(),
|
||||
'post class init set key pair returned not equal to original key pair'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$public_key,
|
||||
$crypt->getPublicKey(),
|
||||
'post class init automatic set public key returned not equal to original public key'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::setKeyPair
|
||||
* @covers ::setPublicKey
|
||||
* @covers ::getKeyPair
|
||||
* @covers ::compareKeyPair
|
||||
* @covers ::getPublicKey
|
||||
* @covers ::comparePublicKey
|
||||
* @testdox Check if set key pair after class init matches to created key pair and public key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testKeyPairPublicKeySetGetCompare(): void
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
$crypt = new AsymmetricAnonymousEncryption();
|
||||
$crypt->setKeyPair($key_pair);
|
||||
$crypt->setPublicKey($public_key);
|
||||
$this->assertTrue(
|
||||
$crypt->compareKeyPair($key_pair),
|
||||
'post class init set key pair not equal to original key pair'
|
||||
);
|
||||
$this->assertTrue(
|
||||
$crypt->comparePublicKey($public_key),
|
||||
'post class init set public key not equal to original public key'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$key_pair,
|
||||
$crypt->getKeyPair(),
|
||||
'post class init set key pair returned not equal to original key pair'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$public_key,
|
||||
$crypt->getPublicKey(),
|
||||
'post class init set public key returned not equal to original public key'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::setPublicKey
|
||||
* @covers ::getKeyPair
|
||||
* @covers ::compareKeyPair
|
||||
* @covers ::getPublicKey
|
||||
* @covers ::comparePublicKey
|
||||
* @testdox Check if set key pair after class init matches to created key pair and public key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testPublicKeySetGetCompare(): void
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
$crypt = new AsymmetricAnonymousEncryption();
|
||||
$crypt->setPublicKey($public_key);
|
||||
$this->assertTrue(
|
||||
$crypt->comparePublicKey($public_key),
|
||||
'post class init set public key not equal to original public key'
|
||||
);
|
||||
$this->assertEquals(
|
||||
null,
|
||||
$crypt->getKeyPair(),
|
||||
'post class init unset key pair returned not equal to original key pair'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$public_key,
|
||||
$crypt->getPublicKey(),
|
||||
'post class init set public key returned not equal to original public key'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @testdox Check different key pair and public key set
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testDifferentSetKeyPairPublicKey()
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
$key_pair_2 = CreateKey::createKeyPair();
|
||||
$public_key_2 = CreateKey::getPublicKey($key_pair_2);
|
||||
$crypt = new AsymmetricAnonymousEncryption($key_pair, $public_key_2);
|
||||
$this->assertTrue(
|
||||
$crypt->compareKeyPair($key_pair),
|
||||
'key pair set matches key pair created'
|
||||
);
|
||||
$this->assertTrue(
|
||||
$crypt->comparePublicKey($public_key_2),
|
||||
'alternate public key set matches alternate public key created'
|
||||
);
|
||||
$this->assertFalse(
|
||||
$crypt->comparePublicKey($public_key),
|
||||
'alternate public key set does not match key pair public key'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @testdox Check if new set privat key does not overwrite set public key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testUpdateKeyPairNotUpdatePublicKey(): void
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
$crypt = new AsymmetricAnonymousEncryption($key_pair);
|
||||
$this->assertTrue(
|
||||
$crypt->compareKeyPair($key_pair),
|
||||
'set key pair not equal to original key pair'
|
||||
);
|
||||
$this->assertTrue(
|
||||
$crypt->comparePublicKey($public_key),
|
||||
'set public key not equal to original public key'
|
||||
);
|
||||
$key_pair_2 = CreateKey::createKeyPair();
|
||||
$public_key_2 = CreateKey::getPublicKey($key_pair_2);
|
||||
$crypt->setKeyPair($key_pair_2);
|
||||
$this->assertTrue(
|
||||
$crypt->compareKeyPair($key_pair_2),
|
||||
'new set key pair not equal to original new key pair'
|
||||
);
|
||||
$this->assertTrue(
|
||||
$crypt->comparePublicKey($public_key),
|
||||
'original set public key not equal to original public key'
|
||||
);
|
||||
$this->assertFalse(
|
||||
$crypt->comparePublicKey($public_key_2),
|
||||
'new public key equal to original public key'
|
||||
);
|
||||
}
|
||||
|
||||
// MARK: empty encrytped string
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::decryptKey
|
||||
* @covers ::decrypt
|
||||
* @testdox Test empty encrypted string to decrypt
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testEmptyDecryptionString(): void
|
||||
{
|
||||
$this->expectExceptionMessage('Encrypted string cannot be empty');
|
||||
AsymmetricAnonymousEncryption::decryptKey('', CreateKey::generateRandomKey());
|
||||
}
|
||||
|
||||
// MARK: encrypt/decrypt
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerEncryptDecryptSuccess(): array
|
||||
{
|
||||
return [
|
||||
'valid string' => [
|
||||
'input' => 'I am a secret',
|
||||
'expected' => 'I am a secret',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* test encrypt/decrypt produce correct output
|
||||
*
|
||||
* @covers ::generateRandomKey
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerEncryptDecryptSuccess
|
||||
* @testdox encrypt/decrypt $input must be $expected [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $expected
|
||||
* @return void
|
||||
*/
|
||||
public function testEncryptDecryptSuccess(string $input, string $expected): void
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
// test class
|
||||
$crypt = new AsymmetricAnonymousEncryption($key_pair);
|
||||
$encrypted = $crypt->encrypt($input);
|
||||
$decrypted = $crypt->decrypt($encrypted);
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$decrypted,
|
||||
'Class call',
|
||||
);
|
||||
$crypt = new AsymmetricAnonymousEncryption($key_pair, $public_key);
|
||||
$encrypted = $crypt->encrypt($input);
|
||||
$decrypted = $crypt->decrypt($encrypted);
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$decrypted,
|
||||
'Class call botjh set',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* test encrypt/decrypt produce correct output
|
||||
*
|
||||
* @covers ::generateRandomKey
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerEncryptDecryptSuccess
|
||||
* @testdox encrypt/decrypt indirect $input must be $expected [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $expected
|
||||
* @return void
|
||||
*/
|
||||
public function testEncryptDecryptSuccessIndirect(string $input, string $expected): void
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
// test indirect
|
||||
$encrypted = AsymmetricAnonymousEncryption::getInstance(public_key:$public_key)->encrypt($input);
|
||||
$decrypted = AsymmetricAnonymousEncryption::getInstance($key_pair)->decrypt($encrypted);
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$decrypted,
|
||||
'Class Instance call',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* test encrypt/decrypt produce correct output
|
||||
*
|
||||
* @covers ::generateRandomKey
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerEncryptDecryptSuccess
|
||||
* @testdox encrypt/decrypt indirect with public key $input must be $expected [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $expected
|
||||
* @return void
|
||||
*/
|
||||
public function testEncryptDecryptSuccessIndirectPublicKey(string $input, string $expected): void
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
// test indirect
|
||||
$encrypted = AsymmetricAnonymousEncryption::getInstance(public_key:$public_key)->encrypt($input);
|
||||
$decrypted = AsymmetricAnonymousEncryption::getInstance($key_pair)->decrypt($encrypted);
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$decrypted,
|
||||
'Class Instance call public key',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* test encrypt/decrypt produce correct output
|
||||
*
|
||||
* @covers ::generateRandomKey
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerEncryptDecryptSuccess
|
||||
* @testdox encrypt/decrypt static $input must be $expected [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $expected
|
||||
* @return void
|
||||
*/
|
||||
public function testEncryptDecryptSuccessStatic(string $input, string $expected): void
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
// test static
|
||||
$encrypted = AsymmetricAnonymousEncryption::encryptKey($input, $public_key);
|
||||
$decrypted = AsymmetricAnonymousEncryption::decryptKey($encrypted, $key_pair);
|
||||
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$decrypted,
|
||||
'Static call',
|
||||
);
|
||||
}
|
||||
|
||||
// MARK: invalid decrypt key
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerEncryptFailed(): array
|
||||
{
|
||||
return [
|
||||
'wrong decryption key' => [
|
||||
'input' => 'I am a secret',
|
||||
'excpetion_message' => 'Invalid key pair'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test decryption with wrong key
|
||||
*
|
||||
* @covers ::generateRandomKey
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerEncryptFailed
|
||||
* @testdox decrypt with wrong key $input throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testEncryptFailed(string $input, string $exception_message): void
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
$wrong_key_pair = CreateKey::createKeyPair();
|
||||
|
||||
// wrong key in class call
|
||||
$crypt = new AsymmetricAnonymousEncryption(public_key:$public_key);
|
||||
$encrypted = $crypt->encrypt($input);
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
$crypt->setKeyPair($wrong_key_pair);
|
||||
$crypt->decrypt($encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test decryption with wrong key
|
||||
*
|
||||
* @covers ::generateRandomKey
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerEncryptFailed
|
||||
* @testdox decrypt indirect with wrong key $input throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testEncryptFailedIndirect(string $input, string $exception_message): void
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
$wrong_key_pair = CreateKey::createKeyPair();
|
||||
|
||||
// class instance
|
||||
$encrypted = AsymmetricAnonymousEncryption::getInstance(public_key:$public_key)->encrypt($input);
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
AsymmetricAnonymousEncryption::getInstance($wrong_key_pair)->decrypt($encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test decryption with wrong key
|
||||
*
|
||||
* @covers ::generateRandomKey
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerEncryptFailed
|
||||
* @testdox decrypt static with wrong key $input throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testEncryptFailedStatic(string $input, string $exception_message): void
|
||||
{
|
||||
$key_pair = CreateKey::createKeyPair();
|
||||
$public_key = CreateKey::getPublicKey($key_pair);
|
||||
$wrong_key_pair = CreateKey::createKeyPair();
|
||||
|
||||
// class static
|
||||
$encrypted = AsymmetricAnonymousEncryption::encryptKey($input, $public_key);
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
AsymmetricAnonymousEncryption::decryptKey($encrypted, $wrong_key_pair);
|
||||
}
|
||||
|
||||
// MARK: invalid key pair
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerWrongKeyPair(): array
|
||||
{
|
||||
return [
|
||||
'not hex key pair' => [
|
||||
'key_pair' => 'not_a_hex_key_pair',
|
||||
'exception_message' => 'Invalid hex key pair'
|
||||
],
|
||||
'too short hex key pair' => [
|
||||
'key_pair' => '1cabd5cba9e042f12522f4ff2de5c31d233b',
|
||||
'excpetion_message' => 'Key pair is not the correct size (must be '
|
||||
],
|
||||
'empty key pair' => [
|
||||
'key_pair' => '',
|
||||
'excpetion_message' => 'Key pair cannot be empty'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* test invalid key provided to decrypt or encrypt
|
||||
*
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerWrongKeyPair
|
||||
* @testdox wrong key pair $key_pair throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $key_pair
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testWrongKeyPair(string $key_pair, string $exception_message): void
|
||||
{
|
||||
$enc_key_pair = CreateKey::createKeyPair();
|
||||
|
||||
// class
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
$crypt = new AsymmetricAnonymousEncryption($key_pair);
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
$crypt->encrypt('test');
|
||||
$crypt->setKeyPair($enc_key_pair);
|
||||
$encrypted = $crypt->encrypt('test');
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
$crypt->setKeyPair($key_pair);
|
||||
$crypt->decrypt($encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* test invalid key provided to decrypt or encrypt
|
||||
*
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerWrongKeyPair
|
||||
* @testdox wrong key pair indirect $key_pair throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $key_pair
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testWrongKeyPairIndirect(string $key_pair, string $exception_message): void
|
||||
{
|
||||
$enc_key_pair = CreateKey::createKeyPair();
|
||||
|
||||
// set valid encryption
|
||||
$encrypted = AsymmetricAnonymousEncryption::getInstance($enc_key_pair)->encrypt('test');
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
AsymmetricAnonymousEncryption::getInstance($key_pair)->decrypt($encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* test invalid key provided to decrypt or encrypt
|
||||
*
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerWrongKeyPair
|
||||
* @testdox wrong key pair static $key_pair throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $key_pair
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testWrongKeyPairStatic(string $key_pair, string $exception_message): void
|
||||
{
|
||||
$enc_key_pair = CreateKey::createKeyPair();
|
||||
|
||||
// set valid encryption
|
||||
$encrypted = AsymmetricAnonymousEncryption::encryptKey('test', CreateKey::getPublicKey($enc_key_pair));
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
AsymmetricAnonymousEncryption::decryptKey($encrypted, $key_pair);
|
||||
}
|
||||
|
||||
// MARK: invalid public key
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerWrongPublicKey(): array
|
||||
{
|
||||
return [
|
||||
'not hex public key' => [
|
||||
'public_key' => 'not_a_hex_public_key',
|
||||
'exception_message' => 'Invalid hex public key'
|
||||
],
|
||||
'too short hex public key' => [
|
||||
'public_key' => '1cabd5cba9e042f12522f4ff2de5c31d233b',
|
||||
'excpetion_message' => 'Public key is not the correct size (must be '
|
||||
],
|
||||
'empty public key' => [
|
||||
'public_key' => '',
|
||||
'excpetion_message' => 'Public key cannot be empty'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* test invalid key provided to decrypt or encrypt
|
||||
*
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerWrongPublicKey
|
||||
* @testdox wrong public key $public_key throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $public_key
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testWrongPublicKey(string $public_key, string $exception_message): void
|
||||
{
|
||||
$enc_key_pair = CreateKey::createKeyPair();
|
||||
// $enc_public_key = CreateKey::getPublicKey($enc_key_pair);
|
||||
|
||||
// class
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
$crypt = new AsymmetricAnonymousEncryption(public_key:$public_key);
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
$crypt->decrypt('test');
|
||||
$crypt->setKeyPair($enc_key_pair);
|
||||
$encrypted = $crypt->encrypt('test');
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
$crypt->setPublicKey($public_key);
|
||||
$crypt->decrypt($encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* test invalid key provided to decrypt or encrypt
|
||||
*
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerWrongPublicKey
|
||||
* @testdox wrong public key indirect $key throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testWrongPublicKeyIndirect(string $key, string $exception_message): void
|
||||
{
|
||||
$enc_key = CreateKey::createKeyPair();
|
||||
|
||||
// class instance
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
AsymmetricAnonymousEncryption::getInstance(public_key:$key)->encrypt('test');
|
||||
// we must encrypt valid thing first so we can fail with the wrong key
|
||||
$encrypted = AsymmetricAnonymousEncryption::getInstance($enc_key)->encrypt('test');
|
||||
// $this->expectExceptionMessage($exception_message);
|
||||
AsymmetricAnonymousEncryption::getInstance($key)->decrypt($encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* test invalid key provided to decrypt or encrypt
|
||||
*
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerWrongPublicKey
|
||||
* @testdox wrong public key static $key throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testWrongPublicKeyStatic(string $key, string $exception_message): void
|
||||
{
|
||||
$enc_key = CreateKey::createKeyPair();
|
||||
|
||||
// class static
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
AsymmetricAnonymousEncryption::encryptKey('test', $key);
|
||||
// we must encrypt valid thing first so we can fail with the wrong key
|
||||
$encrypted = AsymmetricAnonymousEncryption::encryptKey('test', $enc_key);
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
AsymmetricAnonymousEncryption::decryptKey($encrypted, $key);
|
||||
}
|
||||
|
||||
// MARK: wrong cipher text
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function providerWrongCiphertext(): array
|
||||
{
|
||||
return [
|
||||
'invalid cipher text' => [
|
||||
'input' => 'short',
|
||||
'exception_message' => 'base642bin failed: '
|
||||
],
|
||||
'cannot decrypt' => [
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
'input' => 'Um8tBGiVfFAOg2YoUgA5fTqK1wXPB1S7uxhPNE1lqDxgntkEhYJDOmjXa0DMpBlYHjab6sC4mgzwZSzGCUnXDAgsHckwYwfAzs/r',
|
||||
// phpcs:enable Generic.Files.LineLength
|
||||
'exception_message' => 'Invalid key pair'
|
||||
],
|
||||
'invalid text' => [
|
||||
'input' => 'U29tZSB0ZXh0IGhlcmU=',
|
||||
'exception_message' => 'Invalid key pair'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerWrongCiphertext
|
||||
* @testdox too short ciphertext $input throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testWrongCiphertext(string $input, string $exception_message): void
|
||||
{
|
||||
$key = CreateKey::createKeyPair();
|
||||
// class
|
||||
$crypt = new AsymmetricAnonymousEncryption($key);
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
$crypt->decrypt($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::decryptKey
|
||||
* @dataProvider providerWrongCiphertext
|
||||
* @testdox too short ciphertext indirect $input throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testWrongCiphertextIndirect(string $input, string $exception_message): void
|
||||
{
|
||||
$key = CreateKey::createKeyPair();
|
||||
|
||||
// class instance
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
AsymmetricAnonymousEncryption::getInstance($key)->decrypt($input);
|
||||
|
||||
// class static
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
AsymmetricAnonymousEncryption::decryptKey($input, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::decryptKey
|
||||
* @dataProvider providerWrongCiphertext
|
||||
* @testdox too short ciphertext static $input throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testWrongCiphertextStatic(string $input, string $exception_message): void
|
||||
{
|
||||
$key = CreateKey::createKeyPair();
|
||||
// class static
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
AsymmetricAnonymousEncryption::decryptKey($input, $key);
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
@@ -15,6 +15,77 @@ use CoreLibs\Security\SymmetricEncryption;
|
||||
*/
|
||||
final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
|
||||
{
|
||||
// MARK: key set compare
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::compareKey
|
||||
* @covers ::getKey
|
||||
* @testdox Check if init class set key matches to created key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testKeyInitGetCompare(): void
|
||||
{
|
||||
$key = CreateKey::generateRandomKey();
|
||||
$crypt = new SymmetricEncryption($key);
|
||||
$this->assertTrue(
|
||||
$crypt->compareKey($key),
|
||||
'set key not equal to original key'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$key,
|
||||
$crypt->getKey(),
|
||||
'set key returned not equal to original key'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::setKey
|
||||
* @covers ::compareKey
|
||||
* @covers ::getKey
|
||||
* @testdox Check if set key after class init matches to created key
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testKeySetGetCompare(): void
|
||||
{
|
||||
$key = CreateKey::generateRandomKey();
|
||||
$crypt = new SymmetricEncryption();
|
||||
$crypt->setKey($key);
|
||||
$this->assertTrue(
|
||||
$crypt->compareKey($key),
|
||||
'set key not equal to original key'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$key,
|
||||
$crypt->getKey(),
|
||||
'set key returned not equal to original key'
|
||||
);
|
||||
}
|
||||
|
||||
// MARK: empty encrypted string
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::decryptKey
|
||||
* @covers ::decrypt
|
||||
* @testdox Test empty encrypted string to decrypt
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testEmptyDecryptionString(): void
|
||||
{
|
||||
$this->expectExceptionMessage('Encrypted string cannot be empty');
|
||||
SymmetricEncryption::decryptKey('', CreateKey::generateRandomKey());
|
||||
}
|
||||
|
||||
// MARK: encrypt/decrypt compare
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
@@ -46,15 +117,73 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
|
||||
public function testEncryptDecryptSuccess(string $input, string $expected): void
|
||||
{
|
||||
$key = CreateKey::generateRandomKey();
|
||||
$encrypted = SymmetricEncryption::encrypt($input, $key);
|
||||
$decrypted = SymmetricEncryption::decrypt($encrypted, $key);
|
||||
|
||||
// test class
|
||||
$crypt = new SymmetricEncryption($key);
|
||||
$encrypted = $crypt->encrypt($input);
|
||||
$decrypted = $crypt->decrypt($encrypted);
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$decrypted,
|
||||
'Class call',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* test encrypt/decrypt produce correct output
|
||||
*
|
||||
* @covers ::generateRandomKey
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerEncryptDecryptSuccess
|
||||
* @testdox encrypt/decrypt indirect $input must be $expected [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $expected
|
||||
* @return void
|
||||
*/
|
||||
public function testEncryptDecryptSuccessIndirect(string $input, string $expected): void
|
||||
{
|
||||
$key = CreateKey::generateRandomKey();
|
||||
// test indirect
|
||||
$encrypted = SymmetricEncryption::getInstance($key)->encrypt($input);
|
||||
$decrypted = SymmetricEncryption::getInstance($key)->decrypt($encrypted);
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$decrypted,
|
||||
'Class Instance call',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* test encrypt/decrypt produce correct output
|
||||
*
|
||||
* @covers ::generateRandomKey
|
||||
* @covers ::encryptKey
|
||||
* @covers ::decryptKey
|
||||
* @dataProvider providerEncryptDecryptSuccess
|
||||
* @testdox encrypt/decrypt static $input must be $expected [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $expected
|
||||
* @return void
|
||||
*/
|
||||
public function testEncryptDecryptSuccessStatic(string $input, string $expected): void
|
||||
{
|
||||
$key = CreateKey::generateRandomKey();
|
||||
// test static
|
||||
$encrypted = SymmetricEncryption::encryptKey($input, $key);
|
||||
$decrypted = SymmetricEncryption::decryptKey($encrypted, $key);
|
||||
|
||||
$this->assertEquals(
|
||||
$expected,
|
||||
$decrypted
|
||||
$decrypted,
|
||||
'Static call',
|
||||
);
|
||||
}
|
||||
|
||||
// MARK: invalid key
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
@@ -86,12 +215,66 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
|
||||
public function testEncryptFailed(string $input, string $exception_message): void
|
||||
{
|
||||
$key = CreateKey::generateRandomKey();
|
||||
$encrypted = SymmetricEncryption::encrypt($input, $key);
|
||||
$wrong_key = CreateKey::generateRandomKey();
|
||||
|
||||
// wrong key in class call
|
||||
$crypt = new SymmetricEncryption($key);
|
||||
$encrypted = $crypt->encrypt($input);
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::decrypt($encrypted, $wrong_key);
|
||||
$crypt->setKey($wrong_key);
|
||||
$crypt->decrypt($encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test decryption with wrong key
|
||||
*
|
||||
* @covers ::generateRandomKey
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerEncryptFailed
|
||||
* @testdox decrypt indirect with wrong key $input throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testEncryptFailedIndirect(string $input, string $exception_message): void
|
||||
{
|
||||
$key = CreateKey::generateRandomKey();
|
||||
$wrong_key = CreateKey::generateRandomKey();
|
||||
|
||||
// class instance
|
||||
$encrypted = SymmetricEncryption::getInstance($key)->encrypt($input);
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::getInstance($wrong_key)->decrypt($encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test decryption with wrong key
|
||||
*
|
||||
* @covers ::generateRandomKey
|
||||
* @covers ::encryptKey
|
||||
* @covers ::decryptKey
|
||||
* @dataProvider providerEncryptFailed
|
||||
* @testdox decrypt static with wrong key $input throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testEncryptFailedStatic(string $input, string $exception_message): void
|
||||
{
|
||||
$key = CreateKey::generateRandomKey();
|
||||
$wrong_key = CreateKey::generateRandomKey();
|
||||
|
||||
// class static
|
||||
$encrypted = SymmetricEncryption::encryptKey($input, $key);
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::decryptKey($encrypted, $wrong_key);
|
||||
}
|
||||
|
||||
// MARK: wrong key
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
@@ -107,8 +290,11 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
|
||||
'too short hex key' => [
|
||||
'key' => '1cabd5cba9e042f12522f4ff2de5c31d233b',
|
||||
'excpetion_message' => 'Key is not the correct size (must be '
|
||||
. 'SODIUM_CRYPTO_SECRETBOX_KEYBYTES bytes long).'
|
||||
],
|
||||
'empty key' => [
|
||||
'key' => '',
|
||||
'excpetion_message' => 'Key cannot be empty'
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -126,15 +312,72 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
|
||||
*/
|
||||
public function testWrongKey(string $key, string $exception_message): void
|
||||
{
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::encrypt('test', $key);
|
||||
// we must encrypt valid thing first so we can fail with the wrong kjey
|
||||
$enc_key = CreateKey::generateRandomKey();
|
||||
$encrypted = SymmetricEncryption::encrypt('test', $enc_key);
|
||||
|
||||
// class
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::decrypt($encrypted, $key);
|
||||
$crypt = new SymmetricEncryption($key);
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
$crypt->encrypt('test');
|
||||
$crypt->setKey($enc_key);
|
||||
$encrypted = $crypt->encrypt('test');
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
$crypt->setKey($key);
|
||||
$crypt->decrypt($encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* test invalid key provided to decrypt or encrypt
|
||||
*
|
||||
* @covers ::encrypt
|
||||
* @covers ::decrypt
|
||||
* @dataProvider providerWrongKey
|
||||
* @testdox wrong key indirect $key throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testWrongKeyIndirect(string $key, string $exception_message): void
|
||||
{
|
||||
$enc_key = CreateKey::generateRandomKey();
|
||||
|
||||
// class instance
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::getInstance($key)->encrypt('test');
|
||||
// we must encrypt valid thing first so we can fail with the wrong key
|
||||
$encrypted = SymmetricEncryption::getInstance($enc_key)->encrypt('test');
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::getInstance($key)->decrypt($encrypted);
|
||||
}
|
||||
|
||||
/**
|
||||
* test invalid key provided to decrypt or encrypt
|
||||
*
|
||||
* @covers ::encryptKey
|
||||
* @covers ::decryptKey
|
||||
* @dataProvider providerWrongKey
|
||||
* @testdox wrong key static $key throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testWrongKeyStatic(string $key, string $exception_message): void
|
||||
{
|
||||
$enc_key = CreateKey::generateRandomKey();
|
||||
|
||||
// class static
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::encryptKey('test', $key);
|
||||
// we must encrypt valid thing first so we can fail with the wrong key
|
||||
$encrypted = SymmetricEncryption::encryptKey('test', $enc_key);
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::decryptKey($encrypted, $key);
|
||||
}
|
||||
|
||||
// MARK: wrong input
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
@@ -145,7 +388,7 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
|
||||
return [
|
||||
'too short ciphertext' => [
|
||||
'input' => 'short',
|
||||
'exception_message' => 'Invalid ciphertext (too short)'
|
||||
'exception_message' => 'Decipher message failed: '
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -164,8 +407,61 @@ final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
|
||||
public function testWrongCiphertext(string $input, string $exception_message): void
|
||||
{
|
||||
$key = CreateKey::generateRandomKey();
|
||||
// class
|
||||
$crypt = new SymmetricEncryption($key);
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::decrypt($input, $key);
|
||||
$crypt->decrypt($input);
|
||||
|
||||
// class instance
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::getInstance($key)->decrypt($input);
|
||||
|
||||
// class static
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::decryptKey($input, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::decryptKey
|
||||
* @dataProvider providerWrongCiphertext
|
||||
* @testdox too short ciphertext indirect $input throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testWrongCiphertextIndirect(string $input, string $exception_message): void
|
||||
{
|
||||
$key = CreateKey::generateRandomKey();
|
||||
|
||||
// class instance
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::getInstance($key)->decrypt($input);
|
||||
|
||||
// class static
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::decryptKey($input, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @covers ::decryptKey
|
||||
* @dataProvider providerWrongCiphertext
|
||||
* @testdox too short ciphertext static $input throws $exception_message [$_dataName]
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $exception_message
|
||||
* @return void
|
||||
*/
|
||||
public function testWrongCiphertextStatic(string $input, string $exception_message): void
|
||||
{
|
||||
$key = CreateKey::generateRandomKey();
|
||||
// class static
|
||||
$this->expectExceptionMessage($exception_message);
|
||||
SymmetricEncryption::decryptKey($input, $key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1232
test/phpunit/UrlRequests/CoreLibsUrlRequestsCurlTest.php
Normal file
1232
test/phpunit/UrlRequests/CoreLibsUrlRequestsCurlTest.php
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user