Compare commits

...

20 Commits

Author SHA1 Message Date
Clemens Schwaighofer
92214ae136 DB\IO bug fixes for unset returns 2023-08-28 09:52:51 +09:00
Clemens Schwaighofer
71f9afd6c7 Release: v9.5.0 2023-08-22 13:36:08 +09:00
Clemens Schwaighofer
11de4f9915 Logging update for class/method trace 2023-08-22 13:34:43 +09:00
Clemens Schwaighofer
dfb46d4f4a Release: v9.4.0 2023-08-22 13:33:08 +09:00
Clemens Schwaighofer
53c7dda9a0 JSON Array to convert fix, bool/false return fix 2023-08-02 16:34:57 +09:00
Clemens Schwaighofer
daf2706e7e Release: v9.3.6 2023-07-26 12:13:59 +09:00
Clemens Schwaighofer
6d3a7b7b28 ACL\Login bug fix for acl:base must always be int 2023-07-26 11:52:53 +09:00
Clemens Schwaighofer
745340a7f5 Release: v9.3.5 2023-07-24 09:16:09 +09:00
Clemens Schwaighofer
419c578c46 ACL\Login get edit access id return value fix 2023-07-24 09:14:16 +09:00
Clemens Schwaighofer
6beff9c6ac Release: v9.3.4 2023-07-21 19:07:40 +09:00
Clemens Schwaighofer
79dbd053fa Remove per class loggingn from ACL\Login 2023-07-21 19:06:30 +09:00
Clemens Schwaighofer
74004e5221 Release: v9.3.3 2023-07-21 17:53:18 +09:00
Clemens Schwaighofer
0392187299 DB\IO init with null 2023-07-21 17:52:16 +09:00
Clemens Schwaighofer
edcc65df3e Release: v9.3.2 2023-07-21 17:49:09 +09:00
Clemens Schwaighofer
5dde52a309 DB\IO error/warning log prefix remove, Admin\Backend acl default value check 2023-07-14 15:10:03 +09:00
Clemens Schwaighofer
5f223fb50d Release: v9.3.1 2023-07-04 11:47:24 +09:00
Clemens Schwaighofer
2eaf80b1bd new Combined\DateTime method dateRangeHasWeekend 2023-07-04 11:46:34 +09:00
Clemens Schwaighofer
b5d601aec0 Release: v9.3.0 2023-06-28 15:34:31 +09:00
Clemens Schwaighofer
edcc77a6ab HtmlBuilder classes 2023-06-28 15:33:12 +09:00
Clemens Schwaighofer
0b1df7a901 Release: v9.2.0 2023-06-16 13:28:44 +09:00
28 changed files with 2721 additions and 197 deletions

View File

@@ -0,0 +1,123 @@
<?php
/**
* This configuration will be read and overlaid on top of the
* default configuration. Command line arguments will be applied
* after this file is read.
*
* @see src/Phan/Config.php
* See Config for all configurable options.
*
* A Note About Paths
* ==================
*
* Files referenced from this file should be defined as
*
* ```
* Config::projectPath('relative_path/to/file')
* ```
*
* where the relative path is relative to the root of the
* project which is defined as either the working directory
* of the phan executable or a path passed in via the CLI
* '-d' flag.
*/
use Phan\Config;
return [
// "target_php_version" => "8.2",
// turn color on (-C)
"color_issue_messages_if_supported" => true,
// If true, missing properties will be created when
// they are first seen. If false, we'll report an
// error message.
"allow_missing_properties" => false,
// Allow null to be cast as any type and for any
// type to be cast to null.
"null_casts_as_any_type" => false,
// Backwards Compatibility Checking
'backward_compatibility_checks' => false,
// Run a quick version of checks that takes less
// time
"quick_mode" => false,
// Only emit critical issues to start with
// (0 is low severity, 5 is normal severity, 10 is critical)
"minimum_severity" => 0,
// enable for dead code check
// this will spill out errors for all methods never called
// use after all is OK to try to find unused code blocks
// ignore recommended: PhanUnreferencedPublicMethod
// "dead_code_detection" => true,
// default false for include path check
"enable_include_path_checks" => true,
"include_paths" => [
'.',
// '../test/configs/'
],
'ignore_undeclared_variables_in_global_scope' => true,
"file_list" => [
"./test/configs/config.php",
// "./test/configs/config.db.php",
// "./test/configs/config.host.php",
// "./test/configs/config.path.php",
"./test/configs/config.other.php",
"./test/configs/config.master.php",
],
// A list of directories that should be parsed for class and
// method information. After excluding the directories
// defined in exclude_analysis_directory_list, the remaining
// files will be statically analyzed for errors.
//
// Thus, both first-party and third-party code being used by
// your application should be included in this list.
'directory_list' => [
// Change this to include the folders you wish to analyze
// (and the folders of their dependencies)
'src',
// To speed up analysis, we recommend going back later and
// limiting this to only the vendor/ subdirectories your
// project depends on.
// `phan --init` will generate a list of folders for you
'vendor/egrajp/smarty-extended',
],
// A list of directories holding code that we want
// to parse, but not analyze
"exclude_analysis_directory_list" => [
'vendor/egrajp/smarty-extended',
],
'exclude_file_list' => [
],
// what not to show as problem
'suppress_issue_types' => [
// 'PhanUndeclaredMethod',
'PhanEmptyFile',
// ignore unreferences public methods, etc here (for dead code check)
'PhanUnreferencedPublicMethod',
'PhanUnreferencedClass',
'PhanWriteOnlyPublicProperty',
'PhanUnreferencedConstant',
'PhanWriteOnlyPublicProperty',
'PhanReadOnlyPublicProperty',
// start ignore annotations
'PhanUnextractableAnnotationElementName',
'PhanUnextractableAnnotationSuffix',
],
// Override to hardcode existence and types of (non-builtin) globals in the global scope.
// Class names should be prefixed with `\`.
//
// (E.g. `['_FOO' => '\FooClass', 'page' => '\PageClass', 'userId' => 'int']`)
'globals_type_map' => [],
];

View File

@@ -1,12 +1,25 @@
<?php
/**
* This configuration file was automatically generated by 'phan --init --init-level=3'
*
* TODOs (added by 'phan --init'):
*
* - Go through this file and verify that there are no missing/unnecessary files/directories.
* (E.g. this only includes direct composer dependencies
* - You may have to manually add indirect composer dependencies to 'directory_list')
* - Look at 'plugins' and add or remove plugins if appropriate
* (see https://github.com/phan/phan/tree/v5/.phan/plugins#plugins)
* - Add global suppressions for pre-existing issues to suppress_issue_types
* (https://github.com/phan/phan/wiki/Tutorial-for-Analyzing-a-Large-Sloppy-Code-Base)
* - Consider setting up a baseline if there are a large number of pre-existing issues (see `phan --extended-help`)
*
* This configuration will be read and overlaid on top of the
* default configuration. Command line arguments will be applied
* after this file is read.
*
* @see src/Phan/Config.php
* See Config for all configurable options.
* @see https://github.com/phan/phan/wiki/Phan-Config-Settings for all configurable options
* @see https://github.com/phan/phan/tree/v5/src/Phan/Config.php
*
* A Note About Paths
* ==================
@@ -23,101 +36,343 @@
* '-d' flag.
*/
use Phan\Config;
use Phan\Issue;
return [
// "target_php_version" => "8.2",
// turn color on (-C)
"color_issue_messages_if_supported" => true,
// If true, missing properties will be created when
// The PHP version that the codebase will be checked for compatibility against.
// For best results, the PHP binary used to run Phan should have the same PHP version.
// (Phan relies on Reflection for some types, param counts,
// and checks for undefined classes/methods/functions)
//
// Supported values: `'5.6'`, `'7.0'`, `'7.1'`, `'7.2'`, `'7.3'`, `'7.4'`,
// `'8.0'`, `'8.1'`, `null`.
// If this is set to `null`,
// then Phan assumes the PHP version which is closest to the minor version
// of the php executable used to execute Phan.
//
// 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',
// If enabled, missing properties will be created when
// they are first seen. If false, we'll report an
// error message.
"allow_missing_properties" => false,
// error message if there is an attempt to write
// to a class property that wasn't explicitly
// defined.
'allow_missing_properties' => false,
// Allow null to be cast as any type and for any
// type to be cast to null.
"null_casts_as_any_type" => false,
// If enabled, null can be cast to any type and any
// type can be cast to null. Setting this to true
// will cut down on false positives.
'null_casts_as_any_type' => false,
// Backwards Compatibility Checking
'backward_compatibility_checks' => false,
// If enabled, allow null to be cast as any array-like type.
//
// This is an incremental step in migrating away from `null_casts_as_any_type`.
// If `null_casts_as_any_type` is true, this has no effect.
'null_casts_as_array' => true,
// Run a quick version of checks that takes less
// time
"quick_mode" => false,
// If enabled, allow any array-like type to be cast to null.
// This is an incremental step in migrating away from `null_casts_as_any_type`.
// If `null_casts_as_any_type` is true, this has no effect.
'array_casts_as_null' => true,
// Only emit critical issues to start with
// (0 is low severity, 5 is normal severity, 10 is critical)
"minimum_severity" => 0,
// If enabled, scalars (int, float, bool, string, null)
// are treated as if they can cast to each other.
// This does not affect checks of array keys. See `scalar_array_key_cast`.
'scalar_implicit_cast' => false,
// enable for dead code check
// this will spill out errors for all methods never called
// use after all is OK to try to find unused code blocks
// ignore recommended: PhanUnreferencedPublicMethod
// "dead_code_detection" => true,
// If enabled, any scalar array keys (int, string)
// are treated as if they can cast to each other.
// E.g. `array<int,stdClass>` can cast to `array<string,stdClass>` and vice versa.
// Normally, a scalar type such as int could only cast to/from int and mixed.
'scalar_array_key_cast' => true,
// default false for include path check
"enable_include_path_checks" => true,
"include_paths" => [
'.',
// '../test/configs/'
],
// If this has entries, scalars (int, float, bool, string, null)
// are allowed to perform the casts listed.
//
// E.g. `['int' => ['float', 'string'], 'float' => ['int'], 'string' => ['int'], 'null' => ['string']]`
// allows casting null to a string, but not vice versa.
// (subset of `scalar_implicit_cast`)
'scalar_implicit_partial' => [],
// If enabled, Phan will warn if **any** type in a method invocation's object
// is definitely not an object,
// or if **any** type in an invoked expression is not a callable.
// Setting this to true will introduce numerous false positives
// (and reveal some bugs).
'strict_method_checking' => false,
// If enabled, Phan will warn if **any** type of the object expression for a property access
// does not contain that property.
'strict_object_checking' => false,
// If enabled, Phan will warn if **any** type in the argument's union type
// cannot be cast to a type in the parameter's expected union type.
// Setting this to true will introduce numerous false positives
// (and reveal some bugs).
'strict_param_checking' => false,
// If enabled, Phan will warn if **any** type in a property assignment's union type
// cannot be cast to a type in the property's declared union type.
// Setting this to true will introduce numerous false positives
// (and reveal some bugs).
'strict_property_checking' => false,
// If enabled, Phan will warn if **any** type in a returned value's union type
// cannot be cast to the declared return type.
// Setting this to true will introduce numerous false positives
// (and reveal some bugs).
'strict_return_checking' => false,
// If true, seemingly undeclared variables in the global
// scope will be ignored.
//
// This is useful for projects with complicated cross-file
// globals that you have no hope of fixing.
'ignore_undeclared_variables_in_global_scope' => true,
"file_list" => [
"./test/configs/config.php",
// "./test/configs/config.db.php",
// "./test/configs/config.host.php",
// "./test/configs/config.path.php",
"./test/configs/config.other.php",
"./test/configs/config.master.php",
],
// A list of directories that should be parsed for class and
// method information. After excluding the directories
// defined in exclude_analysis_directory_list, the remaining
// files will be statically analyzed for errors.
// Set this to false to emit `PhanUndeclaredFunction` issues for internal functions that Phan has signatures for,
// but aren't available in the codebase, or from Reflection.
// (may lead to false positives if an extension isn't loaded)
//
// Thus, both first-party and third-party code being used by
// your application should be included in this list.
'directory_list' => [
// Change this to include the folders you wish to analyze
// (and the folders of their dependencies)
'src',
// To speed up analysis, we recommend going back later and
// limiting this to only the vendor/ subdirectories your
// project depends on.
// `phan --init` will generate a list of folders for you
'vendor/egrajp/smarty-extended',
],
// If this is true(default), then Phan will not warn.
//
// Even when this is false, Phan will still infer return values and check parameters of internal functions
// if Phan has the signatures.
'ignore_undeclared_functions_with_known_signatures' => true,
// Backwards Compatibility Checking. This is slow
// and expensive, but you should consider running
// it before upgrading your version of PHP to a
// new version that has backward compatibility
// breaks.
//
// If you are migrating from PHP 5 to PHP 7,
// you should also look into using
// [php7cc (no longer maintained)](https://github.com/sstalle/php7cc)
// and [php7mar](https://github.com/Alexia/php7mar),
// which have different backwards compatibility checks.
//
// If you are still using versions of php older than 5.6,
// `PHP53CompatibilityPlugin` may be worth looking into if you are not running
// syntax checks for php 5.3 through another method such as
// `InvokePHPNativeSyntaxCheckPlugin` (see .phan/plugins/README.md).
'backward_compatibility_checks' => false,
// A list of directories holding code that we want
// to parse, but not analyze
"exclude_analysis_directory_list" => [
'vendor/egrajp/smarty-extended',
],
'exclude_file_list' => [
],
// If true, check to make sure the return type declared
// in the doc-block (if any) matches the return type
// declared in the method signature.
'check_docblock_signature_return_type_match' => false,
// what not to show as problem
'suppress_issue_types' => [
// 'PhanUndeclaredMethod',
'PhanEmptyFile',
// ignore unreferences public methods, etc here (for dead code check)
'PhanUnreferencedPublicMethod',
'PhanUnreferencedClass',
'PhanWriteOnlyPublicProperty',
'PhanUnreferencedConstant',
'PhanWriteOnlyPublicProperty',
'PhanReadOnlyPublicProperty',
// start ignore annotations
'PhanUnextractableAnnotationElementName',
'PhanUnextractableAnnotationSuffix',
],
// This setting maps case-insensitive strings to union types.
//
// This is useful if a project uses phpdoc that differs from the phpdoc2 standard.
//
// If the corresponding value is the empty string,
// then Phan will ignore that union type (E.g. can ignore 'the' in `@return the value`)
//
// If the corresponding value is not empty,
// then Phan will act as though it saw the corresponding UnionTypes(s)
// when the keys show up in a UnionType of `@param`, `@return`, `@var`, `@property`, etc.
//
// This matches the **entire string**, not parts of the string.
// (E.g. `@return the|null` will still look for a class with the name `the`,
// but `@return the` will be ignored with the below setting)
//
// (These are not aliases, this setting is ignored outside of doc comments).
// (Phan does not check if classes with these names exist)
//
// Example setting: `['unknown' => '', 'number' => 'int|float', 'char' => 'string', 'long' => 'int', 'the' => '']`
'phpdoc_type_mapping' => [],
// Set to true in order to attempt to detect dead
// (unreferenced) code. Keep in mind that the
// results will only be a guess given that classes,
// properties, constants and methods can be referenced
// as variables (like `$class->$property` or
// `$class->$method()`) in ways that we're unable
// to make sense of.
//
// To more aggressively detect dead code,
// you may want to set `dead_code_detection_prefer_false_negative` to `false`.
'dead_code_detection' => false,
// Set to true in order to attempt to detect unused variables.
// `dead_code_detection` will also enable unused variable detection.
//
// This has a few known false positives, e.g. for loops or branches.
'unused_variable_detection' => false,
// Set to true in order to attempt to detect redundant and impossible conditions.
//
// This has some false positives involving loops,
// variables set in branches of loops, and global variables.
'redundant_condition_detection' => false,
// If enabled, Phan will act as though it's certain of real return types of a subset of internal functions,
// even if those return types aren't available in reflection
// (real types were taken from php 7.3 or 8.0-dev, depending on target_php_version).
//
// Note that with php 7 and earlier, php would return null or false for many internal functions
// if the argument types or counts were incorrect.
// As a result, enabling this setting with target_php_version 8.0 may result in false positives
// for `--redundant-condition-detection` when codebases also support php 7.x.
'assume_real_types_for_internal_functions' => false,
// If true, this runs a quick version of checks that takes less
// time at the cost of not running as thorough
// of an analysis. You should consider setting this
// to true only when you wish you had more **undiagnosed** issues
// to fix in your code base.
//
// In quick-mode the scanner doesn't rescan a function
// or a method's code block every time a call is seen.
// This means that the problem here won't be detected:
//
// ```php
// <?php
// function test($arg):int {
// return $arg;
// }
// test("abc");
// ```
//
// This would normally generate:
//
// ```
// test.php:3 PhanTypeMismatchReturn Returning type string but test() is declared to return int
// ```
//
// The initial scan of the function's code block has no
// type information for `$arg`. It isn't until we see
// the call and rescan `test()`'s code block that we can
// detect that it is actually returning the passed in
// `string` instead of an `int` as declared.
'quick_mode' => false,
// Override to hardcode existence and types of (non-builtin) globals in the global scope.
// Class names should be prefixed with `\`.
//
// (E.g. `['_FOO' => '\FooClass', 'page' => '\PageClass', 'userId' => 'int']`)
'globals_type_map' => [],
// The minimum severity level to report on. This can be
// set to `Issue::SEVERITY_LOW`, `Issue::SEVERITY_NORMAL` or
// `Issue::SEVERITY_CRITICAL`. Setting it to only
// critical issues is a good place to start on a big
// sloppy mature code base.
'minimum_severity' => Issue::SEVERITY_LOW,
// Add any issue types (such as `'PhanUndeclaredMethod'`)
// to this list to inhibit them from being reported.
'suppress_issue_types' => [
// start ignore annotations
'PhanUnextractableAnnotationElementName',
'PhanUnextractableAnnotationSuffix',
// wrongly thinks Enum is class
'PhanCommentObjectInClassConstantType',
],
// A regular expression to match files to be excluded
// from parsing and analysis and will not be read at all.
//
// This is useful for excluding groups of test or example
// directories/files, unanalyzable files, or files that
// can't be removed for whatever reason.
// (e.g. `'@Test\.php$@'`, or `'@vendor/.*/(tests|Tests)/@'`)
'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@',
// A list of files that will be excluded from parsing and analysis
// and will not be read at all.
//
// This is useful for excluding hopelessly unanalyzable
// files that can't be removed for whatever reason.
'exclude_file_list' => [],
// A directory list that defines files that will be excluded
// from static analysis, but whose class and method
// information should be included.
//
// Generally, you'll want to include the directories for
// third-party code (such as "vendor/") in this list.
//
// n.b.: If you'd like to parse but not analyze 3rd
// party code, directories containing that code
// should be added to the `directory_list` as well as
// to `exclude_analysis_directory_list`.
'exclude_analysis_directory_list' => [
'vendor/',
],
// Enable this to enable checks of require/include statements referring to valid paths.
// The settings `include_paths` and `warn_about_relative_include_statement` affect the checks.
'enable_include_path_checks' => true,
"include_paths" => [
'.',
],
// The number of processes to fork off during the analysis
// phase.
'processes' => 1,
// List of case-insensitive file extensions supported by Phan.
// (e.g. `['php', 'html', 'htm']`)
'analyzed_file_extensions' => [
'php',
],
// You can put paths to stubs of internal extensions in this config option.
// If the corresponding extension is **not** loaded, then Phan will use the stubs instead.
// Phan will continue using its detailed type annotations,
// but load the constants, classes, functions, and classes (and their Reflection types)
// from these stub files (doubling as valid php files).
// Use a different extension from php to avoid accidentally loading these.
// The `tools/make_stubs` script can be used to generate your own stubs (compatible with php 7.0+ right now)
//
// (e.g. `['xdebug' => '.phan/internal_stubs/xdebug.phan_php']`)
'autoload_internal_extension_signatures' => [],
// A list of plugin files to execute.
//
// Plugins which are bundled with Phan can be added here by providing their name (e.g. `'AlwaysReturnPlugin'`)
//
// Documentation about available bundled plugins can be found
// [here](https://github.com/phan/phan/tree/v5/.phan/plugins).
//
// Alternately, you can pass in the full path to a PHP file with the plugin's implementation
// (e.g. `'vendor/phan/phan/.phan/plugins/AlwaysReturnPlugin.php'`)
'plugins' => [
'AlwaysReturnPlugin',
'PregRegexCheckerPlugin',
'UnreachableCodePlugin',
],
// A list of directories that should be parsed for class and
// method information. After excluding the directories
// defined in `exclude_analysis_directory_list`, the remaining
// files will be statically analyzed for errors.
//
// Thus, both first-party and third-party code being used by
// your application should be included in this list.
'directory_list' => [
'src',
'vendor/egrajp/smarty-extended/src',
'vendor/phan/phan/src/Phan',
'vendor/phpunit/phpunit/src',
'vendor/psr/log/src',
'vendor/vimeo/psalm/src/Psalm',
'vendor/gullevek/dotenv',
],
// A list of individual files to include in analysis
// with a path relative to the root directory of the
// project.
'file_list' => [
"./test/configs/config.php",
"./test/configs/config.other.php",
"./test/configs/config.master.php",
],
];

View File

@@ -16,7 +16,7 @@
],
"minimum-stability": "dev",
"require": {
"php": ">=8.1",
"php": ">=8.2",
"psr/log": "^3.0@dev"
},
"require-dev": {
@@ -24,7 +24,8 @@
"phan/phan": "v5.x-dev",
"phpunit/phpunit": "^9",
"egrajp/smarty-extended": "^4.3",
"vimeo/psalm": "^5.0@dev"
"vimeo/psalm": "^5.0@dev",
"gullevek/dotenv": "dev-master"
},
"repositories": {
"git.egplusww.jp.Composer": {

View File

@@ -1 +1 @@
9.1.0
9.5.0

View File

@@ -231,8 +231,6 @@ class Login
) {
// attach db class
$this->db = $db;
// log login data for this class only
$log->setLogFlag(\CoreLibs\Logging\Logger\Flag::per_class);
// attach logger
$this->log = $log;
// attach session class
@@ -1060,9 +1058,9 @@ class Login
];
// set the default unit
if ($res['edit_default']) {
$_SESSION['UNIT_DEFAULT'] = $res['edit_access_id'];
$_SESSION['UNIT_DEFAULT'] = (int)$res['edit_access_id'];
}
$_SESSION['UNIT_UID'][$res['uid']] = $res['edit_access_id'];
$_SESSION['UNIT_UID'][$res['uid']] = (int)$res['edit_access_id'];
// sub arrays for simple access
array_push($eauid, $res['edit_access_id']);
$unit_acl[$res['edit_access_id']] = $res['level'];
@@ -1148,18 +1146,18 @@ class Login
// user > page > group
// group ACL 0
if ($_SESSION['GROUP_ACL_LEVEL'] != -1) {
$this->acl['base'] = $_SESSION['GROUP_ACL_LEVEL'];
$this->acl['base'] = (int)$_SESSION['GROUP_ACL_LEVEL'];
}
// page ACL 1
if (
isset($_SESSION['PAGES_ACL_LEVEL'][$this->page_name]) &&
$_SESSION['PAGES_ACL_LEVEL'][$this->page_name] != -1
) {
$this->acl['base'] = $_SESSION['PAGES_ACL_LEVEL'][$this->page_name];
$this->acl['base'] = (int)$_SESSION['PAGES_ACL_LEVEL'][$this->page_name];
}
// user ACL 2
if ($_SESSION['USER_ACL_LEVEL'] != -1) {
$this->acl['base'] = $_SESSION['USER_ACL_LEVEL'];
$this->acl['base'] = (int)$_SESSION['USER_ACL_LEVEL'];
}
}
$_SESSION['BASE_ACL_LEVEL'] = $this->acl['base'];
@@ -2347,7 +2345,10 @@ HTML;
is_array($_SESSION['UNIT']) &&
!array_key_exists($edit_access_id, $_SESSION['UNIT'])
) {
return $_SESSION['UNIT_DEFAULT'] ?? null;
$edit_access_id = null;
if (is_numeric($_SESSION['UNIT_DEFAULT'])) {
$edit_access_id = (int)$_SESSION['UNIT_DEFAULT'];
}
}
return $edit_access_id;
}

View File

@@ -164,6 +164,10 @@ class Backend
);
}
$this->default_acl = $set_default_acl_level ?? DEFAULT_ACL_LEVEL;
// if negative or larger than 100, reset to 0
if ($this->default_acl < 0 || $this->default_acl > 100) {
$this->default_acl = 0;
}
// queue key
if (preg_match("/^(add|save|delete|remove|move|up|down|push_live)$/", $this->action)) {
@@ -552,7 +556,7 @@ class Backend
string $suffix = '',
int $min_steps = 1,
bool $name_pos_back = false
) {
): string {
// get the build layout
$html_time = \CoreLibs\Output\Form\Elements::printDateTime(
$year,

View File

@@ -169,10 +169,10 @@ class Email
* @param string $email email string
* @param bool $short default false, if true,
* returns only short type (pc instead of pc_html)
* @return string|bool email type, eg "pc", "docomo", etc,
* @return string|false email type, eg "pc", "docomo", etc,
* false for invalid short type
*/
public static function getEmailType(string $email, bool $short = false)
public static function getEmailType(string $email, bool $short = false): string|false
{
// trip if there is no email address
if (!$email) {
@@ -200,9 +200,9 @@ class Email
* gets the short email type from a long email type
*
* @param string $email_type email string
* @return string|bool short string or false for invalid
* @return string|false short string or false for invalid
*/
public static function getShortEmailType(string $email_type)
public static function getShortEmailType(string $email_type): string|false
{
// check if the short email type exists
if (isset(self::$mobile_email_type_short[$email_type])) {

View File

@@ -452,6 +452,31 @@ class DateTime
return $days;
}
}
/**
* check if a weekend day (sat/sun) is in the given date range
* Can have time too, but is not needed
*
* @param string $start_date Y-m-d
* @param string $end_date Y-m-d
* @return bool True for has weekend, False for has not
*/
public static function dateRangeHasWeekend(
string $start_date,
string $end_date,
): bool {
$dd_start = new \DateTime($start_date);
$dd_end = new \DateTime($end_date);
if (
// starts with a weekend
$dd_start->format('N') >= 6 ||
// start day plus diff will be 6 and so fall into a weekend
((int)$dd_start->format('w') + $dd_start->diff($dd_end)->days) >= 6
) {
return true;
}
return false;
}
}
// __END__

View File

@@ -28,7 +28,7 @@ class Colors
* @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,
* @return string|false rgb in hex values with leading # if set,
* false for invalid color
*/
public static function rgb2hex(
@@ -36,7 +36,7 @@ class Colors
int $green,
int $blue,
bool $hex_prefix = true
): string|bool {
): string|false {
$hex_color = '';
if ($hex_prefix === true) {
$hex_color = '#';
@@ -58,14 +58,14 @@ class Colors
* @param string $hexStr RGB hexstring
* @param bool $return_as_string flag to return as string
* @param string $seperator string seperator: default: ","
* @return string|array<string,float|int>|bool false on error or array with RGB
* or a string with the seperator
* @return string|array<string,float|int>|false false on error or array with RGB
* or a string with the seperator
*/
public static function hex2rgb(
string $hexStr,
bool $return_as_string = false,
string $seperator = ','
): string|array|bool {
): string|array|false {
$hexStr = preg_replace("/[^0-9A-Fa-f]/", '', $hexStr); // Gets a proper hex string
if (!is_string($hexStr)) {
return false;
@@ -97,13 +97,13 @@ class Colors
* 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<int|float>|bool Hue, Sat, Brightness/Value
* false for input value error
* @param int $red red 0-255
* @param int $green green 0-255
* @param int $blue blue 0-255
* @return array<int|float>|false Hue, Sat, Brightness/Value
* false for input value error
*/
public static function rgb2hsb(int $red, int $green, int $blue): array|bool
public static function rgb2hsb(int $red, int $green, int $blue): array|false
{
// check that rgb is from 0 to 255
foreach (['red', 'green', 'blue'] as $c) {
@@ -144,13 +144,13 @@ class Colors
* converts HSB/V to RGB values RGB is full INT
* if HSB/V value is invalid, sets this value to 0
*
* @param float $H hue 0-360 (int)
* @param float $S saturation 0-100 (int)
* @param float $V brightness/value 0-100 (int)
* @return array<int>|bool 0 red/1 green/2 blue array as 0-255
* false for input value error
* @param float $H hue 0-360 (int)
* @param float $S saturation 0-100 (int)
* @param float $V brightness/value 0-100 (int)
* @return array<int>|false 0 red/1 green/2 blue array as 0-255
* false for input value error
*/
public static function hsb2rgb(float $H, float $S, float $V): array|bool
public static function hsb2rgb(float $H, float $S, float $V): array|false
{
// check that H is 0 to 359, 360 = 0
// and S and V are 0 to 1
@@ -230,13 +230,13 @@ class Colors
* return:
* array with hue (0-360), saturation (0-100%) and luminance (0-100%)
*
* @param int $red red 0-255
* @param int $green green 0-255
* @param int $blue blue 0-255
* @return array<float>|bool hue/sat/luminance
* false for input value error
* @param int $red red 0-255
* @param int $green green 0-255
* @param int $blue blue 0-255
* @return array<float>|false hue/sat/luminance
* false for input value error
*/
public static function rgb2hsl(int $red, int $green, int $blue): array|bool
public static function rgb2hsl(int $red, int $green, int $blue): array|false
{
// check that rgb is from 0 to 255
foreach (['red', 'green', 'blue'] as $c) {
@@ -284,12 +284,12 @@ class Colors
* converts an HSL to RGB
* if HSL value is invalid, set this value to 0
*
* @param float $hue hue: 0-360 (degrees)
* @param float $sat saturation: 0-100
* @param float $lum luminance: 0-100
* @return array<int,float|int>|bool red/blue/green 0-255 each
* @param float $hue hue: 0-360 (degrees)
* @param float $sat saturation: 0-100
* @param float $lum luminance: 0-100
* @return array<int,float|int>|false red/blue/green 0-255 each
*/
public static function hsl2rgb(float $hue, float $sat, float $lum): array|bool
public static function hsl2rgb(float $hue, float $sat, float $lum): array|false
{
if ($hue == 360) {
$hue = 0;

View File

@@ -48,8 +48,26 @@ class Json
return (array)$json;
}
/**
* convert array to json
* Will set empty json {} on false/error
* Error can be read with jsonGetLastError
* Deos not throw errors
*
* @param array<mixed> $data
* @param int $flags json_encode flags as is
* @return string JSON string or '{}' if false
*/
public static function jsonConvertArrayTo(array $data, int $flags = 0): string
{
$json_string = json_encode($data, $flags) ?: '{}';
self::$json_last_error = json_last_error();
return (string)$json_string;
}
/**
* returns human readable string for json errors thrown in jsonConvertToArray
* Source: https://www.php.net/manual/en/function.json-last-error.php
*
* @param bool $return_string [default=false] if set to true
* it will return the message string and not
@@ -80,6 +98,15 @@ class Json
case JSON_ERROR_UTF8:
$json_error_string = 'Malformed UTF-8 characters, possibly incorrectly encoded';
break;
case JSON_ERROR_RECURSION:
$json_error_string = 'One or more recursive references in the value to be encoded';
break;
case JSON_ERROR_INF_OR_NAN:
$json_error_string = 'One or more NAN or INF values in the value to be encoded';
break;
case JSON_ERROR_UNSUPPORTED_TYPE:
$json_error_string = ' A value of a type that cannot be encoded was given';
break;
case JSON_ERROR_INVALID_PROPERTY_NAME:
$json_error_string = 'A key starting with \u0000 character was in the string';
break;

View File

@@ -302,14 +302,14 @@ class IO
/** @var string */
private string $to_encoding = '';
/** @var string the query string at the moment */
private string $query;
private string $query = '';
/** @var array<mixed> current params for query */
private array $params;
private array $params = [];
// only inside
// basic vars
// the dbh handler, if disconnected by command is null, bool:false on error,
/** @var \PgSql\Connection|false|null */
private \PgSql\Connection|false|null $dbh;
private \PgSql\Connection|false|null $dbh = null;
/** @var bool DB_DEBUG ... (if set prints out debug msgs) */
private bool $db_debug = false;
/** @var string the DB connected to */
@@ -344,7 +344,7 @@ class IO
// FOR BELOW: (This should be private and only readable through some method)
// cursor array for cached readings
/** @var array<string,mixed> extended cursoers string index with content */
private array $cursor_ext;
private array $cursor_ext = [];
// per query vars
/** @var \PgSql\Result|false actual cursor (DBH) */
private \PgSql\Result|false $cursor;
@@ -401,7 +401,7 @@ class IO
/** @var bool if we use RETURNING in the INSERT call */
private bool $returning_id = false;
/** @var string if a sync is running holds the hash key of the query */
private string $async_running;
private string $async_running = '';
// logging class, must be public so settings can be changed
/** @var \CoreLibs\Logging\Logging */
public \CoreLibs\Logging\Logging $log;
@@ -805,7 +805,10 @@ class IO
$call_stack[] =
($call_trace['file'] ?? 'n/f') . ':'
. ($call_trace['line'] ?? '-') . ':'
. (!empty($call_trace['class']) ? $call_trace['class'] . '->' : '')
. (!empty($call_trace['class']) ?
$call_trace['class'] . ($call_trace['type'] ?? '') :
''
)
. $call_trace['function'];
}
$context = [
@@ -814,18 +817,18 @@ class IO
switch ($id) {
case 'DB_ERROR':
$this->log->error(
$debug_id . ' :' . $prefix . $error_string,
$prefix . $error_string,
$context
);
break;
case 'DB_WARNING':
$this->log->warning(
$debug_id . ' :' . $prefix . $error_string,
$prefix . $error_string,
$context
);
break;
default:
// used named arguments so we can easy change the order of debug
// used named arguments so we can easy change the order of debug
$this->log->debug(
group_id: $debug_id,
message: $error_string,
@@ -2057,10 +2060,11 @@ class IO
/**
* this is only needed for Postgresql. Converts postgresql arrays to PHP
* Recommended to rather user 'array_to_json' instead and convet JSON in PHP
* or if ARRAY_AGG -> JSONB_AGG
*
* @param string $text input text to parse to an array
* @return array<mixed> PHP array of the parsed data
* @deprecated Recommended to use 'array_to_json' in PostgreSQL instead
* @deprecated Recommended to use 'array_to_json/jsonb_agg' in PostgreSQL instead
*/
public function dbArrayParse(string $text): array
{
@@ -2683,7 +2687,7 @@ class IO
*/
public function dbGetCursor(): \PgSql\Result|false
{
return $this->cursor;
return $this->cursor ?? false;
}
// ***************************
@@ -2740,7 +2744,7 @@ class IO
}
$query_hash = $this->dbGetQueryHash($query, $params);
if (
is_array($this->cursor_ext) &&
!empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash])
) {
if (empty($query_field)) {
@@ -2759,9 +2763,9 @@ class IO
* @param string $query Query to find in cursor_ext
* @param array<mixed> $params If the query is params type we need params
* data to create a unique call one, optional
* @return int|false query position (row pos), false on error
* @return int|false|null query position (row pos), false on error
*/
public function dbGetCursorPos(string $query, array $params = []): int|false
public function dbGetCursorPos(string $query, array $params = []): int|false|null
{
$this->__dbErrorReset();
if (!$query) {
@@ -2769,7 +2773,14 @@ class IO
return false;
}
$query_hash = $this->dbGetQueryHash($query, $params);
return (int)$this->cursor_ext[$query_hash]['pos'];
if (
!empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash])
) {
return (int)$this->cursor_ext[$query_hash]['pos'];
} else {
return null;
}
}
/**
@@ -2778,9 +2789,9 @@ class IO
* @param string $query Query to find in cursor_ext
* @param array<mixed> $params If the query is params type we need params
* data to create a unique call one, optional
* @return int|false query position (row pos), false on error
* @return int|false|null numer of rows returned, false on error
*/
public function dbGetCursorNumRows(string $query, array $params = []): int|false
public function dbGetCursorNumRows(string $query, array $params = []): int|false|null
{
$this->__dbErrorReset();
if (!$query) {
@@ -2788,7 +2799,14 @@ class IO
return false;
}
$query_hash = $this->dbGetQueryHash($query, $params);
return (int)$this->cursor_ext[$query_hash]['num_rows'];
if (
!empty($this->cursor_ext) &&
isset($this->cursor_ext[$query_hash])
) {
return (int)$this->cursor_ext[$query_hash]['num_rows'];
} else {
return null;
}
}
// ***************************
@@ -3190,7 +3208,7 @@ class IO
/**
* Returns the current async running query hash
*
* @return string Current async running query hash
* @return string Current async running query hash, empty string for nothing
*/
public function dbGetAsyncRunning(): string
{
@@ -3735,7 +3753,7 @@ class IO
* Either a single element for a single insert or an array
* if multiple insert values where used.
*
* @return array<mixed>|string|int|null Current insert query primary key
* @return array<mixed>|string|int|null Current insert query primary key, null on not set
*/
public function dbGetInsertPK(): array|string|int|null
{
@@ -3825,7 +3843,7 @@ class IO
*/
public function dbGetNumRows(): ?int
{
return $this->num_rows;
return $this->num_rows ?? null;
}
/**
@@ -3835,10 +3853,7 @@ class IO
*/
public function dbGetNumFields(): ?int
{
if (!isset($this->num_fields)) {
return null;
}
return $this->num_fields;
return $this->num_fields ?? null;
}
/**

View File

@@ -405,7 +405,7 @@ class LoggingLegacy
// set per class, but don't use get_class as we will only get self
$rpl_string = !$this->log_per_class ? '' : '_'
// set sub class settings
. str_replace('\\', '-', Support::getCallerClass());
. str_replace('\\', '-', Support::getCallerTopLevelClass());
$fn = str_replace('{CLASS}', $rpl_string, $fn); // create output filename
// if request to write to one file
@@ -756,7 +756,7 @@ class LoggingLegacy
return $status;
}
// get the last class entry and wrie that
$class = Support::getCallerClass();
$class = Support::getCallerTopLevelClass();
// get timestamp
$timestamp = Support::printTime();
// same string put for print (no html data inside)
@@ -855,7 +855,7 @@ class LoggingLegacy
. 'border-bottom: 1px solid black; margin: 10px 0 10px 0; '
. 'background-color: white; color: black;">'
. '<div style="font-size: 12px;">{<span style="font-style: italic; color: #928100;">'
. Support::getCallerClass() . '</span>}</div>';
. Support::getCallerTopLevelClass() . '</span>}</div>';
$string_output = $string_prefix . $string_output
. '<div><span style="font-style: italic; color: #108db3;">Script Run Time:</span> '
. $script_end . '</div>'

View File

@@ -79,10 +79,10 @@ class Support
* default true: true, false: false
*
* @param bool $bool Variable to convert
* @param string $name [default: ''] Prefix name
* @param string $true [default: 'true'] True string
* @param string $false [default: 'false'] False string
* @param bool $no_html [default: false] if true do not print html
* @param string $name [=''] Prefix name
* @param string $true [='true'] True string
* @param string $false [='false'] False string
* @param bool $no_html [=false] if true do not print html
* @return string String with converted bool text for debug
*/
public static function printBool(
@@ -104,8 +104,8 @@ class Support
* Convert bool value to string value. Short name alias for printBool
*
* @param bool $bool Bool value to be transformed
* @param string $true [default: 'true'] Override default string 'true'
* @param string $false [default: 'false'] Override default string 'false'
* @param string $true [='true'] Override default string 'true'
* @param string $false [=false'] Override default string 'false'
* @return string $true or $false string for true/false bool
*/
public static function prBl(
@@ -159,7 +159,7 @@ class Support
* Recommended debug output
*
* @param mixed $data Anything
* @param bool $no_html [default=false] If true strip all html tags
* @param bool $no_html [=false] If true strip all html tags
* (for text print)
* @return string A text string
*/
@@ -203,7 +203,7 @@ class Support
* exports (dumps) var, in more printable design, but without detail info
*
* @param mixed $data Anything
* @param bool $no_html If true true do not add <pre> tags
* @param bool $no_html [=false] If true true do not add <pre> tags
* @return string A text string
*/
public static function exportVar(mixed $data, bool $no_html = false): string
@@ -217,7 +217,7 @@ class Support
* Return file name and line number where this was called
* One level up
*
* @param int $level trace level, default 1
* @param int $level [=1] trace level
* @return string|null null or file name:line number
*/
public static function getCallerFileLine(int $level = 1): ?string
@@ -238,14 +238,14 @@ class Support
* eg for debugging, this function does this
*
* call this method in the child method and you get the parent function that called
* @param int $level trace level, default 1
* @return ?string null or the function that called the function
* where this method is called
* @param int $level [=1] trace level
* @return string|null null or the function that called the function
* where this method is called
*/
public static function getCallerMethod(int $level = 1): ?string
{
$traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
// print \CoreLibs\Debug\Support::printAr($traces);
// print "getCallerMethod:<br>" . \CoreLibs\Debug\Support::printAr($traces);
// We should check from top down if unset?
// sets the start point here, and in level two (the sub call) we find this
if (isset($traces[$level])) {
@@ -254,6 +254,41 @@ class Support
return null;
}
/**
* get the class that first called it and skip the base class
* Companion method to getCallerMethod
*
* @param int $level [=1] trace level
* @return ?string null if class not found
*/
public static function getCallerClass(int $level = 1): ?string
{
$traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
// print "getCallerClass:<br>" . \CoreLibs\Debug\Support::printAr($traces);
if (isset($traces[$level])) {
return $traces[$level]['class'] ?? null;
}
return null;
}
/**
* Returns class and method together
*
* @param int $level [=1] travel level
* @return string|null null if trace level not found, else namespace class and method
*/
public static function getCallerClassMethod(int $level = 1): ?string
{
$traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
// print "getCallerClass:<br>" . \CoreLibs\Debug\Support::printAr($traces);
if (isset($traces[$level])) {
return ($traces[$level]['class'] ?? '-')
. ($traces[$level]['type'] ?? '')
. $traces[$level]['function'];
}
return null;
}
/**
* Returns array with all methods in the call stack in the order so that last
* called is last in order
@@ -283,25 +318,21 @@ class Support
* Is mostly used in debug log statements to get the class where the debug
* was called
* gets top level class
* loops over the debug backtrace until if finds the first class (from the end)
* loops over the debug backtrace until if finds the first class (from the end)
*
* @return string Class name with namespace
*/
public static function getCallerClass(): string
public static function getCallerTopLevelClass(): string
{
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
// ?? [['class' => get_called_class()]];
// TODO make sure that this doesn't loop forver
$traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
// print "getCallerClass:<br>" . \CoreLibs\Debug\Support::printAr($traces);
$class = null;
while ($class === null && count($backtrace) > 0) {
// if current is
// [function] => debug
// [class] => CoreLibs\Debug\Logging
// then return
// (OUTSIDE) because it was not called from a class method
// or return file name
$get_class = array_pop($backtrace);
$class = $get_class['class'] ?? null;
// reverse and stop at first set class, this is the top level one
foreach (array_reverse($traces) as $trace) {
$class = $trace['class'] ?? null;
if (!empty($class)) {
break;
}
}
// on null or empty return empty string
return empty($class) ? '' : $class;

View File

@@ -381,7 +381,7 @@ class Logging
// auto set (should be deprecated in future)
$this->setLogFileId(
str_replace(':', '-', $this->host_name) . '_'
. str_replace('\\', '-', Support::getCallerClass())
. str_replace('\\', '-', Support::getCallerTopLevelClass())
);
}
if (empty($this->getLogFileId())) {
@@ -460,7 +460,7 @@ class Logging
// set per class, but don't use get_class as we will only get self
$rpl_string = !$this->getLogFlag(Flag::per_class) ? '' : '_'
// set sub class settings
. str_replace('\\', '-', Support::getCallerClass());
. str_replace('\\', '-', Support::getCallerTopLevelClass());
$fn = str_replace('{CLASS}', $rpl_string, $fn); // create output filename
// if request to write to one file
@@ -526,7 +526,10 @@ class Logging
/**
* Prepare the log message with all needed info blocks:
* [timestamp] [host name] [file path + file] [running uid] {class} <debug level/group id> - message
* [timestamp] [host name] [file path + file::row number] [running uid] {class::/->method}
* <debug level:debug group id> - message
* Note: group id is only for debug level
* if no method can be found or no class is found a - will be wirtten
*
* @param Level $level Log level we will write to
* @param string|Stringable $message The message to write
@@ -545,16 +548,32 @@ class Logging
if (!$this->checkLogLevel($level)) {
return '';
}
$file_line = '';
$caller_class_method = '-';
$traces = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
// print "[" . $level->getName() . "] [$message] prepareLog:<br>" . Support::printAr($traces);
// file + line: call not this but one before (the one that calls this)
$file_line = Support::getCallerFileLine(2) ??
System::getPageName(System::FULL_PATH);
// get the last class entry and wrie that
$class = Support::getCallerClass();
// method/function: prepareLog->(debug|info|...)->[THIS]
$method = Support::getCallerMethod(3);
if ($method !== null) {
$class .= '::' . $method;
// start from this level, if unset fall down until we are at null
$start_trace_level = 2;
for ($trace_level = $start_trace_level; $trace_level >= 0; $trace_level--) {
if (isset($traces[$trace_level])) {
$file_line = ($traces[$trace_level]['file'] ?? $traces[$trace_level]['function'])
. ':' . ($traces[$trace_level]['line'] ?? '-');
// as namespace\class->method
$caller_class_method =
// get the last call before we are in the Logging class
($traces[$trace_level]['class'] ?? '')
// connector, if unkown use ==
. ($traces[$trace_level]['type'] ?? '')
// method/function: prepareLog->(debug|info|...)->[THIS]
. $traces[$trace_level]['function'];
break;
}
}
if (empty($file_line)) {
$file_line = System::getPageName(System::FULL_PATH);
}
// print "CLASS: " . $class . "<br>";
// get timestamp
$timestamp = Support::printTime();
@@ -574,7 +593,7 @@ class Logging
. '[' . $this->host_name . '] '
. '[' . $file_line . '] '
. '[' . $this->running_uid . '] '
. '{' . $class . '} '
. '{' . $caller_class_method . '} '
. '<' . strtoupper($group_str) . '> '
. $message
. $context_str;

View File

@@ -0,0 +1,264 @@
<?php // phpcs:disable Generic.Files.LineLength
/**
* AUTOR: Clemens Schwaighofer
* CREATED: 2023/6/1
* DESCRIPTION:
* html builder: array
* static build for array lists (not objects)
*
* Recommended to use the Object one or for speed the String Replace
*/
namespace CoreLibs\Template\HtmlBuilder;
use CoreLibs\Template\HtmlBuilder\General\Settings;
use CoreLibs\Template\HtmlBuilder\General\Error;
use CoreLibs\Template\HtmlBuilder\General\HtmlBuilderExcpetion;
class Block
{
/**
* Undocumented function
*
* @param string $tag
* @param string $id
* @param string $content
* @param array<string> $css,
* @param array<string,string> $options
* @return array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>}
* @throws HtmlBuilderExcpetion
*/
public static function cel(
string $tag,
string $id = '',
string $content = '',
array $css = [],
array $options = []
): array {
if (!preg_match("/^[A-Za-z]+$/", $tag)) {
Error::setError(
'201',
'invalid or empty tag',
['tag' => $tag]
);
throw new HtmlBuilderExcpetion('Invalid or empty tag');
}
return [
'tag' => $tag,
'id' => $id,
'name' => $options['name'] ?? '',
'content' => $content,
'css' => $css,
'options' => $options,
'sub' => [],
];
}
/**
* Search element tree for id and add
* if id is empty add at current
*
* @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
* @param string $id
* @return array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>}
*/
public static function ael(
array $base,
array $attach,
string $id = ''
): array {
// no id or matching id
if (
empty($id) ||
$base['id'] == $id
) {
self::addSub($base, $attach);
return $base;
}
// find id in 'id' in all 'sub'
foreach ($base['sub'] as $el) {
$el = self::ael($el, $attach, $id);
}
return $base;
}
/**
* Undocumented function
*
* @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
* @return array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>}
*/
public static function aelx(
array $base,
array ...$attach
): array {
$base = self::addSub($base, ...$attach);
return $base;
}
/**
* Undocumented function
*
* @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
* @return array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>}
*/
public static function addSub(array $element, array ...$sub): array
{
if (!isset($element['sub'])) {
$element['sub'] = [];
}
array_push($element['sub'], ...$sub);
return $element;
}
/**
* Undocumented function
*
* @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>}
*/
public static function resetSub(array $element): array
{
$element['sub'] = [];
return $element;
}
// CSS Elements
/**
* Undocumented function
*
* @param array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>} $element
* @param string ...$css
* @return array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>}
*/
public static function acssel(array $element, string ...$css): array
{
$element['css'] = array_unique(array_merge($element['css'] ?? [], $css));
return $element;
}
/**
* Undocumented function
*
* @param array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>} $element
* @param string ...$css
* @return array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>}
*/
public static function rcssel(array $element, string ...$css): array
{
$element['css'] = array_diff($element['css'] ?? [], $css);
return $element;
}
/**
* Undocumented function
* scssel (switch) is not supported
* use rcssel -> acssel
*
* @param array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>} $element
* @param array<string> $rcss
* @param array<string> $acss
* @return array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>}
*/
public static function scssel(array $element, array $rcss, array $acss): array
{
return self::acssel(
self::rcssel($element, ...$rcss),
...$acss
);
}
/**
* Undocumented function
* alias 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 buildHtml(array $tree, bool $add_nl = false): string
{
if (empty($tree['tag'])) {
return '';
}
// print "D01: " . microtime(true) . "<br>";
$line = '<' . $tree['tag'];
if (!empty($tree['id'])) {
$line .= ' id="' . $tree['id'] . '"';
if (in_array($tree['tag'], Settings::NAME_ELEMENTS)) {
$line .= ' name="'
. (!empty($tree['name']) ? $tree['name'] : $tree['id'])
. '"';
}
}
if (count($tree['css'])) {
$line .= ' class="' . join(' ', $tree['css']) . '"';
}
foreach ($tree['options'] ?? [] as $key => $item) {
if (in_array($key, Settings::SKIP_OPTIONS)) {
continue;
}
$line .= ' ' . $key . '="' . $item . '"';
}
$line .= '>';
if (!empty($tree['content'])) {
$line .= $tree['content'];
}
// sub nodes
foreach ($tree['sub'] ?? [] as $sub) {
if ($add_nl === true) {
$line .= "\n";
}
$line .= self::buildHtml($sub, $add_nl);
if ($add_nl === true) {
$line .= "\n";
}
}
// close line if needed
if (!in_array($tree['tag'], Settings::NO_CLOSE)) {
$line .= '</' . $tree['tag'] . '>';
}
return $line;
}
/**
* Undocumented function
* alias phfa
*
* @param array<array{tag:string,id:string,name:string,content:string,css:array<string>,options:array<string,string>,sub:array<mixed>}> $list
* @param bool $add_nl [default=false]
* @return string
*/
public static function buildHtmlFromList(array $list, bool $add_nl = false): string
{
$output = '';
foreach ($list as $el) {
$output .= self::buildHtml($el, $add_nl);
}
return $output;
}
/**
* Undocumented function
* wrapper 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
* @return string build html as string
*/
public static function printHtmlFromArray(array $list, bool $add_nl = false): string
{
return self::buildHtmlFromList($list, $add_nl);
}
}
// __END__

View File

@@ -0,0 +1,559 @@
<?php
/**
* AUTOR: Clemens Schwaighofer
* CREATED: 2023/6/1
* DESCRIPTION:
* html builder: element
* nested and connected objects
*/
declare(strict_types=1);
namespace CoreLibs\Template\HtmlBuilder;
use CoreLibs\Template\HtmlBuilder\General\Settings;
use CoreLibs\Template\HtmlBuilder\General\Error;
use CoreLibs\Template\HtmlBuilder\General\HtmlBuilderExcpetion;
class Element
{
/** @var string */
private string $tag = '';
/** @var string */
private string $id = '';
/** @var string */
private string $name = '';
/** @var string */
private string $content = '';
/** @var array<string> */
private array $css = [];
/** @var array<string,mixed> */
private array $options = [];
/** @var array<Element> list of elements */
private array $sub = [];
/**
* create new html element
*
* @param string $tag html tag (eg div, button, etc)
* @param string $id html tag id, used also for name if name
* not set in $options
* @param string $content content text inside, eg <div>Content</div>
* if sub elements exist, they are added after content
* @param array<string> $css array of css names, put style in $options
* @param array<string,string> $options Additional element options in
* key = value format
* eg: onClick => 'something();'
* id, css are skipped
* name only set on input/button
* @throws HtmlBuilderExcpetion
*/
public function __construct(
string $tag,
string $id = '',
string $content = '',
array $css = [],
array $options = []
) {
// exit if not valid tag
try {
$this->setTag($tag);
} catch (HtmlBuilderExcpetion $e) {
throw new HtmlBuilderExcpetion('Could not create Element', 0, $e);
}
$this->setId($id);
$this->setName($options['name'] ?? '');
$this->setContent($content);
$this->addCss(...$css);
$this->setOptions($options);
}
/**
* set tag
*
* @param string $tag
* @return void
* @throws HtmlBuilderExcpetion
*/
public function setTag(string $tag): void
{
// tag must be letters only
if (!preg_match("/^[A-Za-z]+$/", $tag)) {
Error::setError(
'201',
'invalid or empty tag',
['tag' => $tag]
);
throw new HtmlBuilderExcpetion('Invalid or empty tag: ' . $tag);
}
$this->tag = $tag;
}
/**
* get the tag name
*
* @return string HTML element tag
*/
public function getTag(): string
{
return $this->tag;
}
/**
* set the element id
*
* @param string $id
* @return void
*/
public function setId(string $id): void
{
// invalid id and name check too
// be strict: [a-zA-Z0-9], -, _
// cannot start with digit, two hyphens or a hyphen with a digit:
// 0abc
// __abc
// _0abc
if (
!empty($id) &&
!preg_match("/^[A-Za-z][\w-]*$/", $id)
) {
Error::setWarning(
'202',
'possible invalid id',
['id' => $id, 'tag' => $this->getTag()]
);
// TODO: shoud throw error
}
$this->id = $id;
}
/**
* get the html tag id
*
* @return string HTML element id
*/
public function getId(): string
{
return $this->id;
}
/**
* Set name for elements
* only for elements that need it (input/button/form)
*
* @param string $name
* @return void
*/
public function setName(string $name): void
{
if (
!empty($name) &&
!preg_match("/^[A-Za-z][\w-]*$/", $name)
) {
Error::setWarning(
'203',
'possible invalid name',
['name' => $name, 'id' => $this->getId(), 'tag' => $this->getTag()]
);
// TODO: shoud throw error
}
$this->name = $name;
}
/**
* get the name if set
*
* @return string Optional HTML name (eg for input)
*/
public function getName(): string
{
return $this->name;
}
/**
* Set new content for element
*
* @param string $content
* @return void
*/
public function setContent(string $content): void
{
$this->content = $content;
}
/**
* get the elment text content (not sub elements)
*
* @return string HTML content text as is
*/
public function getContent(): string
{
return $this->content;
}
/**
* set or update options
*
* @param array<string,mixed> $options
* @return void
*/
public function setOptions(array $options): void
{
foreach ($options as $key => $value) {
if (empty($key)) {
Error::setError(
'110',
'Cannot set option with empty key',
['id' => $this->getId(), 'tag' => $this->getTag()]
);
// TODO: shoud throw error
continue;
}
// if data is null
if ($value === null) {
if (isset($this->options[$key])) {
unset($this->options[$key]);
} else {
Error::setError(
'210',
'Cannot set option with null value',
['id' => $this->getId(), 'tag' => $this->getTag()]
);
}
// TODO: shoud throw error
continue;
}
$this->options[$key] = $value;
}
}
/**
* get the options array
* also holds "name" option
* anything like: style, javascript, value or any other html tag option
* right side can be empty but not null
*
* @return array<string,string> get options as list html option name and value
*/
public function getOptions(): array
{
return $this->options;
}
// Sub Elements
/**
* get the sub elements (array of Elements)
*
* @return array<Element> Array of Elements (that can have sub elements)
*/
public function getSub(): array
{
return $this->sub;
}
/**
* add one or many sub elements (add at the end)
*
* @param Element $sub One or many elements to add
* @return void
* @throws HtmlBuilderExcpetion
*/
public function addSub(Element ...$sub): void
{
foreach ($sub as $_sub) {
// if one of the elements is the same as this class, ignore it
// with this we avoid self reference loop
if ($_sub == $this) {
Error::setError(
'100',
'Cannot assign Element to itself, this would create an infinite loop',
['id' => $this->getId(), 'tag' => $this->getTag()]
);
throw new HtmlBuilderExcpetion('Cannot assign Element to itself, this would create an infinite loop');
}
array_push($this->sub, $_sub);
}
}
/**
* Remove an element from the sub array
* By pos in array or id set on first level
*
* @param int|string $id String id name or int pos number in array
* @return void
*/
public function removeSub(int|string $id): void
{
// find element with id and remove it
// or when number find pos in sub and remove it
if (is_int($id)) {
if (!isset($this->sub[$id])) {
return;
}
unset($this->sub[$id]);
return;
}
// only on first level
foreach ($this->sub as $pos => $el) {
if (
$el->getId() === $id
) {
unset($this->sub[$pos]);
return;
}
}
}
/**
* remove all sub elements
*
* @return void
*/
public function resetSub(): void
{
$this->sub = [];
}
// CSS Elements
/**
* get the current set css elements
*
* @return array<string> list of css element entries
*/
public function getCss(): array
{
return $this->css;
}
/**
* add one or many new css elements
* Note that we can chain: add/remove/reset
*
* @param string ...$css one or more css strings to add
* @return Element Current element for chaining
*/
public function addCss(string ...$css): Element
{
// should do check for empty/invalid css
$_set_css = [];
foreach ($css as $_css) {
if (empty($_css)) {
Error::setError(
'204',
'cannot have empty css string',
);
// TODO: shoud throw error
continue;
}
// -?[_A-Za-z][_A-Za-z0-9-]*
if (!preg_match("/^-?[_A-Za-z][_A-Za-z0-9-]*$/", $_css)) {
Error::setWarning(
'205',
'possible invalid css string',
['css' => $_css, 'id' => $this->id, 'tag' => $this->tag]
);
// TODO: shoud throw error
}
$_set_css[] = $_css;
}
$this->css = array_unique(array_merge($this->css, $_set_css));
return $this;
}
/**
* remove one or more css elements
* Note that we can chain: add/remove/reset
*
* @param string ...$css one or more css strings to remove
* @return Element Current element for chaining
*/
public function removeCss(string ...$css): Element
{
$this->css = array_diff($this->css, $css);
return $this;
}
/**
* unset all css elements
* Note that we can chain: add/remove/reset
*
* @return Element
*/
public function resetCss(): Element
{
$this->css = [];
return $this;
}
// build output from tree
/**
* build html string from the current element tree (self)
* or from the Element tree given as parameter
* if $add_nl is set then new lines are added before each sub element added
* no indet is done (tab or other)
*
* @param Element|null $tree Different Element tree to build
* if not set (null), self is used
* @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
{
// print "D01: " . microtime(true) . "<br>";
if ($tree === null) {
$tree = $this;
}
$line = '<' . $tree->getTag();
if ($tree->getId()) {
$line .= ' id="' . $tree->getId() . '"';
if (in_array($tree->getTag(), Settings::NAME_ELEMENTS)) {
$line .= ' name="'
. (!empty($tree->getName()) ? $tree->getName() : $tree->getId())
. '"';
}
}
if (count($tree->getCss())) {
$line .= ' class="' . join(' ', $tree->getCss()) . '"';
}
foreach ($tree->getOptions() as $key => $item) {
// skip
if (in_array($key, Settings::SKIP_OPTIONS)) {
continue;
}
$line .= ' ' . $key . '="' . $item . '"';
}
$line .= '>';
if (strlen($tree->getContent()) > 0) {
$line .= $tree->getContent();
}
// sub nodes
foreach ($tree->getSub() as $sub) {
if ($add_nl === true) {
$line .= "\n";
}
$line .= $tree->buildHtml($sub, $add_nl);
if ($add_nl === true) {
$line .= "\n";
}
}
// close line if needed
if (!in_array($tree->getTag(), Settings::NO_CLOSE)) {
$line .= '</' . $tree->getTag() . '>';
}
return $line;
}
// this is static
/**
* Builds a single string from an array of elements
* a new line can be added before each new element if $add_nl is set to true
*
* @param array<Element> $list array of Elements, uses buildHtml internal
* @param bool $add_nl [default=false] Optional output string line breaks
* @return string HTML as string
*/
public static function buildHtmlFromList(array $list, bool $add_nl = false): string
{
$output = '';
foreach ($list as $el) {
if (!empty($output) && $add_nl === true) {
$output .= "\n";
}
$output .= $el->buildHtml();
}
return $output;
}
// so we can call builder statically
/**
* Search element tree for id and add
* if id is empty add at element given in parameter $base
*
* @param Element $base Element to attach to
* @param Element $attach Element to attach (single)
* @param string $id Optional id, if empty then attached at the end
* If set will loop through ALL sub elements until
* matching id found. if not found, not added
* @return Element Element with attached sub element
*/
public static function addElementWithId(
Element $base,
Element $attach,
string $id = ''
): Element {
// no id or matching id
if (
empty($id) ||
$base->getId() == $id
) {
$base->addSub($attach);
return $base;
}
// find id in 'id' in all 'sub'
foreach ($base->getSub() as $el) {
self::addElementWithId($el, $attach, $id);
}
return $base;
}
/**
* add one or more elemens to $base
*
* @param Element $base Element to attach to
* @param Element ...$attach Element or Elements to attach
* @return Element Element with attached sub elements
*/
public static function addElement(
Element $base,
Element ...$attach
): Element {
// we must make sure we do not self attach
$base->addSub(...$attach);
return $base;
}
/**
* Static call version for building
* not recommended to be used, rather use "Element->buildHtml()"
* wrapper for buildHtml
*
* @param ?Element $tree Element tree to build
* if not set returns empty string
* @param bool $add_nl [default=false] Optional output string line break
* @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
{
// nothing ->bad
if ($tree === null) {
return '';
}
return $tree->buildHtml(add_nl: $add_nl);
}
/**
* Undocumented function
* wrapper for buildHtmlFromList
*
* @param array<Element> $list array of Elements to build string from
* @param bool $add_nl [default=false] Optional output string line break
* @return string build html as string
*/
public static function printHtmlFromArray(array $list, bool $add_nl = false): string
{
return self::buildHtmlFromList($list, $add_nl);
}
}
// __END__

View File

@@ -0,0 +1,127 @@
<?php
/**
* AUTOR: Clemens Schwaighofer
* CREATED: 2023/6/27
* DESCRIPTION:
* Error logging for the HtmlBuilder systs
*/
declare(strict_types=1);
namespace CoreLibs\Template\HtmlBuilder\General;
class Error
{
/** @var array<array{level:string,id:string,message:string,context:array<mixed>}> */
private static array $messages = [];
/**
* internal writer for messages
*
* @param string $level
* @param string $id
* @param string $message
* @param array<mixed> $context
* @return void
*/
private static function writeContent(
string $level,
string $id,
string $message,
array $context
): void {
self::$messages[] = [
'level' => $level,
'id' => $id,
'message' => $message,
'context' => $context,
];
}
/**
* warning collector for all internal string errors
* builds an warning with warning id, message text and array with optional content
*
* @param string $id
* @param string $message
* @param array<mixed> $context
* @return void
*/
public static function setWarning(string $id, string $message, array $context = []): void
{
self::writeContent('Warning', $id, $message, $context);
}
/**
* error collector for all internal string errors
* builds an error with error id, message text and array with optional content
*
* @param string $id
* @param string $message
* @param array<mixed> $context
* @return void
*/
public static function setError(string $id, string $message, array $context = []): void
{
self::writeContent('Error', $id, $message, $context);
}
/**
* Return all set errors
*
* @return array<mixed>
*/
public static function getMessages(): array
{
return self::$messages;
}
/**
* Reset all errors
*
* @return void
*/
public static function resetMessages(): void
{
self::$messages = [];
}
/**
* internal level in message array exists check
*
* @param string $level
* @return bool
*/
private static function hasLevel(string $level): bool
{
return array_filter(
self::$messages,
function ($var) use ($level) {
return $var['level'] == $level ? true : false;
}
) === [] ? false : true;
}
/**
* Check if any error is set
*
* @return bool
*/
public static function hasError(): bool
{
return self::hasLevel('Error');
}
/**
* Check if any warning is set
*
* @return bool
*/
public static function hasWarning(): bool
{
return self::hasLevel('Warning');
}
}
// __END__

View File

@@ -0,0 +1,21 @@
<?php
/**
* AUTOR: Clemens Schwaighofer
* CREATED: 2023/6/28
* DESCRIPTION:
* Exception class for the HtmlBuilder blocks
*/
declare(strict_types=1);
namespace CoreLibs\Template\HtmlBuilder\General;
/**
* Exception class for HtmlBuilder
*/
class HtmlBuilderExcpetion extends \Exception
{
}
// __END__

View File

@@ -0,0 +1,69 @@
<?php
/**
* AUTOR: Clemens Schwaighofer
* CREATED: 2023/7/22
* DESCRIPTION:
* General settings for html elements
*/
declare(strict_types=1);
namespace CoreLibs\Template\HtmlBuilder\General;
class Settings
{
/** @var array<string> list of html elements that can have the name tag */
public const NAME_ELEMENTS = [
'button',
'fieldset',
'form',
'iframe',
'input',
'map',
'meta',
'object',
'output',
'param',
'select',
'textarea',
];
/** @var array<string> options key entries to be skipped in build */
public const SKIP_OPTIONS = [
'id',
'name',
'class',
];
/** @var array<string> html elements that don't need to be closed */
public const NO_CLOSE = [
'input',
'br',
'img',
'hr',
'area',
'col',
'keygen',
'wbr',
'track',
'source',
'param',
'command',
// only in header
'base',
'meta',
'link',
'embed',
];
/** @var array<string> invalid tags, not allowed in body */
public const NOT_IN_BODY_ALLOWED = [
'base',
'meta',
'link',
'embed', // not sure
];
}
// __END__

View File

@@ -0,0 +1,194 @@
<?php
/**
* AUTOR: Clemens Schwaighofer
* CREATED: 2023/6/23
* DESCRIPTION:
* Simeple string replace calls for elements
*/
declare(strict_types=1);
namespace CoreLibs\Template\HtmlBuilder;
use CoreLibs\Template\HtmlBuilder\General\Error;
use CoreLibs\Template\HtmlBuilder\General\HtmlBuilderExcpetion;
class StringReplace
{
/** @var array<string,string> */
private static array $elements = [];
/** @var array<string,string> */
private static array $replace = [];
/**
* load html blocks into array for repeated usage
* each array group parameter has 0: index, 1: content
* There is no content check done.
* index must be non empty (but has no fixed format)
* if same index is tried twice it will set an error and skip
*
* @param array{0:string,1:string} ...$element Elements to load
* @return void
* @throws HtmlBuilderExcpetion
*/
public static function loadElements(array ...$element): void
{
foreach ($element as $el) {
$index = $el[0] ?? '';
if (empty($index)) {
Error::setError(
'310',
'Index cannot be an empty string',
[
'element' => $index
]
);
throw new HtmlBuilderExcpetion('Index cannot be an empty string');
}
if (isset(self::$elements[$index])) {
Error::setError(
'311',
'Index already exists',
[
'element' => $index
]
);
throw new HtmlBuilderExcpetion('Index already exists: ' . $index);
}
// content check?
self::$elements[$index] = $el[1];
}
}
/**
* update an element at index
* can also be used to reset (empty string)
*
* @param string $index
* @param string $element
* @return void
*/
public static function updateElement(string $index, string $element): void
{
if (!isset(self::$elements[$index])) {
Error::setError(
'312',
'Index does not exists',
[
'element' => $index
]
);
throw new HtmlBuilderExcpetion('Index does not exists: ' . $index);
}
// allow empty reset
self::$elements[$index] = $element;
}
/**
* get an element block at index
* if not found will return false
*
* @param string $index
* @return string
* @throws HtmlBuilderExcpetion
*/
public static function getElement(string $index): string
{
if (!isset(self::$elements[$index])) {
Error::setError('321', 'Index not found in elements', ['element' => $index]);
throw new HtmlBuilderExcpetion('Index not found in elements array: ' . $index);
}
return self::$elements[$index];
}
/**
* set a replacement block at index
* can be used for setting one block and using it agai
*
* @param string $index
* @param string $content
* @return void
*/
public static function setReplaceBlock(string $index, string $content): void
{
self::$replace[$index] = $content;
}
/**
* get replacement block at index, if not found return empty and set error
*
* @param string $index
* @return string
* @throws HtmlBuilderExcpetion
*/
public static function getReplaceBlock(string $index): string
{
if (!isset(self::$replace[$index])) {
Error::setError('331', 'Index not found in replace block', ['replace' => $index]);
throw new HtmlBuilderExcpetion('Index not found in replace block array: ' . $index);
}
return self::$replace[$index];
}
/**
* build and element on an index and either returns it or also sets it
* into the replace block array
* if index not found in relement list will return false
*
* @param string $index index of set element
* @param array<string,string> $replace array of text to search (key) and replace (value) for
* @return string
* @throws HtmlBuilderExcpetion
*/
public static function buildElement(
string $index,
array $replace,
string $replace_index = ''
): string {
try {
self::getElement($index);
} catch (HtmlBuilderExcpetion $e) {
throw new HtmlBuilderExcpetion('Cannot fetch element with index: ' . $index, 0, $e);
}
if ($replace_index) {
self::setReplaceBlock(
$replace_index,
self::replaceData(self::$elements[$index], $replace)
);
return self::getReplaceBlock($replace_index);
} else {
return self::replaceData(self::$elements[$index], $replace);
}
}
/**
* main replace entries in text string
* elements to be replaced are in {} brackets. if they are missing in the
* replace array they will be added.
* if the replace and content count is not the same then an error will be thrown
*
* @param string $data
* @param array<string,string> $replace
* @return string
* @throws HtmlBuilderExcpetion
*/
public static function replaceData(string $data, array $replace): string
{
$to_replace = array_keys($replace);
// all replace data must have {} around
array_walk($to_replace, function (&$entry) {
if (!str_starts_with($entry, '{')) {
$entry = '{' . $entry;
}
if (!str_ends_with($entry, '}')) {
$entry .= '}';
}
// do some validation?
});
// replace content
return str_replace($to_replace, array_values($replace), $data);
}
}
// __END__

View File

@@ -458,6 +458,47 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
];
}
/**
* Undocumented function
*
* @return array
*/
public function dateRangeHasWeekendProvider(): array
{
return [
'no weekend' => [
'2023-07-03',
'2023-07-04',
false
],
'start weekend sat' => [
'2023-07-01',
'2023-07-04',
true
],
'start weekend sun' => [
'2023-07-02',
'2023-07-04',
true
],
'end weekend sat' => [
'2023-07-03',
'2023-07-08',
true
],
'end weekend sun' => [
'2023-07-03',
'2023-07-09',
true
],
'long period > 6 days' => [
'2023-07-03',
'2023-07-27',
true
]
];
}
/**
* date string convert test
*
@@ -780,6 +821,29 @@ final class CoreLibsCombinedDateTimeTest extends TestCase
$output
);
}
/**
* Undocumented function
*
* @covers ::dateRangeHasWeekend
* @dataProvider dateRangeHasWeekendProvider
* @testdox dateRangeHasWeekend $start_date and $end_date are expected weekend $expected [$_dataName]
*
* @param string $start_date
* @param string $end_date
* @param bool $expected
* @return void
*/
public function testDateRangeHasWeekend(
string $start_date,
string $end_date,
bool $expected
): void {
$this->assertEquals(
$expected,
\CoreLibs\Combined\DateTime::dateRangeHasWeekend($start_date, $end_date)
);
}
}
// __END__

View File

@@ -16,7 +16,7 @@ final class CoreLibsConvertJsonTest extends TestCase
/**
* test list for json convert tests
*
* @return array
* @return array<mixed>
*/
public function jsonProvider(): array
{
@@ -54,10 +54,36 @@ final class CoreLibsConvertJsonTest extends TestCase
];
}
/**
* Undocumented function
*
* @return array<mixed>
*/
public function jsonArrayProvider(): array
{
return [
'valid json' => [
[
'm' => 2,
'f' => 'sub_2'
],
'{"m":2,"f":"sub_2"}',
],
'empty json array' => [
[],
'[]'
],
'empty json hash' => [
['' => ''],
'{"":""}'
]
];
}
/**
* json error list
*
* @return array JSON error list
* @return array<mixed> JSON error list
*/
public function jsonErrorProvider(): array
{
@@ -127,7 +153,7 @@ final class CoreLibsConvertJsonTest extends TestCase
*
* @param string|null $input
* @param bool $flag
* @param array $expected
* @param array<mixed> $expected
* @return void
*/
public function testJsonConvertToArray(?string $input, bool $flag, array $expected): void
@@ -146,7 +172,8 @@ final class CoreLibsConvertJsonTest extends TestCase
* @testdox jsonGetLastError $input will be $expected_i/$expected_s [$_dataName]
*
* @param string|null $input
* @param string $expected
* @param int $expected_i
* @param string $expected_s
* @return void
*/
public function testJsonGetLastError(?string $input, int $expected_i, string $expected_s): void
@@ -161,6 +188,25 @@ final class CoreLibsConvertJsonTest extends TestCase
\CoreLibs\Convert\Json::jsonGetLastError(true)
);
}
/**
* Undocumented function
*
* @covers ::jsonConvertArrayTo
* @dataProvider jsonArrayProvider
* @testdox jsonConvertArrayTo $input (Override: $flag) will be $expected [$_dataName]
*
* @param array<mixed> $input
* @param string $expected
* @return void
*/
public function testJsonConvertArrayto(array $input, string $expected): void
{
$this->assertEquals(
$expected,
\CoreLibs\Convert\Json::jsonConvertArrayTo($input)
);
}
}
// __END__

View File

@@ -562,10 +562,10 @@ final class CoreLibsDebugSupportTest extends TestCase
}
/**
* Undocumented function
* test the lowest one (one above base)
*
* @cover ::getCallerClass
* @testWith ["PHPUnit\\TextUI\\Command"]
* @testWith ["tests\\CoreLibsDebugSupportTest"]
* @testdox getCallerClass check if it returns $expected [$_dataName]
*
* @return void
@@ -578,6 +578,40 @@ final class CoreLibsDebugSupportTest extends TestCase
);
}
/**
* test highest return (top level)
*
* @cover ::getCallerTopLevelClass
* @testWith ["PHPUnit\\TextUI\\Command"]
* @testdox getCallerTopLevelClass check if it returns $expected [$_dataName]
*
* @return void
*/
public function testGetCallerTopLevelClass(string $expected): void
{
$this->assertEquals(
$expected,
Support::getCallerTopLevelClass()
);
}
/**
* test highest return (top level)
*
* @cover ::getCallerClassMethod
* @testWith ["tests\\CoreLibsDebugSupportTest->testGetCallerClassMethod"]
* @testdox getCallerClassMethod check if it returns $expected [$_dataName]
*
* @return void
*/
public function testGetCallerClassMethod(string $expected): void
{
$this->assertEquals(
$expected,
Support::getCallerClassMethod()
);
}
/**
* Undocumented function
*

View File

@@ -22,7 +22,7 @@ final class CoreLibsLoggingLoggingTest extends TestCase
. "\[[\w\.]+(:\d+)?\]\s{1}" // host:port
. "\[[\w\-\.\/]+:\d+\]\s{1}" // folder/file
. "\[\w+\]\s{1}" // run id
. "{[\w\\\\]+(::\w+)?}\s{1}"; // class
. "{[\w\\\\]+((::|->)\w+)?}\s{1}"; // class
public static function tearDownAfterClass(): void
{

View File

@@ -13,7 +13,7 @@ use CoreLibs\Security\SymmetricEncryption;
* @coversDefaultClass \CoreLibs\Security\SymmetricEncryption
* @testdox \CoreLibs\Security\SymmetricEncryption method tests
*/
final class CoreLibsSecuritySymmetricEncryption extends TestCase
final class CoreLibsSecuritySymmetricEncryptionTest extends TestCase
{
/**
* Undocumented function

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
use CoreLibs\Template\HtmlBuilder\Block;
/**
* Test class for Template\HtmlBuilder\Block
* @coversDefaultClass \CoreLibs\Template\HtmlBuilder\Block
* @testdox \CoreLibs\Template\HtmlBuilder\Block method tests
*/
final class CoreLibsTemplateHtmlBuilderBlockTest extends TestCase
{
public function testCreateBlock(): void
{
$el = Block::cel('div', 'id', 'content', ['css'], ['onclick' => 'foo();']);
$this->assertEquals(
'<div id="id" class="css" onclick="foo();">content</div>',
Block::buildHtml($el)
);
}
// ael
// aelx|addSub
// resetSub
// acssel/rcssel/scssel
// buildHtml
// buildHtmlFromList|printHtmlFromArray
}
// __END__

View File

@@ -0,0 +1,546 @@
<?php
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
use CoreLibs\Template\HtmlBuilder\Element;
use CoreLibs\Template\HtmlBuilder\General\Error;
use CoreLibs\Template\HtmlBuilder\General\HtmlBuilderExcpetion;
/**
* Test class for Template\HtmlBuilder\Element
* @coversDefaultClass \CoreLibs\Template\HtmlBuilder\Element
* @testdox \CoreLibs\Template\HtmlBuilder\Element method tests
*/
final class CoreLibsTemplateHtmlBuilderElementTest extends TestCase
{
public function providerCreateElements(): array
{
return [
'simple div' => [
'tag' => 'div',
'id' => 'id',
'content' => 'content',
'css' => ['css'],
'options' => ['onclick' => 'foo();'],
'expected' => '<div id="id" class="css" onclick="foo();">content</div>'
],
'simple input' => [
'tag' => 'input',
'id' => 'id',
'content' => null,
'css' => ['css'],
'options' => ['name' => 'name', 'onclick' => 'foo();'],
'expected' => '<input id="id" name="name" class="css" onclick="foo();">'
]
];
}
/**
* Undocumented function
*
* @covers ::Element
* @covers ::buildHtml
* @covers ::getTag
* @covers ::getId
* @covers ::getContent
* @covers ::getOptions
* @covers ::getCss
* @dataProvider providerCreateElements
* @testdox create single new Element test [$_dataName]
*
* @param string $tag
* @param string|null $id
* @param string|null $content
* @param array|null $css
* @param array|null $options
* @param string $expected
* @return void
*/
public function testCreateElement(
string $tag,
?string $id,
?string $content,
?array $css,
?array $options,
string $expected
): void {
$el = new Element($tag, $id ?? '', $content ?? '', $css ?? [], $options ?? []);
$this->assertEquals(
$expected,
$el->buildHtml(),
'element creation failed'
);
$this->assertEquals(
$tag,
$el->getTag(),
'get tag failed'
);
if ($id !== null) {
$this->assertEquals(
$id,
$el->getId(),
'get id failed'
);
}
if ($content !== null) {
$this->assertEquals(
$content,
$el->getContent(),
'get content failed'
);
}
if ($css !== null) {
$this->assertEquals(
$css,
$el->getCss(),
'get css failed'
);
}
if ($options !== null) {
$this->assertEquals(
$options,
$el->getOptions(),
'get options failed'
);
}
if (!empty($options['name'])) {
$this->assertEquals(
$options['name'],
$el->getName(),
'get name failed'
);
}
}
/**
* css add/remove
*
* @cover ::getCss
* @cover ::addCss
* @cover ::removeCss
* @testdox test handling of adding and removing css classes
*
* @return void
*/
public function testCssHandling(): void
{
$el = new Element('div', 'css-test', 'CSS content');
$this->assertEqualsCanonicalizing(
[],
$el->getCss(),
'check empty css'
);
$el->addCss('foo');
$this->assertEqualsCanonicalizing(
['foo'],
$el->getCss(),
'check added one css'
);
$el->removeCss('foo');
$this->assertEqualsCanonicalizing(
[],
$el->getCss(),
'check remove added css'
);
// add serveral
// remove some of them
$el->addCss('a', 'b', 'c');
$this->assertEqualsCanonicalizing(
['a', 'b', 'c'],
$el->getCss(),
'check added some css'
);
$el->removeCss('a', 'c');
// $this->assertArray
$this->assertEqualsCanonicalizing(
['b'],
$el->getCss(),
'check remove some css'
);
// chained add and remove
$el->addCss('a', 'b', 'c', 'd')->removeCss('b', 'd');
$this->assertEqualsCanonicalizing(
['a', 'c'],
$el->getCss(),
'check chain add remove some css'
);
$el->resetCss();
$this->assertEqualsCanonicalizing(
[],
$el->getCss(),
'check reset css'
);
// remove something that does not eixst
$el->addCss('exists');
$el->removeCss('not');
$this->assertEqualsCanonicalizing(
['exists'],
$el->getCss(),
'check remove not exitsing'
);
}
/**
* nested test
*
* @testdox nested test and loop assign detection
*
* @return void
*/
public function testBuildNested(): void
{
Error::resetMessages();
$el = new Element('div', 'build-test');
$el_sub = new Element('div', 'sub-1');
$el->addSub($el_sub);
$this->assertEquals(
'<div id="build-test"><div id="sub-1"></div></div>',
$el->buildHtml(),
'nested build failed'
);
// this would create a loop, throws error
$this->expectException(HtmlBuilderExcpetion::class);
$this->expectExceptionMessage("Cannot assign Element to itself, this would create an infinite loop");
$el_sub->addSub($el_sub);
$this->assertEquals(
'<div id="sub-1"></div>',
$el_sub->buildHtml(),
'loop detection failed'
);
$this->assertTrue(
Error::hasError(),
'failed to throw error post loop detection'
);
$this->assertEquals(
[[
'level' => 'Error',
'id' => '100',
'message' => 'Cannot assign Element to itself, this would create an infinite loop',
'context' => ['tag' => 'div', 'id' => 'sub-1']
]],
Error::getMessages(),
'check error is 100 failed'
);
// get sub
$this->assertEquals(
[$el_sub],
$el->getSub(),
'get sub failed'
);
// reset sub
$el->resetSub();
$this->assertEquals(
[],
$el->getSub(),
'reset sub failed'
);
}
/**
* Undocumented function
*
* @testdox updated nested connection
*
* @return void
*/
public function testNestedChangeContent(): void
{
$el = new Element('div', 'build-test');
$el_s_1 = new Element('div', 'sub-1');
$el_s_2 = new Element('div', 'sub-2');
$el_s_3 = new Element('div', 'sub-3');
$el_s_4 = new Element('div', 'sub-4');
$el->addSub($el_s_1, $el_s_2);
// only sub -1, -2
$this->assertEquals(
'<div id="build-test"><div id="sub-1"></div><div id="sub-2"></div></div>',
$el->buildHtml(),
'check basic nested'
);
// now add -3, -4 to both -1 and -2
$el_s_1->addSub($el_s_3, $el_s_4);
$el_s_2->addSub($el_s_3, $el_s_4);
$this->assertEquals(
'<div id="build-test"><div id="sub-1"><div id="sub-3"></div><div id="sub-4">'
. '</div></div><div id="sub-2"><div id="sub-3"></div><div id="sub-4"></div>'
. '</div></div>',
$el->buildHtml(),
'check nested added'
);
// now add some css to el_s_3, will update in both sets
$el_s_3->addCss('red');
$this->assertEquals(
'<div id="build-test"><div id="sub-1"><div id="sub-3" class="red"></div><div id="sub-4">'
. '</div></div><div id="sub-2"><div id="sub-3" class="red"></div><div id="sub-4"></div>'
. '</div></div>',
$el->buildHtml(),
'check nested u@dated'
);
}
/**
* Undocumented function
*
* @testdox test change tag/id/content
*
* @return void
*/
public function testChangeElementData(): void
{
$el = new Element('div', 'id', 'Content');
// content change
$this->assertEquals(
'Content',
$el->getContent(),
'set content'
);
$el->setContent('New Content');
$this->assertEquals(
'New Content',
$el->getContent(),
'changed content'
);
$this->assertEquals(
'div',
$el->getTag(),
'set tag'
);
$el->setTag('span');
$this->assertEquals(
'span',
$el->getTag(),
'changed tag'
);
$this->assertEquals(
'id',
$el->getId(),
'set id'
);
$el->setId('id-2');
$this->assertEquals(
'id-2',
$el->getId(),
'changed id'
);
}
/**
* Undocumented function
*
* @testdox test change options
*
* @return void
*/
public function testChangeOptions(): void
{
$el = new Element('button', 'id', 'Action', ['css'], ['value' => '3']);
$this->assertEquals(
['value' => '3'],
$el->getOptions(),
'set option'
);
$el->setOptions([
'value' => '2'
]);
$this->assertEquals(
['value' => '2'],
$el->getOptions(),
'changed option'
);
// add a new one
$el->setOptions([
'Foo' => 'bar',
'Moo' => 'cow'
]);
$this->assertEquals(
[
'value' => '2',
'Foo' => 'bar',
'Moo' => 'cow'
],
$el->getOptions(),
'changed option'
);
}
// build output
// build from array list
/**
* Undocumented function
*
* @testdox build element tree from object
*
* @return void
*/
public function testBuildHtmlObject(): void
{
// build a simple block
// div -> div -> button
// -> div -> span
// -> div -> input
$el = new Element('div', 'master', '', ['master']);
$el->addSub(
Element::addElement(
new Element('div', 'div-button', '', ['dv-bt']),
new Element('button', 'button-id', 'Click me', ['bt-red'], [
'OnClick' => 'action();',
'value' => 'click',
'type' => 'button'
]),
),
Element::addElement(
new Element('div', 'div-span', '', ['dv-sp']),
new Element('span', 'span-id', 'Big important message<br>other', ['red']),
),
Element::addElement(
new Element('div', 'div-input', '', ['dv-in']),
new Element('input', 'input-id', '', ['in-blue'], [
'OnClick' => 'otherAction();',
'value' => 'Touch',
'type' => 'button'
]),
),
);
$this->assertEquals(
'<div id="master" class="master">'
. '<div id="div-button" class="dv-bt">'
. '<button id="button-id" name="button-id" class="bt-red" OnClick="action();" '
. 'value="click" type="button">Click me</button>'
. '</div>'
. '<div id="div-span" class="dv-sp">'
. '<span id="span-id" class="red">Big important message<br>other</span>'
. '</div>'
. '<div id="div-input" class="dv-in">'
. '<input id="input-id" name="input-id" '
. 'class="in-blue" OnClick="otherAction();" value="Touch" type="button">'
. '</div>'
. '</div>',
$el->buildHtml()
);
}
/**
* Undocumented function
*
* @testdox build elements from array list
*
* @return void
*/
public function testbuildHtmlArray(): void
{
$this->assertEquals(
'<div id="id-1">A</div>'
. '<div id="id-2">B</div>'
. '<div id="id-3">C</div>',
Element::buildHtmlFromList([
new Element('div', 'id-1', 'A'),
new Element('div', 'id-2', 'B'),
new Element('div', 'id-3', 'C'),
])
);
}
/**
* Undocumented function
*
* @testdox check for invalid tag detection, possible invalid id, possible invalid css
*
* @return void
*/
public function testInvalidElement(): void
{
Error::resetMessages();
$this->expectException(HtmlBuilderExcpetion::class);
$this->expectExceptionMessage("Could not create Element");
$el = new Element('');
$this->assertTrue(
Error::hasError(),
'failed to set error invalid tag detection'
);
$this->assertEquals(
[[
'level' => 'Error',
'id' => '201',
'message' => 'invalid or empty tag',
'context' => ['tag' => '']
]],
Error::getMessages(),
'check error message failed'
);
// if we set invalid tag
$el = new Element('div');
$this->expectException(HtmlBuilderExcpetion::class);
$this->expectExceptionMessageMatches("/^Invalid or empty tag: /");
$this->expectExceptionMessage("Invalid or empty tag: 123123");
$el->setTag('123123');
$this->assertTrue(
Error::hasError(),
'failed to set error invalid tag detection'
);
$this->assertEquals(
[[
'level' => 'Error',
'id' => '201',
'message' => 'invalid or empty tag',
'context' => ['tag' => '']
]],
Error::getMessages(),
'check error message failed'
);
// invalid id (warning)
Error::resetMessages();
$el = new Element('div', '-$a15');
$this->assertTrue(
Error::hasWarning(),
'failed to set warning invalid id detection'
);
$this->assertEquals(
[[
'level' => 'Warning',
'id' => '202',
'message' => 'possible invalid id',
'context' => ['id' => '-$a15', 'tag' => 'div']
]],
Error::getMessages(),
'check error message failed'
);
// invalid name
Error::resetMessages();
$el = new Element('div', 'valid', '', [], ['name' => '-$asdf&']);
$this->assertTrue(
Error::hasWarning(),
'failed to set warning invalid name detection'
);
$this->assertEquals(
[[
'level' => 'Warning',
'id' => '203',
'message' => 'possible invalid name',
'context' => ['name' => '-$asdf&', 'id' => 'valid', 'tag' => 'div']
]],
Error::getMessages(),
'check error message failed'
);
}
// static add element
// print object/array
}
// __END__

View File

@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace tests;
use PHPUnit\Framework\TestCase;
use CoreLibs\Template\HtmlBuilder\StringReplace;
/**
* Test class for Template\HtmlBuilder\StringReplace
* @coversDefaultClass \CoreLibs\Template\HtmlBuilder\StringReplace
* @testdox \CoreLibs\Template\HtmlBuilder\StringReplace method tests
*/
final class CoreLibsTemplateHtmlBuilderStringReplaceTest extends TestCase
{
/**
* Undocumented function
*
* @covers ::replaceData
* @testdox test basic replaceData
*
* @return void
*/
public function testReplaceData(): void
{
$html_block = <<<HTML
<div id="{ID}" class="{CSS}">
{CONTENT}
</div>
HTML;
$this->assertEquals(
<<<HTML
<div id="block-id" class="blue,red">
Some content here<br>with bla bla inside
</div>
HTML,
StringReplace::replaceData(
$html_block,
[
'ID' => 'block-id',
'CSS' => join(',', ['blue', 'red']),
'{CONTENT}' => 'Some content here<br>with bla bla inside',
]
)
);
}
/**
* Undocumented function
*
* @testdox replaceData error
*
* @return void
*/
/* public function testReplaceDataErrors(): void
{
$this->expectException(HtmlBuilderExcpetion::class);
$this->expectExceptionMessage("Replace and content array count differ");
StringReplace::replaceData('<span>{FOO}</span>', ['{FOO}', '{BAR}'], ['foo']);
} */
}
// __END__