Compare commits

...

21 Commits

Author SHA1 Message Date
Clemens Schwaighofer
5f2668b011 ACL\Login
Remove log per class flag set inside Login.
If per class logging is needed here, set that BEFORE and AFTER the class
call
2023-07-21 19:03:24 +09:00
Clemens Schwaighofer
eba1ef9c59 Init DB\IO dbh with null to avoid any problems
There could have been some problems where the dbh var was not touched
even thought it was inited.
2023-07-21 17:48:09 +09:00
Clemens Schwaighofer
8497144053 Admin\Backend level check, DB\IO Error/Warning message update
on Admin\Backend init check that the provided default acl level is valid

DB\IO warning and error drop the "db :" prefix part as this is not needed
we have [DB_ERROR] and [DB_WARNING] sub prefixes anyway, also we run
dedicated log level alerts with context
2023-07-14 15:01:46 +09:00
Clemens Schwaighofer
2006798388 Init set empty db config if db config not found 2023-07-10 08:18:49 +09:00
Clemens Schwaighofer
bf63d850ca Add new DateTime class has date range weekened method
dateRangeHasWeekend with two dates, checks if between those two dates
a weekend (sat or sun) is set
2023-07-04 11:43:27 +09:00
Clemens Schwaighofer
53e267ce24 Updates and fixes from phan/phpstan runs 2023-06-28 15:30:08 +09:00
Clemens Schwaighofer
1754ecf2ee HtmlBuilder: change all return error to Throw, update Element/StrinReplace
We do not return old style bool on error, we throw Exceptions: HtmlBuilderExcpetion

Element has more classes to set tag, id, etc with basic checks for valid data

String Replace to set strings is one array with key -> value entries

Errors thrown on index for element/replace blocks
2023-06-28 14:16:44 +09:00
Clemens Schwaighofer
3c37899a48 Rename Replace to StringReplace to match the actual content
Replace is too general. it is String Replace
2023-06-27 18:33:04 +09:00
Clemens Schwaighofer
0436cfe3da HtmlBuilder classes for Object, Array, String Replace build
Object build is a replicata from the JS one
Array is similar but build on pure Array elements
String replace is just a simple string replacer for now

General\Error for overall error handling
General\Settings for Object/Array based checks and settings
2023-06-27 18:30:26 +09:00
Clemens Schwaighofer
3606de1a00 Back add some checks for the phfo function that matches the php one 2023-06-27 18:30:00 +09:00
Clemens Schwaighofer
3081439eda Switch edit_base CSS name from ADMIN_STYLESHEET to EDIT_BASE_STYLESHEET 2023-06-27 18:29:28 +09:00
Clemens Schwaighofer
7af0e74b85 Fix phpUnit test name for Security\SymmetricEncryption 2023-06-26 14:25:36 +09:00
Clemens Schwaighofer
7748b83a6b DB\IO add stack trace to debug/error/warning calls
To add the actuall call reference for DB IO debug calls we add the
call trace as context options
2023-06-16 13:17:49 +09:00
Clemens Schwaighofer
f83293ff1a Info comments for DB\IO convert options 2023-06-13 11:47:38 +09:00
Clemens Schwaighofer
9c3be2942e phan and phpstan fixes, also add a convert flag reset to original
dbResetConvertFlag resets to the settings given on init of class
2023-06-09 18:23:28 +09:00
Clemens Schwaighofer
ee62bd98ee Add auto type convert for DB\IO
set via db options "db_convert_type" as array with "on", "json", "numeric",
"bytea"

"on" only converts know good types: "bool", "int"

"json" will convert json/jsonb to array
"bytea" will decode escaped bytea to string (note: this might change to resource)

"numeric" will convert to float.
NOTE: if a numeric number is too large a covnersion might drop data.
Use with care.

Convert flags can be chagned with dbSetConvertFlag and dbUnsetConvertFlag

All convert flags are in "DB\Options\Convert" as enum.
2023-06-09 17:01:03 +09:00
Clemens Schwaighofer
02e9610fad Add a general log method to Logger class
the params order is the actual correct one:
log level, message, context, group_id, prefix

not that group_id and prefix are only used if log level is debug

Switched debug params order for context and prefix so prefix is last
2023-06-09 16:59:22 +09:00
Clemens Schwaighofer
8a41db4649 Output\Image cleanup for phpstan 2023-06-09 12:16:49 +09:00
Clemens Schwaighofer
e27ea3dc9f DB\IO phpdoc update 2023-06-09 10:24:26 +09:00
Clemens Schwaighofer
ec4bf54d81 DB\IO phpdoc layout update
Add line between params and method description
2023-06-09 10:21:02 +09:00
Clemens Schwaighofer
ec3ca787fa Logging: prepare message only if log level is high enough
Also some clean ups on internal method call parameters
2023-06-05 09:30:26 +09:00
40 changed files with 3471 additions and 125 deletions

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

@@ -38,6 +38,7 @@ namespace tests;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\MockObject\MockObject;
use CoreLibs\Logging\Logger\Level;
use CoreLibs\DB\Options\Convert;
/**
* Test class for DB\IO + DB\SQL\PgSQL
@@ -4557,6 +4558,176 @@ final class CoreLibsDBIOTest extends TestCase
$db->dbClose();
}
// testing auto convert
/**
* Undocumented function
*
* @covers ::dbSetConvertFlag
* @testdox Check convert type works
*
* @return void
*/
public function testConvertType(): void
{
$db = new \CoreLibs\DB\IO(
self::$db_config['valid'],
self::$log
);
$bytea_data = $db->dbEscapeBytea(
file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'CoreLibsDBIOTest.php') ?: ''
);
$query_insert = <<<SQL
INSERT INTO table_with_primary_key (
uid,
row_int, row_numeric, row_varchar, row_varchar_literal,
row_json, row_jsonb, row_bytea, row_timestamp,
row_date, row_interval, row_array_int, row_array_varchar
) VALUES (
$1,
$2, $3, $4, $5,
$6, $7, $8, $9,
$10, $11, $12, $13
)
SQL;
$db->dbExecParams(
$query_insert,
[
'CONVERT_TYPE_TEST',
1, 1.5, 'varchar', 'varchar literla',
json_encode(['json', 'a', 1, true, 'sub' => ['b', 'c']]),
json_encode(['jsonb', 'a', 1, true, 'sub' => ['b', 'c']]),
$bytea_data, date('Y-m-d H:i:s'), date('Y-m-d'), date('H:m:s'),
'{1,2,3}', '{"a","b","c"}'
]
);
$type_layout = [
'uid' => 'string',
'row_int' => 'int',
'row_numeric' => 'float',
'row_varchar' => 'string',
'row_varchar_literal' => 'string',
'row_json' => 'json',
'row_jsonb' => 'json',
'row_bytea' => 'bytea',
'row_timestamp' => 'string',
'row_date' => 'string',
'row_interval' => 'string',
'row_array_int' => 'string',
'row_array_varchar' => 'string'
];
$query_select = <<<SQL
SELECT
uid,
row_int, row_numeric, row_varchar, row_varchar_literal,
row_json, row_jsonb, row_bytea, row_timestamp,
row_date, row_interval, row_array_int, row_array_varchar
FROM
table_with_primary_key
WHERE
uid = $1
SQL;
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
// all hast to be string
foreach ($res as $key => $value) {
$this->assertIsString($value, 'Aseert string for column: ' . $key);
}
// convert base only
$db->dbSetConvertFlag(Convert::on);
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
foreach ($res as $key => $value) {
if (is_numeric($key)) {
$name = $db->dbGetFieldName($key);
} else {
$name = $key;
}
switch ($type_layout[$name]) {
case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name);
break;
default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name);
break;
}
}
$db->dbSetConvertFlag(Convert::numeric);
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
foreach ($res as $key => $value) {
if (is_numeric($key)) {
$name = $db->dbGetFieldName($key);
} else {
$name = $key;
}
switch ($type_layout[$name]) {
case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name);
break;
case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name);
break;
default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name);
break;
}
}
$db->dbSetConvertFlag(Convert::json);
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
foreach ($res as $key => $value) {
if (is_numeric($key)) {
$name = $db->dbGetFieldName($key);
} else {
$name = $key;
}
switch ($type_layout[$name]) {
case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name);
break;
case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name);
break;
case 'json':
case 'jsonb':
$this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name);
break;
default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name);
break;
}
}
$db->dbSetConvertFlag(Convert::bytea);
$res = $db->dbReturnRowParams($query_select, ['CONVERT_TYPE_TEST']);
foreach ($res as $key => $value) {
if (is_numeric($key)) {
$name = $db->dbGetFieldName($key);
} else {
$name = $key;
}
switch ($type_layout[$name]) {
case 'int':
$this->assertIsInt($value, 'Aseert int for column: ' . $key . '/' . $name);
break;
case 'float':
$this->assertIsFloat($value, 'Aseert float for column: ' . $key . '/' . $name);
break;
case 'json':
case 'jsonb':
$this->assertIsArray($value, 'Aseert array for column: ' . $key . '/' . $name);
break;
case 'bytea':
// for hex types it must not start with \x
$this->assertStringStartsNotWith(
'\x',
$value,
'Aseert bytes not starts with \x for column: ' . $key . '/' . $name
);
break;
default:
$this->assertIsString($value, 'Aseert string for column: ' . $key . '/' . $name);
break;
}
}
}
// - internal read data (post exec)
// dbGetNumRows, dbGetNumFields, dbGetFieldNames,
// dbGetQuery, dbGetQueryHash, dbGetDbh
@@ -4588,7 +4759,7 @@ final class CoreLibsDBIOTest extends TestCase
. "('Foxtrott', 'Tango', 789, '1982-10-15') ",
null,
//
2,
3,
4,
['row_varchar', 'row_varchar_literal', 'row_int', 'row_date'],
['varchar', 'varchar', 'int4', 'date'],
@@ -4705,9 +4876,16 @@ final class CoreLibsDBIOTest extends TestCase
$db->dbExecParams($query, $params);
}
$this->assertInstanceOf(
'PgSql\Result',
$db->dbGetCursor(),
'Failed assert dbGetCursor'
);
$this->assertEquals(
$compare_query ?? $query,
$db->dbGetQuery()
$db->dbGetQuery(),
'Failed assert dbGetQuery'
);
$this->assertEquals(
// perhaps move that somewhere else?
@@ -4720,7 +4898,8 @@ final class CoreLibsDBIOTest extends TestCase
($params === null ?
$db->dbGetQueryHash($query) :
$db->dbGetQueryHash($query, $params)
)
),
'Failed assertdbGetQueryHash '
);
$this->assertEquals(
$expected_rows,
@@ -4742,6 +4921,35 @@ final class CoreLibsDBIOTest extends TestCase
$db->dbGetFieldTypes(),
'Failed assert dbGetFieldTypes'
);
// check FieldNameTypes matches
$this->assertEquals(
array_combine(
$expected_col_names,
$expected_col_types
),
$db->dbGetFieldNameTypes(),
'Failed assert dbGetFieldNameTypes'
);
// check pos matches name
// name matches type
// pos matches type
foreach ($expected_col_names as $pos => $name) {
$this->assertEquals(
$name,
$db->dbGetFieldName($pos),
'Failed assert dbGetFieldName: ' . $pos . ' => ' . $name
);
$this->assertEquals(
$expected_col_types[$pos],
$db->dbGetFieldType($name),
'Failed assert dbGetFieldType: ' . $name . ' => ' . $expected_col_types[$pos]
);
$this->assertEquals(
$expected_col_types[$pos],
$db->dbGetFieldType($pos),
'Failed assert dbGetFieldType: ' . $pos . ' => ' . $expected_col_types[$pos]
);
}
$dbh = $db->dbGetDbh();
$this->assertIsObject(
$dbh

View File

@@ -691,6 +691,11 @@ final class CoreLibsLoggingLoggingTest extends TestCase
);
}
/**
* Undocumented function
*
* @return array
*/
public function providerLoggingLevelWrite(): array
{
return [
@@ -796,6 +801,46 @@ final class CoreLibsLoggingLoggingTest extends TestCase
);
}
// check log level that writer writes in correct level
// also that non debug ignores prefix/group
/**
* Undocumented function
*
* @covers ::log
* @testdox log() general call test
*
* @return void
*/
public function testLoggingLog(): void
{
// init logger
$log = new \CoreLibs\Logging\Logging([
'log_file_id' => 'testLoggingLog',
'log_folder' => self::LOG_FOLDER,
'log_per_level' => true,
]);
$log_ok = $log->log(Level::Debug, 'DEBUG', group_id: 'GROUP_ID', prefix: 'PREFIX:');
$this->assertTrue($log_ok, 'assert ::log (debug) OK');
$this->assertEquals(
$log->getLogFile(),
$log->getLogFileId() . '_DEBUG.log'
);
$log_ok = $log->log(Level::Info, 'INFO', group_id: 'GROUP_ID', prefix: 'PREFIX:');
$this->assertTrue($log_ok, 'assert ::log (info) OK');
$this->assertEquals(
$log->getLogFile(),
$log->getLogFileId() . '_INFO.log'
);
}
// must test flow:
// init normal
// log -> check file name
// set per date
// log -> check file name
// and same for per_run
// deprecated calls check?
}

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__

View File

@@ -143,6 +143,26 @@ print "DATE-dow[$date];invalid: " . DateTime::setWeekdayNameFromDate($date) . "<
print "DATE-dow[$date],long;invalid: " . DateTime::setWeekdayNameFromDate($date, true) . "<br>";
print "DOW-date[$date];invalid: " . DateTime::setWeekdayNumberFromDate($date) . "<br>";
// check date range includes a weekend
// does not:
$start_date = '2023-07-03';
$end_date = '2023-07-05';
print "Has Weekend: " . $start_date . " ~ " . $end_date . ": "
. Dgs::prBl(DateTime::dateRangeHasWeekend($start_date, $end_date)) . "<br>";
$start_date = '2023-07-03';
$end_date = '2023-07-10';
print "Has Weekend: " . $start_date . " ~ " . $end_date . ": "
. Dgs::prBl(DateTime::dateRangeHasWeekend($start_date, $end_date)) . "<br>";
$start_date = '2023-07-03';
$end_date = '2023-07-31';
print "Has Weekend: " . $start_date . " ~ " . $end_date . ": "
. Dgs::prBl(DateTime::dateRangeHasWeekend($start_date, $end_date)) . "<br>";
$start_date = '2023-07-01';
$end_date = '2023-07-03';
print "Has Weekend: " . $start_date . " ~ " . $end_date . ": "
. Dgs::prBl(DateTime::dateRangeHasWeekend($start_date, $end_date)) . "<br>";
print "</body></html>";
// __END__

View File

@@ -38,7 +38,9 @@ print "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title><head>";
print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><a href="class_test.db.type.php">Class Test DB Types</a></div>';
print '<div><a href="class_test.db.dbReturn.php">Class Test DB dbReturn</a></div>';
print '<div><a href="class_test.db.single.php">Class Test DB Single Aciont</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>';
print "LOGFILE NAME: " . $db->log->getLogFile() . "<br>";
@@ -170,10 +172,10 @@ print "EOM READ OF PREVIOUS INSERTED: " . print_r($db->dbReturnRow($query), true
print "LAST ERROR: " . $db->dbGetLastError() . "<br>";
print "<br>";
$query = <<<SQL
SELECT
test_foo_id, test
FROM test_foo
WHERE test_foo_id = $1;
SELECT
test_foo_id, test
FROM test_foo
WHERE test_foo_id = $1;
SQL;
print "RETURN ROW PARAMS: " . print_r(
$db->dbReturnRowParams(
@@ -334,10 +336,10 @@ $status = $db->dbExecParams($query_insert, $query_params);
echo "<b>*</b><br>";
echo "EOM STRING WITH MORE THAN 10 PARAMETERS: "
. Support::printToString($query_params) . " |<br>"
. " |<br>"
. "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " | "
. "RETURNING EXT: " . print_r($db->dbGetReturningExt(), true) . " | "
. "RETURNING RETURN: " . print_r($db->dbGetReturningArray(), true)
. "QUERY: " . $db->dbGetQuery() . " |<br>"
. "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " |<br>"
. "RETURNING EXT: <pre>" . print_r($db->dbGetReturningExt(), true) . "</pre> |<br>"
. "RETURNING RETURN: <pre>" . print_r($db->dbGetReturningArray(), true) . "</pre> |<br>"
. "ERROR: " . $db->dbGetLastError(true) . "<br>";
echo "<hr>";
// binary insert tests
@@ -355,10 +357,10 @@ $status = $db->dbExec($query);
$__last_insert_id = $db->dbGetInsertPK();
print "BINARY DATA INSERT: "
. Support::printToString($status) . " |<br>"
. " |<br>"
. "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " | "
. "RETURNING EXT: " . print_r($db->dbGetReturningExt(), true) . " | "
. "RETURNING RETURN: " . print_r($db->dbGetReturningArray(), true)
. "QUERY: " . $db->dbGetQuery() . " |<br>"
. "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " |<br>"
. "RETURNING EXT: <pre>" . print_r($db->dbGetReturningExt(), true) . "</pre> |<br>"
. "RETURNING RETURN: <pre>" . print_r($db->dbGetReturningArray(), true) . "</pre> |<br>"
. "ERROR: " . $db->dbGetLastError(true) . "<br>";
echo "<b>*</b><br>";
@@ -369,7 +371,11 @@ INSERT INTO binary_test (
$1, $2, $3
)
SQL;
$status = $db->dbExecParams($query, [$filename, $rand_bin_uid, $binary_data]);
// $binary_data is dbEscapedBytea!
$uniqid = \CoreLibs\Create\Uids::uniqIdShort();
$status = $db->dbExecParams($query, [
'class_test.db.php', $uniqid, $binary_data
]);
$__last_insert_id = $db->dbGetInsertPK();
print "BINARY DATA INSERT PARAMS: "
. Support::printToString($status) . " |<br>"
@@ -378,7 +384,23 @@ print "BINARY DATA INSERT PARAMS: "
. "RETURNING EXT: " . print_r($db->dbGetReturningExt(), true) . " | "
. "RETURNING RETURN: " . print_r($db->dbGetReturningArray(), true)
. "ERROR: " . $db->dbGetLastError(true) . "<br>";
print "BINARY READER: ";
$query = <<<SQL
SELECT
filename, uid, binary_data
FROM
binary_test
WHERE
uid = $1
SQL;
$res = $db->dbReturnRowParams($query, [$uniqid]);
if (is_array($res)) {
var_dump($res);
$file = $db->dbUnescapeBytea($res['binary_data']);
// var_dump($file);
} else {
print "Execute error";
}
echo "<hr>";
// returning test with multiple entries
@@ -578,6 +600,44 @@ while (
}
echo "<hr>";
print "DB RETURN PARAMS LIKE<br>";
$q = <<<SQL
SELECT
test_foo_id, test, some_bool, string_a, number_a, number_a_numeric
FROM test_foo
WHERE string_a LIKE $1;
SQL;
while (
is_array($res = $db->dbReturnParams($q, ['%string%']))
) {
print "ROW: <pre>" . print_r($res, true) . "</pre><br>";
}
echo "<hr>";
print "DB RETURN PARAMS ANY<br>";
$q = <<<SQL
SELECT
test_foo_id, test, some_bool, string_a, number_a, number_a_numeric
FROM test_foo
WHERE string_a = ANY($1);
SQL;
$query_value = '{'
. join(',', ['string a'])
. '}';
while (
is_array($res = $db->dbReturnParams($q, [$query_value]))
) {
print "ROW: <pre>" . print_r($res, true) . "</pre><br>";
}
echo "<hr>";
print "COMPOSITE ELEMENT READ<br>";
$res = $db->dbReturnRow("SELECT item, count, (item).name, (item).price, (item).supplier_id FROM on_hand");
print "ROW: <pre>" . print_r($res) . "</pre>";
var_dump($res);
print "Field Name/Types: <pre>" . print_r($db->dbGetFieldNameTypes(), true) . "</pre>";
echo "<hr>";
// NOTE: try to replacate connection still exists if script is run a second time
// open pg bouncer connection
$db_pgb = new CoreLibs\DB\IO($DB_CONFIG['test_pgbouncer'] ?? [], $log);

View File

@@ -35,7 +35,6 @@ print "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title><head>";
print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><a href="class_test.db.dbReturn.php">Class Test DB dbReturn</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>';
print "LOGFILE NAME: " . $db->log->getLogFile() . "<br>";
@@ -54,38 +53,76 @@ if (($dbh = $db->dbGetDbh()) instanceof \PgSql\Connection) {
print "NO DB HANDLER<br>";
}
// params > 10 for debug
// error catcher
$query_insert = <<<SQL
INSERT INTO many_columns (
col_01_int,
col_01, col_02, col_03, col_04, col_05, col_06, col_07, col_08, col_09,
col_10, col_11, col_12, col_02_int
) VALUES (
1,
$1, $2, $3, $4, $5, $6, $7, $8, $9,
$10, $11, $12, $13
)
RETURNING
many_columns_id,
col_01_int,
col_01, col_02, col_03, col_04, col_05, col_06, col_07, col_08, col_09,
col_10, col_11, col_12, col_02_int
SQL;
/**
* Undocumented function
*
* @param \CoreLibs\DB\IO $dbc
* @return void
*/
function testDBS(\CoreLibs\DB\IO $dbc): void
{
echo "Int call<br>";
$dbc->dbReturnRow("SELECT test FROM test_foo LIMIT 1");
}
$uniqid = \CoreLibs\Create\Uids::uniqIdShort();
$binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: '');
$query_params = [
'col 1', 'col 2', 'col 3', 'col 4', 'col 5', 'col 6', 'col 7', 'col 8',
'col 9', 'col 10', 'col 11', 'col 12', null
$uniqid,
true,
'STRING A',
2,
2.5,
1,
date('H:m:s'),
date('Y-m-d H:m:s'),
json_encode(['a' => 'string', 'b' => 1, 'c' => 1.5, 'f' => true, 'g' => ['a', 1, 1.5]]),
null,
'{"a", "b"}',
'{1,2}',
'{"(array Text A, 5, 8.8)","(array Text B, 10, 15.2)"}',
'("Text", 4, 6.3)',
$binary_data
];
$query_insert = <<<SQL
INSERT INTO test_foo (
test, some_bool, string_a, number_a, number_a_numeric, smallint_a,
some_time, some_timestamp, json_string, null_var,
array_char_1, array_int_1,
array_composite,
composite_item,
some_binary
) VALUES (
$1, $2, $3, $4, $5, $6,
$7, $8, $9, $10,
$11, $12,
$13,
$14,
$15
)
SQL;
$status = $db->dbExecParams($query_insert, $query_params);
echo "<b>*</b><br>";
echo "EOM STRING WITH MORE THAN 10 PARAMETERS: "
. Support::printToString($query_params) . " |<br>"
. " |<br>"
. "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " | "
// . "RETURNING EXT: " . Support::printToString($db->dbGetReturningExt()) . " | "
. "RETURNING RETURN: " . Support::printToString($db->dbGetReturningArray())
. "ERROR: " . $db->dbGetLastError(true) . "<br>";
echo "<hr>";
$query_select = <<<SQL
SELECT
test_foo_id,
test, some_bool, string_a, number_a, number_a_numeric, smallint_a,
number_real, number_double, number_serial,
some_time, some_timestamp, json_string, null_var,
array_char_1, array_char_2, array_int_1, array_int_2, array_composite,
composite_item, (composite_item).*
some_binary
FROM
test_foo
WHERE
test = $1;
SQL;
$res = $db->dbReturnRowParams($query_select, [$uniqid]);
if (is_array($res)) {
var_dump($res);
}
testDBS($db);
print "</body></html>";

View File

@@ -0,0 +1,171 @@
<?php // phpcs:ignore warning
/**
* @phan-file-suppress PhanTypeSuspiciousStringExpression
*/
declare(strict_types=1);
// turn on all error reporting
error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR);
ob_start();
// basic class test file
define('USE_DATABASE', true);
// sample config
require 'config.php';
// define log file id
$LOG_FILE_ID = 'classTest-db-types';
ob_end_flush();
use CoreLibs\Debug\Support;
use CoreLibs\DB\Options\Convert;
$log = new CoreLibs\Logging\Logging([
'log_folder' => BASE . LOG,
'log_file_id' => $LOG_FILE_ID,
'log_per_date' => true,
]);
// db connection and attach logger
$db = new CoreLibs\DB\IO(DB_CONFIG, $log);
$db->log->debug('START', '=============================>');
$PAGE_NAME = 'TEST CLASS: DB COLUMN TYPES';
print "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title><head>";
print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>';
print "LOGFILE NAME: " . $db->log->getLogFile() . "<br>";
print "LOGFILE ID: " . $db->log->getLogFileId() . "<br>";
print "DBINFO: " . $db->dbInfo() . "<br>";
// DB client encoding
print "DB client encoding: " . $db->dbGetEncoding() . "<br>";
print "DB search path: " . $db->dbGetSchema() . "<br>";
$to_db_version = '15.2';
print "VERSION DB: " . $db->dbVersion() . "<br>";
print "SERVER ENCODING: " . $db->dbVersionInfo('server_encoding') . "<br>";
if (($dbh = $db->dbGetDbh()) instanceof \PgSql\Connection) {
print "ALL OUTPUT [TEST]: <pre>" . print_r(pg_version($dbh), true) . "</pre><br>";
} else {
print "NO DB HANDLER<br>";
}
print "<b>TRUNCATE test_foo</b><br>";
$db->dbExec("TRUNCATE test_foo");
/* $q = <<<SQL
INSERT INTO test_foo (test, array_composite) VALUES ('C', '{"(a,1,1.5)","(b,2,2.5)"}')
SQL;
$db->dbExecParams($q);
pg_query($db->dbGetDbh(), $q);
$q = <<<SQL
INSERT INTO test_foo (test, array_composite) VALUES ($1, $2)
SQL;
// $db->dbExecParams($q, ['D', '{"(a b,1,1.5)","(c,3,4.5)"}']);
$db->dbExecParams($q, ['D', '{"(array Text A, 5, 8.8)","(array Text B, 10, 15.2)"}']);
*/
$uniqid = \CoreLibs\Create\Uids::uniqIdShort();
$binary_data = $db->dbEscapeBytea(file_get_contents('class_test.db.php') ?: '');
$query_params = [
$uniqid,
true,
'STRING A',
2,
2.5,
1,
date('H:m:s'),
date('Y-m-d H:i:s'),
json_encode(['a' => 'string', 'b' => 1, 'c' => 1.5, 'f' => true, 'g' => ['a', 1, 1.5]]),
null,
'{"a", "b"}',
'{1,2}',
'{"(array Text A, 5, 8.8)","(array Text B, 10, 15.2)"}',
'("Text", 4, 6.3)',
$binary_data
];
$query_insert = <<<SQL
INSERT INTO test_foo (
test, some_bool, string_a, number_a, number_a_numeric, smallint_a,
some_time, some_timestamp, json_string, null_var,
array_char_1, array_int_1,
array_composite,
composite_item,
some_binary
) VALUES (
$1, $2, $3, $4, $5, $6,
$7, $8, $9, $10,
$11, $12,
$13,
$14,
$15
)
RETURNING
test_foo_id,
test, some_bool, string_a, number_a, number_a_numeric, smallint_a,
some_time, some_timestamp, json_string, null_var,
array_char_1, array_int_1,
array_composite,
composite_item,
some_binary
SQL;
$status = $db->dbExecParams($query_insert, $query_params);
echo "<b>*</b><br>";
echo "INSERT ALL COLUMN TYPES: "
. Support::printToString($query_params) . " |<br>"
. "QUERY: " . $db->dbGetQuery() . " |<br>"
. "PRIMARY KEY: " . Support::printToString($db->dbGetInsertPK()) . " |<br>"
. "RETURNING EXT: <pre>" . print_r($db->dbGetReturningExt(), true) . "</pre> |<br>"
. "RETURNING RETURN: <pre>" . print_r($db->dbGetReturningArray(), true) . "<pre> |<br>"
. "ERROR: " . $db->dbGetLastError(true) . "<br>";
echo "<hr>";
$query_select = <<<SQL
SELECT
test_foo_id,
test, some_bool, string_a, number_a, number_a_numeric, smallint_a,
number_real, number_double, number_numeric_3, number_serial,
some_time, some_timestamp, json_string, null_var,
array_char_1, array_char_2, array_int_1, array_int_2, array_composite,
composite_item, (composite_item).*,
some_binary
FROM
test_foo
WHERE
test = $1;
SQL;
$res = $db->dbReturnRowParams($query_select, [$uniqid]);
// auto switch:
// ^int
// bool
// with flags:
// json(b) => array
// bytes => string? or resource?
// numeric => float (can have precision cut)
$pos = 0;
$name = '';
if (is_array($res)) {
$cursor = $db->dbGetCursor();
var_dump($res);
print "Field Name/Types: <pre>" . print_r($db->dbGetFieldNameTypes(), true) . "</pre>";
print "Get type for: 'number_a':" . $db->dbGetFieldType('number_a') . "<br>";
print "Get type for: 0: " . $db->dbGetFieldType(0) . "<br>";
print "Get name for: 0: " . $db->dbGetFieldName(0) . "<br>";
}
$db->dbSetConvertFlag(Convert::on);
$db->dbSetConvertFlag(Convert::json);
$db->dbSetConvertFlag(Convert::numeric);
$db->dbSetConvertFlag(Convert::bytea);
$res = $db->dbReturnRowParams($query_select, [$uniqid]);
if (is_array($res)) {
var_dump($res);
}
print "</body></html>";
// __END__

View File

@@ -0,0 +1,83 @@
<?php // phpcs:ignore warning
/**
* @phan-file-suppress PhanTypeSuspiciousStringExpression
*/
declare(strict_types=1);
error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR);
ob_start();
// basic class test file
define('USE_DATABASE', false);
// sample config
require 'config.php';
// define log file id
$LOG_FILE_ID = 'classTest-html_build-block';
ob_end_flush();
use CoreLibs\Template\HtmlBuilder\Block;
$log = new CoreLibs\Logging\Logging([
'log_folder' => BASE . LOG,
'log_file_id' => $LOG_FILE_ID,
'log_per_date' => true,
]);
// define a list of from to color sets for conversion test
$PAGE_NAME = 'TEST CLASS: HTML BUILD: BLOCK';
print "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title><head>";
print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>';
$el = Block::cel('div', 'el-1', 'Content', ['red'], ['onClick' => 'javascript:alert(\'JS alert\');']);
print "<pre>" . htmlentities(Block::buildHtml($el)) . "</pre>";
$el_a = Block::cel('div', 'u-id', '', ['base', 'cool']);
$el_a_1 = Block::cel('span', 's-id-1', 'Span A', ['bold']);
$el_a_2 = Block::cel('span', 's-id-2', 'Span B');
$el_a_3 = Block::cel('a', 'link-a', 'Title', ['l-highlight'], ['OnClick' => 'Foo();']);
$el_a_2 = Block::aelx($el_a_2, $el_a_3);
// css changes before added to array
$el_a_1 = Block::acssel($el_a_1, 'italic', 'green', 'italic', 'font-large');
$el_a_1 = Block::rcssel($el_a_1, 'green');
// switch
$el_a_1 = Block::scssel($el_a_1, ['one', 'two', 'three'], ['three']);
// this will add el_a_2 to the el_a block
$el_a_1 = Block::aelx($el_a_1, $el_a_2);
$el_a = Block::aelx($el_a, $el_a_1, $el_a_2);
// this will not update el_a
// $el_a_1 = Block::aelx($el_a_1, $el_a_2);
$el_a_list = [];
$el_a_list[] = Block::cel('foo', 'foo-A');
$el_a_list[] = Block::cel('bar', 'foo-B');
$el_a_list[] = Block::cel('baz', 'foo-C');
$el_a_list[] = Block::cel('br');
$el_a_list[] = Block::cel('input');
echo "<hr>";
print "EL_A: <pre>" . print_r($el_a, true) . "</pre>";
echo "<hr>";
print "phfo(\$el_o): <pre>" . htmlentities(Block::buildHtml($el_a, true)) . "</pre>";
echo "<hr>";
print "phfa(\$el_list): <pre>" . htmlentities(Block::buildHtmlFromList($el_a_list, true)) . "</pre>";
echo "<hr>";
// self loop test (will not trigger, are arrays)
$el_s = Block::cel('div', 'id-s', 'Self', []);
$el_s = Block::aelx($el_s, $el_s);
print "phfo(\$el_): <pre>" . htmlentities(Block::buildHtml($el_s, true)) . "</pre>";
print "</body></html>";
// __END__

View File

@@ -0,0 +1,142 @@
<?php // phpcs:ignore warning
/**
* @phan-file-suppress PhanTypeSuspiciousStringExpression
*/
declare(strict_types=1);
error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR);
ob_start();
// basic class test file
define('USE_DATABASE', false);
// sample config
require 'config.php';
// define log file id
$LOG_FILE_ID = 'classTest-html_build';
ob_end_flush();
use CoreLibs\Template\HtmlBuilder\Element;
use CoreLibs\Template\HtmlBuilder\General\Error;
use CoreLibs\Template\HtmlBuilder\General\HtmlBuilderExcpetion;
use CoreLibs\Debug\Support;
$log = new CoreLibs\Logging\Logging([
'log_folder' => BASE . LOG,
'log_file_id' => $LOG_FILE_ID,
'log_per_date' => true,
]);
// define a list of from to color sets for conversion test
$PAGE_NAME = 'TEST CLASS: HTML BUILD';
print "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title><head>";
print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>';
$el = new Element('div', 'el-1', 'Content', ['red'], ['onClick' => 'javascript:alert(\'JS alert\');']);
print "<pre>" . htmlentities($el->buildHtml()) . "</pre>";
$el_o = new Element('div', 'u-id', '', ['base', 'cool']);
$el_o_1 = new Element('span', 's-id-1', 'Span A', ['bold']);
$el_o_2 = new Element('span', 's-id-2', 'Span B');
$el_o_3 = new Element('a', 'link-a', 'Title', ['l-highlight'], ['OnClick' => 'Foo();']);
$el_o_2->addSub($el_o_3);
$el_o->addSub($el_o_1, $el_o_2);
$el_o_1->addCss('italic', 'green', 'italic', 'font-large')
->removeCss('green');
$el_o_2->addCss('wrong-css')
->removeCss('wrong-css', 'correct-css');
$el_o_2->addCss('a', 'b')->removeCss('correct-css');
$el_o_1->addSub($el_o_2);
// var_dump($el2);
$el_o_list = [];
$el_o_list[] = new Element('foo', 'foo-A');
$el_o_list[] = new Element('bar', 'foo-B');
$el_o_list[] = new Element('baz', 'foo-C');
$el_o_list[] = new Element('br');
$el_o_list[] = new Element('input', 'tag', '', [], ['name' => 'foo', 'value' => 'ABC']);
// $el2->resetSub();
// var_dump($el2);
echo "<hr>";
print "EL_O: <pre>" . print_r($el_o, true) . "</pre>";
echo "<hr>";
print "buildHtml(): <pre>" . htmlentities($el_o->buildHtml()) . "</pre>";
echo "<hr>";
print "phfo(\$el_o): <pre>" . htmlentities($el_o::printHtmlFromObject($el_o, true)) . "</pre>";
echo "<hr>";
print "phfa(\$el_list): <pre>" . htmlentities($el_o::buildHtmlFromList($el_o_list, true)) . "</pre>";
echo "<hr>";
// self loop
$el_s = new Element('div', 'id-s', 'Self');
try {
$el_s->addSub($el_s, new Element('span', '', 'Span'));
} catch (HtmlBuilderExcpetion $e) {
print "E: " . $e->getMessage() . " | " . $e->getTraceAsString() . "<br>";
}
// var_dump($el_s);
print "el_s, buildHtml(): <pre>" . htmlentities($el_s->buildHtml()) . "</pre>";
$el_s_2 = new Element('div', 'id-s', 'Self', []);
$el_s_2->addSub(
new Element('span', 's-1', 's 1'),
new Element('span', 's-2', 's 2'),
);
$el_s_3 = new Element('div', 'id-3', 'ID 3');
try {
$el_s_3->addSub($el_s_2);
$el_s_2->addSub($el_s_2);
} catch (HtmlBuilderExcpetion $e) {
print "E: " . $e->getMessage() . " | " . $e->getTraceAsString() . "<br>";
}
// print "<pre>" . var_export($el_s_3, true) . "</pre>";
print "el_s_3, buildHtml(): <pre>" . htmlentities($el_s_3->buildHtml()) . "</pre>";
echo "<hr>";
Error::resetMessages();
try {
$el_er = new Element('');
} catch (HtmlBuilderExcpetion $e) {
print "E: " . $e->getMessage() . " | " . $e->getTraceAsString() . "<br>";
if ($e->getPrevious() !== null) {
print "E: " . $e->getPrevious()->getMessage() . " | " . $e->getPrevious()->getTraceAsString() . "<br>";
}
}
print "Errors: <pre>" . print_r(Error::getMessages(), true) . "</pre>";
print "Warning: " . Support::printToString(Error::hasWarning()) . "<br>";
print "Error: " . Support::printToString(Error::hasError()) . "<br>";
Error::resetMessages();
try {
$el_er = new Element('123123');
} catch (HtmlBuilderExcpetion $e) {
print "E: " . $e->getMessage() . " | " . $e->getTraceAsString() . "<br>";
if ($e->getPrevious() !== null) {
print "E: " . $e->getPrevious()->getMessage() . " | " . $e->getPrevious()->getTraceAsString() . "<br>";
}
}
print "Errors: <pre>" . print_r(Error::getMessages(), true) . "</pre>";
print "Warning: " . Support::printToString(Error::hasWarning()) . "<br>";
print "Error: " . Support::printToString(Error::hasError()) . "<br>";
print "</body></html>";
// __END__

View File

@@ -0,0 +1,83 @@
<?php // phpcs:ignore warning
/**
* @phan-file-suppress PhanTypeSuspiciousStringExpression
*/
declare(strict_types=1);
error_reporting(E_ALL | E_STRICT | E_ERROR | E_WARNING | E_PARSE | E_COMPILE_ERROR);
ob_start();
// basic class test file
define('USE_DATABASE', false);
// sample config
require 'config.php';
// define log file id
$LOG_FILE_ID = 'classTest-html_build-string-replace';
ob_end_flush();
use CoreLibs\Template\HtmlBuilder\StringReplace;
$log = new CoreLibs\Logging\Logging([
'log_folder' => BASE . LOG,
'log_file_id' => $LOG_FILE_ID,
'log_per_date' => true,
]);
// define a list of from to color sets for conversion test
$PAGE_NAME = 'TEST CLASS: HTML BUILD: STRING REPLACE';
print "<!DOCTYPE html>";
print "<html><head><title>" . $PAGE_NAME . "</title><head>";
print "<body>";
print '<div><a href="class_test.php">Class Test Master</a></div>';
print '<div><h1>' . $PAGE_NAME . '</h1></div>';
$html_block = <<<HTML
<div id="{ID}" class="{CSS}">
{CONTENT}
</div>
HTML;
print "<pre>" . htmlentities(StringReplace::replaceData(
$html_block,
[
'ID' => 'block-id',
'CSS' => join(',', ['blue', 'red']),
'{CONTENT}' => 'Some content here<br>with bla bla inside'
]
)) . "</pre>";
StringReplace::loadElements(
['foo', $html_block],
['bar', <<<HTML
<span id="{ID}">{CONTENT}</span>
HTML]
);
print "Get: <pre>" . htmlentities(StringReplace::getElement('bar') ?: '') . '</pre>';
print "Build element: <pre>" . htmlentities(StringReplace::buildElement(
'bar',
[
'ID}' => 'new-id',
'{CONTENT' => 'Test cow 日本語'
]
)) . '</pre>' ;
print "Build element as replace: <pre>" . htmlentities(StringReplace::buildElement(
'bar',
['
ID}' => 'diff-id',
'{CONTENT' => 'Test cow 日本語. More text plus'
],
'rpl-1'
)) . '</pre>' ;
print "Get replacement: <pre>" . htmlentities(StringReplace::getReplaceBlock('rpl-1')) . "</pre>";
print "</body></html>";
// __END__

View File

@@ -76,7 +76,12 @@ with
and > and <
EOM));
$log->info('Info message', ['info' => 'log']);
$log->notice('Notice message', ['notice' => 'log']);
$log->warning('Warning message', ['warning' => 'log']);
$log->error('Cannot process data', ['error' => 'log']);
$log->critical('Critical message', ['critical' => 'log']);
$log->alert('Alert message', ['Alert' => 'log']);
$log->emergency('Emergency message', ['Emergency' => 'log']);
print "Log File: " . $log->getLogFile() . "<br>";
$log->setLogFlag(Flag::per_run);

View File

@@ -69,6 +69,7 @@ print "<body>";
// key: file name, value; name
$test_files = [
'class_test.db.php' => 'Class Test: DB',
'class_test.db.types.php' => 'Class Test: DB COLUMN TYPES',
'class_test.db.single.php' => 'Class Test: DB SINGLE',
'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn',
'class_test.convert.colors.php' => 'Class Test: CONVERT COLORS',
@@ -80,6 +81,9 @@ $test_files = [
'class_test.encryption.php' => 'Class Test: ENCRYPTION',
'class_test.math.php' => 'Class Test: MATH',
'class_test.html.php' => 'Class Test: HTML/ELEMENTS',
'class_test.html_build.element.php' => 'Class Test: HTML BUILDER: ELEMENT',
'class_test.html_build.block.php' => 'Class Test: HTML BUILDER: BLOCK',
'class_test.html_build.replace.php' => 'Class Test: HTML BUILDER: STRING REPLACE',
'class_test.email.php' => 'Class Test: EMAIL',
'class_test.create_email.php' => 'Class Test: CREATE EMAIL',
'class_test.uids.php' => 'Class Test: UIDS',

View File

@@ -22,6 +22,7 @@ $DB_CONFIG = [
'db_type' => 'pgsql',
'db_encoding' => '',
'db_ssl' => 'allow', // allow, disable, require, prefer
// 'db_convert_type' => ['json'] // on, json, numeric, bytea
],
// same as above, but uses pg bouncer
'test_pgbouncer' => [

View File

@@ -222,7 +222,17 @@ if ($is_secure) {
}
// define the db config set name, the db config and the db schema
define('DB_CONFIG_NAME', $SITE_CONFIG[HOST_NAME]['db_host'] ?? '');
define('DB_CONFIG', $DB_CONFIG[DB_CONFIG_NAME] ?? []);
define('DB_CONFIG', $DB_CONFIG[DB_CONFIG_NAME] ?? [
'db_name' => '',
'db_user' => '',
'db_pass' => '',
'db_host' => '',
'db_port' => 5432,
'db_schema' => '',
'db_encoding' => '',
'db_type' => '',
'db_ssl' => ''
]);
// because we can't change constant, but we want to for db debug flag
$GLOBALS['DB_CONFIG_SET'] = DB_CONFIG;
// define('DB_CONFIG_TARGET', SITE_CONFIG[$HOST_NAME]['db_host_target']);

View File

@@ -10,6 +10,9 @@
declare(strict_types=1);
// style sheet for the edit base interface
define('EDIT_BASE_STYLESHEET', 'edit.css');
// define('SOME_ID', <SOME VALUE>);
/************* CONVERT *******************/

View File

@@ -87,7 +87,7 @@ $l10n = new \CoreLibs\Language\L10n(
$locale['locale'],
$locale['domain'],
$locale['path'],
$locale['encoding']
$locale['encoding'],
);
// create smarty object

View File

@@ -86,7 +86,7 @@ $edit_base->editBaseRun(
BASE . INCLUDES . TEMPLATES . CONTENT_PATH,
BASE . TEMPLATES_C,
BASE . CACHE,
ADMIN_STYLESHEET,
EDIT_BASE_STYLESHEET,
DEFAULT_ENCODING,
LAYOUT . CSS,
LAYOUT . JS,

View File

@@ -575,7 +575,7 @@ function actionIndicator(loc, overlay = true) // eslint-disable-line no-unused-v
*/
function actionIndicatorShow(loc, overlay = true)
{
// console.log('Indicator: SHOW [%s]', loc);
// console.log('{Indicator}: SHOW [%s]', loc);
if (!$('#indicator').is(':visible')) {
if (!$('#indicator').hasClass('progress')) {
$('#indicator').addClass('progress');
@@ -597,7 +597,7 @@ function actionIndicatorShow(loc, overlay = true)
*/
function actionIndicatorHide(loc, overlay = true)
{
// console.log('Indicator: HIDE [%s]', loc);
// console.log('{Indicator}: HIDE [%s]', loc);
$('#indicator').hide();
if (overlay === true) {
overlayBoxHide();
@@ -677,7 +677,7 @@ function ClearCall() // eslint-disable-line no-unused-vars
*/
function showActionIndicator(loc) // eslint-disable-line no-unused-vars
{
// console.log('Indicator: SHOW [%s]', loc);
// console.log('{Indicator}: SHOW [%s]', loc);
// check if indicator element exists
if ($('#indicator').length == 0) {
var el = document.createElement('div');
@@ -715,7 +715,7 @@ function showActionIndicator(loc) // eslint-disable-line no-unused-vars
*/
function hideActionIndicator(loc) // eslint-disable-line no-unused-vars
{
// console.log('Indicator: HIDE [%s]', loc);
// console.log('{Indicator}: HIDE [%s]', loc);
// check if indicator is visible
if ($('#indicator').is(':visible')) {
// hide indicator
@@ -965,6 +965,44 @@ function scssel(_element, rcss, acss) // eslint-disable-line no-unused-vars
*/
function phfo(tree)
{
let name_elements = [
'button',
'fieldset',
'form',
'iframe',
'input',
'map',
'meta',
'object',
'output',
'param',
'select',
'textarea',
];
let skip_options = [
'id',
'name',
'class',
];
let no_close = [
'input',
'br',
'img',
'hr',
'area',
'col',
'keygen',
'wbr',
'track',
'source',
'param',
'command',
// only in header
'base',
'meta',
'link',
'embed',
];
// holds the elements
var content = [];
// main part line
@@ -974,7 +1012,7 @@ function phfo(tree)
if (tree.id) {
line += ' id="' + tree.id + '"';
// if anything input (input, textarea, select then add name too)
if (['input', 'textarea', 'select', 'button'].includes(tree.tag)) {
if (name_elements.includes(tree.tag)) {
line += ' name="' + (tree.name ? tree.name : tree.id) + '"';
}
}
@@ -992,7 +1030,7 @@ function phfo(tree)
if (isObject(tree.options)) {
// ignores id, name, class as key
for (const [key, item] of Object.entries(tree.options)) {
if (!['id', 'name', 'class'].includes(key)) {
if (!skip_options.includes(key)) {
line += ' ' + key + '="' + item + '"';
}
}
@@ -1016,9 +1054,7 @@ function phfo(tree)
}
// if not input, image or br, then close
if (
tree.tag != 'input' ||
tree.tag != 'img' ||
tree.tag != 'br'
!no_close.includes(tree.tag)
) {
content.push('</' + tree.tag + '>');
}

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

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)) {

View File

@@ -41,7 +41,8 @@ class EditBase
/**
* construct form generator
*
* @param array<mixed> $db_config db config array, mandatory
* phpcs:ignore
* @param array{db_name:string,db_user:string,db_pass:string,db_host:string,db_port:int,db_schema:string,db_encoding:string,db_type:string,db_ssl:string,db_convert_type?:string[]} $db_config db config array, mandatory
* @param \CoreLibs\Logging\Logging $log Logging class, null auto set
* @param \CoreLibs\Language\L10n $l10n l10n language class, null auto set
* @param \CoreLibs\ACL\Login $login login class for ACL settings

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

@@ -54,7 +54,8 @@ class ArrayIO extends \CoreLibs\DB\IO
* constructor for the array io class, set the
* primary key name automatically (from array)
*
* @param array<mixed> $db_config db connection config
* phpcs:ignore
* @param array{db_name:string,db_user:string,db_pass:string,db_host:string,db_port:int,db_schema:string,db_encoding:string,db_type:string,db_ssl:string,db_convert_type?:string[]} $db_config db connection config
* @param array<mixed> $table_array table array config
* @param string $table_name table name string
* @param \CoreLibs\Logging\Logging $log Logging class
@@ -587,7 +588,7 @@ class ArrayIO extends \CoreLibs\DB\IO
// get it at the end, cause now we can be more sure of no double IDs, etc
reset($this->table_array);
// create select part & addition FK part
foreach ($this->table_array as $column => $data_array) {
foreach (array_keys($this->table_array) as $column) {
// check FK ...
if (
isset($this->table_array[$column]['fk']) &&

View File

@@ -259,6 +259,8 @@ namespace CoreLibs\DB;
use CoreLibs\Create\Hash;
use CoreLibs\Debug\Support;
use CoreLibs\Create\Uids;
use CoreLibs\Convert\Json;
use CoreLibs\DB\Options\Convert;
// below no ignore is needed if we want to use PgSql interface checks with PHP 8.0
// as main system. Currently all @var sets are written as object
@@ -307,7 +309,7 @@ class IO
// 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 */
@@ -328,6 +330,17 @@ class IO
private string $db_type;
/** @var string ssl flag (for postgres only), disable, allow, prefer, require */
private string $db_ssl;
/** @var array<string> flag for converting types from settings */
private array $db_convert_type = [];
// convert type settings
// 0: OFF (CONVERT_OFF)
// >0: ON
// 1: convert intN/bool (CONVERT_ON)
// 2: convert json/jsonb to array (CONVERT_JSON)
// 4: convert numeric/floatN to float (CONVERT_NUMERIC)
// 8: convert bytea to string data (CONVERT_BYTEA)
/** @var int type settings as bit mask, 0 for off, anything >2 will aways set 1 too */
private int $convert_type = Convert::off->value;
// 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 */
@@ -343,6 +356,8 @@ class IO
private array $field_names = [];
/** @var array<string> field type names */
private array $field_types = [];
/** @var array<string,string> field name to type */
private array $field_name_types = [];
/** @var array<mixed> always return as array, even if only one */
private array $insert_id_arr = [];
/** @var string primary key name for insert recovery from insert_id_arr */
@@ -392,8 +407,11 @@ class IO
public \CoreLibs\Logging\Logging $log;
/**
* main DB concstructor with auto connection to DB and failure set on failed connection
* @param array<mixed> $db_config DB configuration array
* main DB concstructor with auto connection to DB
* and failure set on failed connection
*
* phpcs:ignore
* @param array{db_name:string,db_user:string,db_pass:string,db_host:string,db_port:int,db_schema:string,db_encoding:string,db_type:string,db_ssl:string,db_convert_type?:string[]} $db_config DB configuration array
* @param \CoreLibs\Logging\Logging $log Logging class
*/
public function __construct(
@@ -402,17 +420,8 @@ class IO
) {
// attach logger
$this->log = $log;
// sets the names (for connect/reconnect)
$this->db_name = $db_config['db_name'] ?? '';
$this->db_user = $db_config['db_user'] ?? '';
$this->db_pwd = $db_config['db_pass'] ?? '';
$this->db_host = $db_config['db_host'] ?? '';
$this->db_port = !empty($db_config['db_port']) ? (int)$db_config['db_port'] : 5432;
// do not set to 'public' if not set, because the default is already public
$this->db_schema = !empty($db_config['db_schema']) ? $db_config['db_schema'] : '';
$this->db_encoding = !empty($db_config['db_encoding']) ? $db_config['db_encoding'] : '';
$this->db_type = $db_config['db_type'] ?? '';
$this->db_ssl = !empty($db_config['db_ssl']) ? $db_config['db_ssl'] : 'allow';
// set the config options
$this->__setConfigOptions($db_config);
// set debug, either via global var, or from config, else set to false
$this->dbSetDebug(
// set if logging level is Debug
@@ -497,6 +506,84 @@ class IO
// PRIVATE METHODS
// *************************************************************
/**
* Setup DB config and options
*
* phpcs:ignore
* @param array{db_name:string,db_user:string,db_pass:string,db_host:string,db_port:int,db_schema:string,db_encoding:string,db_type:string,db_ssl:string,db_convert_type?:string[]} $db_config
* @return bool
*/
private function __setConfigOptions(array $db_config): bool
{
// sets the names (for connect/reconnect)
$this->db_name = $db_config['db_name'] ?? '';
$this->db_user = $db_config['db_user'] ?? '';
$this->db_pwd = $db_config['db_pass'] ?? '';
$this->db_host = $db_config['db_host'] ?? '';
// port
$this->db_port = !empty($db_config['db_port']) ?
(int)$db_config['db_port'] : 5432;
if ($this->db_port < 0 || $this->db_port > 65535) {
$this->db_port = 5432;
}
// do not set to 'public' if not set, because the default is already public
$this->db_schema = !empty($db_config['db_schema']) ?
$db_config['db_schema'] : '';
$this->db_encoding = !empty($db_config['db_encoding']) ?
$db_config['db_encoding'] : '';
// db type
$this->db_type = $db_config['db_type'] ?? '';
if (!in_array($this->db_type, ['pgsql'])) {
$this->db_type = 'pgsql';
}
// ssl setting
$this->db_ssl = !empty($db_config['db_ssl']) ?
$db_config['db_ssl'] : 'allow';
if (!in_array($this->db_ssl, ['allow', 'disable', 'require', 'prefer'])) {
$this->db_ssl = 'allow';
}
// trigger convert type
// ['on', 'json', 'numeric', 'bytea'] allowed
// if on is not set but other valid than on is assumed
foreach ($db_config['db_convert_type'] ?? [] as $db_convert_type) {
if (!in_array($db_convert_type, ['on', 'json', 'numeric', 'bytea'])) {
continue;
}
$this->db_convert_type[] = $db_convert_type;
$this->__setConvertType($db_convert_type);
}
// return status true: ok, false: options error
return true;
}
/**
* Set the convert bit flags
*
* @param string $db_convert_type One of 'on', 'json', 'numeric', 'bytea'
* @return void
*/
private function __setConvertType(string $db_convert_type): void
{
switch ($db_convert_type) {
case 'on':
$this->convert_type |= Convert::on->value;
break;
case 'json':
$this->convert_type |= Convert::on->value;
$this->convert_type |= Convert::json->value;
break;
case 'numeric':
$this->convert_type |= Convert::on->value;
$this->convert_type |= Convert::numeric->value;
break;
case 'bytea':
$this->convert_type |= Convert::on->value;
$this->convert_type |= Convert::bytea->value;
break;
}
}
/**
* based on $this->db_type
* here we need to load the db pgsql include one
@@ -525,8 +612,10 @@ class IO
}
/**
* internal connection function. Used to connect to the DB if there is no connection done yet.
* internal connection function.
* Used to connect to the DB if there is no connection done yet.
* Called before any execute
*
* @return bool true on successfull connect, false if failed
*/
private function __connectToDB(): bool
@@ -571,6 +660,7 @@ class IO
/**
* close db connection
* only used by the deconstructor
*
* @return void has no return
*/
private function __closeDB(): void
@@ -585,6 +675,7 @@ class IO
* checks if query is a SELECT, SHOW or WITH, if not error, 0 return
* NOTE:
* Query needs to start with SELECT, SHOW or WITH
*
* @param string $query query to check
* @return bool true if matching, false if not
*/
@@ -602,6 +693,7 @@ class IO
* if pure is set to true, only when INSERT is set will return true
* NOTE:
* Queries need to start with INSERT, UPDATE, DELETE. Anything else is ignored
*
* @param string $query query to check
* @param bool $pure pure check (only insert), default false
* @return bool true if matching, false if not
@@ -620,6 +712,7 @@ class IO
/**
* returns true if the query starts with UPDATE
* query NEEDS to start with UPDATE
*
* @param string $query query to check
* @return bool returns true if the query starts with UPDATE
*/
@@ -635,6 +728,7 @@ class IO
* internal funktion that creates the array
* NOTE:
* used in db_dump_data only
*
* @param array<mixed> $array array to print
* @return string string with printed and formated array
*/
@@ -698,15 +792,46 @@ class IO
. \CoreLibs\Debug\Support::prAr($error_data)
. ']';
}
// we need to trace back where the first non class call was done
// add this stack trace to the context
$call_stack = [];
foreach (array_reverse(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)) as $call_trace) {
if (
!empty($call_trace['file']) &&
str_ends_with($call_trace['file'], '/DB/IO.php')
) {
break;
}
$call_stack[] =
($call_trace['file'] ?? 'n/f') . ':'
. ($call_trace['line'] ?? '-') . ':'
. (!empty($call_trace['class']) ? $call_trace['class'] . '->' : '')
. $call_trace['function'];
}
$context = [
'call_trace' => array_reverse($call_stack)
];
switch ($id) {
case 'DB_ERROR':
$this->log->error($debug_id . ' :' . $prefix . $error_string);
$this->log->error(
$prefix . $error_string,
$context
);
break;
case 'DB_WARNING':
$this->log->warning($debug_id . ' :' . $prefix . $error_string);
$this->log->warning(
$prefix . $error_string,
$context
);
break;
default:
$this->log->debug($debug_id, $error_string, $prefix);
// used named arguments so we can easy change the order of debug
$this->log->debug(
group_id: $debug_id,
message: $error_string,
prefix: $prefix,
context: $context
);
break;
}
}
@@ -740,6 +865,7 @@ class IO
* Is called on base queries to reset error before each run
* Recent error history can be checked with
* dbGetErrorHistory or dbGetWarningHistory
*
* @return void
*/
private function __dbErrorReset(): void
@@ -751,6 +877,7 @@ class IO
/**
* Check if there is a cursor and write this cursors error info
*
* @param \PgSql\Result|false $cursor current cursor for pg_result_error,
* pg_last_error too, but pg_result_error
* is more accurate (PgSql\Result)
@@ -811,6 +938,7 @@ class IO
* error level, source as :: separated string
* additional pg error message if exists and optional msg given on error call
* all error messages are grouped by error_history_id set when errors are reset
*
* @param string $level
* @param string $error_id
* @param string $where_called
@@ -841,6 +969,7 @@ class IO
/**
* write an error
*
* @param integer $error_id Any Error ID, used in debug message string
* @param \PgSql\Result|false $cursor Optional cursor, passed on to preprocessor
* @param string $msg optional message added to debug
@@ -868,6 +997,7 @@ class IO
/**
* write a warning
*
* @param integer $warning_id Integer warning id added to debug
* @param \PgSql\Result|false $cursor Optional cursor, passed on to preprocessor
* @param string $msg optional message added to debug
@@ -895,6 +1025,7 @@ class IO
/**
* if there is the 'to_encoding' var set,
* and the field is in the wrong encoding converts it to the target
*
* @param array<mixed>|false $row Array from fetch_row
* @return array<mixed>|false Convert fetch_row array, or false
*/
@@ -903,11 +1034,8 @@ class IO
if (is_bool($row)) {
return false;
}
// only do if array, else pass through row (can be false)
if (
!is_array($row) ||
empty($this->to_encoding)
) {
// do not do anything if no to encoding is set
if (empty($this->to_encoding)) {
return $row;
}
// go through each row and convert the encoding if needed
@@ -929,8 +1057,70 @@ class IO
return $row;
}
/**
* Convert column content to the type in the name/pos field
* Note that on default it will only convert types that 100% map to PHP
* - intN
* - bool
* everything else will stay string.
* Fruther flags in the conert_type allow to convert:
* - json/jsonb to array
* - bytea to string
* Dangerous convert:
* - numeric/float to float (precision can be lost)
*
* @param array<mixed>|false $row
* @return array<mixed>|false
*/
private function __dbConvertType(array|false $row): array|false
{
if (is_bool($row)) {
return false;
}
// if convert type is not turned on
if (!$this->convert_type) {
return $row;
}
foreach ($row as $key => $value) {
// always bool/int
if (
$this->dbGetFieldType($key) != 'interval' &&
str_starts_with($this->dbGetFieldType($key) ?: '', 'int')
) {
$row[$key] = (int)$value;
}
if ($this->dbGetFieldType($key) == 'bool') {
$row[$key] = $this->dbBoolean($value);
}
if (
$this->convert_type & Convert::json->value &&
str_starts_with($this->dbGetFieldType($key) ?: '', 'json')
) {
$row[$key] = Json::jsonConvertToArray($value);
}
if (
$this->convert_type & Convert::numeric->value &&
(
str_starts_with($this->dbGetFieldType($key) ?: '', 'numeric') ||
str_starts_with($this->dbGetFieldType($key) ?: '', 'float')
// $this->dbGetFieldType($key) == 'real'
)
) {
$row[$key] = (float)$value;
}
if (
$this->convert_type & Convert::bytea->value &&
$this->dbGetFieldType($key) == 'bytea'
) {
$row[$key] = $this->dbUnescapeBytea($value);
}
}
return $row;
}
/**
* for debug purpose replaces $1, $2, etc with actual data
*
* @param string $query Query to replace values in
* @param array<mixed> $data The data array
* @return string string of query with data inside
@@ -964,7 +1154,9 @@ class IO
}
/**
* extracts schema and table from the query, if no schema returns just empty string
* extracts schema and table from the query,
* if no schema returns just empty string
*
* @param string $query insert/select/update/delete query
* @return array<mixed> array with schema and table
*/
@@ -1003,6 +1195,7 @@ class IO
/**
* check if there is another query running, or do we hang after a
* PHP error
*
* @param integer $timeout_seconds For complex timeout waits, default 3 seconds
* @return bool True for connection OK, else false
*/
@@ -1021,6 +1214,7 @@ class IO
/**
* dbReturn
* Read data from previous written data cache
*
* @param string $query_hash The hash for the current query
* @param bool $assoc_only Only return assoc value (key named)
* @return array<mixed> Current position query data from cache
@@ -1099,6 +1293,7 @@ class IO
* - checks for insert if returning is set/pk name
* - sets internal hash for query
* - checks multiple call count
*
* @param string $query Query string
* @param array<mixed> $params Query params, needed for hash creation
* @param string $pk_name primary key
@@ -1183,7 +1378,7 @@ class IO
) {
$this->returning_id = true;
}
// $this->debug('DB IO', 'Q: '.$this->query.', RETURN: '.$this->returning_id);
// $this->debug('DB IO', 'Q: ' . $this->query . ', RETURN: ' . $this->returning_id);
// for DEBUG, only on first time ;)
$this->__dbDebug(
'db',
@@ -1229,6 +1424,7 @@ class IO
/**
* runs post execute for rows affected, field names, inserted primary key, etc
*
* @return bool true on success or false if an error occured
*/
private function __dbPostExec(): bool
@@ -1254,11 +1450,18 @@ class IO
$this->field_names = [];
for ($i = 0; $i < $this->num_fields; $i++) {
$this->field_names[] = $this->db_functions->__dbFieldName($this->cursor, $i) ?: '';
// if (!empty($this->field_names[$i]))
// $this->field_name_types[$this->field_names[$i]] = null;
}
$this->field_types = [];
for ($i = 0; $i < $this->num_fields; $i++) {
$this->field_types[] = $this->db_functions->__dbFieldType($this->cursor, $i) ?: '';
}
// combined array
$this->field_name_types = array_combine(
$this->field_names,
$this->field_types
);
} elseif ($this->__checkQueryForInsert($this->query)) {
// if not select do here
// count affected rows
@@ -1292,6 +1495,7 @@ class IO
* insert_id_ext [DEPRECATED, all in insert_id_arr]
* - holds all returning as array
* TODO: Only use insert_id_arr and use functions to get ok array or single
*
* @param bool $returning_id
* @param string $query
* @param string|null $pk_name
@@ -1384,6 +1588,7 @@ class IO
* closes the db_connection
* normally this is not used, as the class deconstructor closes
* the connection down
*
* @return void has no return
*/
public function dbClose(): void
@@ -1405,6 +1610,7 @@ class IO
* returns the db init error
* if failed to connect it is set to false
* else true
*
* @return bool Connection status
*/
public function dbGetConnectionStatus(): bool
@@ -1414,6 +1620,7 @@ class IO
/**
* get certain settings like username, db name
*
* @param string $name what setting to query
* @return int|string|bool setting value, if not allowed name return false
*/
@@ -1458,6 +1665,7 @@ class IO
/**
* prints out status info from the connected DB (might be usefull for debug stuff)
*
* @param bool $log Show db connection info, default true
* if set to false won't write to error_msg var
* @param bool $strip Strip all HTML
@@ -1497,6 +1705,7 @@ class IO
/**
* Server version as integer value
*
* @return integer Version as integer
*/
public function dbVersionNumeric(): int
@@ -1506,6 +1715,7 @@ class IO
/**
* return current database version (server side) as string
*
* @return string database version as string
*/
public function dbVersion(): string
@@ -1515,6 +1725,7 @@ class IO
/**
* extended version info, can access all additional information data
*
* @param string $parameter Array parameter name, if not valid returns
* empty string
* @param bool $strip Strip extended server info string, default true
@@ -1528,6 +1739,7 @@ class IO
/**
* All possible parameter names for dbVersionInfo
*
* @return array<mixed> List of all parameter names
*/
public function dbVersionInfoParameters(): array
@@ -1537,6 +1749,7 @@ class IO
/**
* returns bool true or false if the string matches the database version
*
* @param string $compare string to match in type =X.Y, >X.Y, <X.Y, <=X.Y, >=X.Y
* @return bool true for ok, false on not ok
*/
@@ -1605,6 +1818,7 @@ class IO
/**
* dumps ALL data for this query, OR if no query given all in cursor_ext array
*
* @param string $query Query, if given, only from this quey (if found)
* else current cursor
* @return string Formated string with all the data in the array
@@ -1632,6 +1846,7 @@ class IO
/**
* neutral function to escape a string for DB writing
*
* @param string|int|float|bool $string string to escape
* @return string escaped string
*/
@@ -1643,6 +1858,7 @@ class IO
/**
* neutral function to escape a string for DB writing
* this one adds '' quotes around the string
*
* @param string|int|float|bool $string string to escape
* @return string escaped string
*/
@@ -1653,6 +1869,7 @@ class IO
/**
* string escape for column and table names
*
* @param string $string string to escape
* @return string escaped string
*/
@@ -1663,6 +1880,7 @@ class IO
/**
* escape data for writing to bytea type column field
*
* @param string $data data to escape to bytea
* @return string escaped bytea string
*/
@@ -1673,6 +1891,7 @@ class IO
/**
* unescape bytea data back to normal binrary data
*
* @param string $bytea bytea data stream
* @return string binary data string
*/
@@ -1683,6 +1902,7 @@ class IO
/**
* clear up any data for valid DB insert
*
* @param int|float|string|bool|null $value to escape data
* @param string $kbn escape trigger type
* @return string escaped value
@@ -1749,10 +1969,11 @@ class IO
* if the input is a single char 't' or 'f
* it will return the bool value instead
* also converts smallint 1/0 to true false
*
* @param string|bool|int $string 't' / 'f' or any string, or bool true/false
* @param bool $rev do reverse (bool to string)
* @return bool|string correct php bool true/false
* or postgresql 't'/'f'
* @return bool|string [default=false]: corretc postgresql -> php,
* true: convert php to postgresql
*/
public function dbBoolean(string|bool|int $string, bool $rev = false): bool|string
{
@@ -1784,6 +2005,7 @@ class IO
/**
* only for postgres. pretty formats an age or datetime difference string
*
* @param string $interval Age or interval/datetime difference
* @param bool $show_micro micro on off (default false)
* @return string Y/M/D/h/m/s formatted string (like timeStringFormat)
@@ -1835,6 +2057,7 @@ 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
*
* @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
@@ -1851,6 +2074,7 @@ class IO
/**
* returns an array of the table with columns and values. FALSE on no table found
*
* @param string $table table name
* @param string $schema optional schema name
* @return array<mixed>|false array of table data, false on error (table not found)
@@ -1954,6 +2178,10 @@ class IO
'data' => [],
// field names as array
'field_names' => [],
// field types as array (pos in field names is pos here)
'field_types' => [],
// name to type assoc array (from field names and field types)
'field_name_types' => [],
// number of fields (field names)
'num_fields' => 0,
// number of rows that will be maximum returned
@@ -2117,6 +2345,12 @@ class IO
);
}
$this->field_types = $this->cursor_ext[$query_hash]['field_types'];
// combined name => type
$this->cursor_ext[$query_hash]['field_name_types'] = array_combine(
$this->field_names,
$this->field_types
);
$this->field_name_types = $this->cursor_ext[$query_hash]['field_name_types'];
// reset first call var
$first_call = false;
// reset the internal pos counter
@@ -2139,9 +2373,11 @@ class IO
!is_int($this->cursor_ext[$query_hash]['cursor'])
) {
$return = $this->__dbConvertEncoding(
$this->db_functions->__dbFetchArray(
$this->cursor_ext[$query_hash]['cursor'],
$this->db_functions->__dbResultType($assoc_only)
$this->__dbConvertType(
$this->db_functions->__dbFetchArray(
$this->cursor_ext[$query_hash]['cursor'],
$this->db_functions->__dbResultType($assoc_only)
)
)
);
$this->cursor_ext[$query_hash]['log'][] = 'DB Reading data: '
@@ -2238,6 +2474,7 @@ class IO
* for INSERT INTO queries it is highly recommended to set the pk_name to avoid an
* additional read from the database for the PK NAME
* Wrapper for dbExecParams without params
*
* @param string $query the query, if not given,
* the query class var will be used
* if this was not set, method will quit with false
@@ -2307,10 +2544,9 @@ class IO
}
}
// add adbExecParams(string $query = '', array $params = [], string $pk_name = ')
/**
* executes a cursor and returns the data, if no more data 0 will be returned
*
* @param \PgSql\Result|false $cursor the cursor from db_exec or
* pg_query/pg_exec/mysql_query
* if not set will use internal cursor,
@@ -2333,9 +2569,11 @@ class IO
return false;
}
return $this->__dbConvertEncoding(
$this->db_functions->__dbFetchArray(
$cursor,
$this->db_functions->__dbResultType($assoc_only)
$this->__dbConvertType(
$this->db_functions->__dbFetchArray(
$cursor,
$this->db_functions->__dbResultType($assoc_only)
)
)
);
}
@@ -2343,6 +2581,7 @@ class IO
/**
* returns the FIRST row of the given query
* wrapper for dbReturnRowParms
*
* @param string $query the query to be executed
* @param bool $assoc_only if true, only return assoc entry (default false)
* @return array<mixed>|false row array or false on error
@@ -2388,6 +2627,7 @@ class IO
/**
* creates an array of hashes of the query (all data)
* Wrapper for dbReturnArrayParams
*
* @param string $query the query to be executed
* @param bool $assoc_only if true, only name ref are returned (default true)
* @return array<mixed>|false array of hashes (row -> fields), false on error
@@ -2432,6 +2672,20 @@ class IO
return $rows;
}
// ***************************
// CURSOR RETURN
// ***************************
/**
* Get current set cursor or false if not set or error
*
* @return \PgSql\Result|false
*/
public function dbGetCursor(): \PgSql\Result|false
{
return $this->cursor;
}
// ***************************
// CURSOR EXT CACHE RESET
// ***************************
@@ -2557,6 +2811,7 @@ class IO
/**
* gets how often a query was called already
*
* @param string $query query string
* @param array<mixed> $params If the query is params type we need params
* data to create a unique call one, optional
@@ -2581,6 +2836,7 @@ class IO
* for INSERT INTO queries it is highly recommended
* to set the pk_name to avoid an additional
* read from the database for the PK NAME
*
* @param string $stm_name statement name
* @param string $query queryt string to run
* @param string $pk_name optional primary key
@@ -2693,6 +2949,7 @@ class IO
/**
* runs a prepare query
*
* @param string $stm_name statement name for the query to run
* @param array<mixed> $data data to run for this query, empty array for none
* @return \PgSql\Result|false false on error, or result on OK
@@ -2791,6 +3048,7 @@ class IO
* executes the query async so other methods can be run at the same time
* Wrapper for dbExecParamsAsync
* NEEDS : dbCheckAsync
*
* @param string $query query to run
* @param string $pk_name optional primary key name, only used with
* insert for returning call
@@ -2890,6 +3148,7 @@ class IO
/**
* checks a previous async query and returns data if finished
* NEEDS : dbExecAsync
*
* @return \PgSql\Result|bool cursor resource if the query is still running,
* false if an error occured or cursor of that query
*/
@@ -2930,6 +3189,7 @@ class IO
/**
* Returns the current async running query hash
*
* @return string Current async running query hash
*/
public function dbGetAsyncRunning(): string
@@ -2948,6 +3208,7 @@ class IO
/**
* writes into one table based on array of table columns
*
* @param array<mixed> $write_array list of elements to write
* @param array<mixed> $not_write_array list of elements not to write
* @param int $primary_key id key to decide if we write insert or update
@@ -3163,6 +3424,54 @@ class IO
return $this->db_debug;
}
/**
* Undocumented function
*
* @param Convert $convert
* @return void
*/
public function dbSetConvertFlag(Convert $convert): void
{
$this->convert_type |= $convert->value;
}
/**
* Undocumented function
*
* @param Convert $convert
* @return void
*/
public function dbUnsetConvertFlag(Convert $convert): void
{
$this->convert_type &= ~$convert->value;
}
/**
* Reset to origincal config file set
*
* @return void
*/
public function dbResetConvertFlag(): void
{
foreach ($this->db_convert_type as $db_convert_type) {
$this->__setConvertType($db_convert_type);
}
}
/**
* Undocumented function
*
* @param Convert $convert
* @return bool
*/
public function dbGetConvertFlag(Convert $convert): bool
{
if ($this->convert_type & $convert->value) {
return true;
}
return false;
}
/**
* set max query calls, set to -1 to disable loop
* protection. this will generate a warning
@@ -3314,12 +3623,11 @@ class IO
* Alternative use dbSetEcnoding to trigger encoding change on the DB side
* Set to empty string to turn off
* @param string $encoding PHP Valid encoding to set
* @return string Current set encoding
* @return void
*/
public function dbSetToEncoding(string $encoding): string
public function dbSetToEncoding(string $encoding): void
{
$this->to_encoding = $encoding;
return $this->to_encoding;
}
/**
@@ -3555,6 +3863,45 @@ class IO
return $this->field_types;
}
/**
* Get the field name to type connection list
*
* @return array<string,string>
*/
public function dbGetFieldNameTypes(): array
{
return $this->field_name_types;
}
/**
* Get the field name for a position
*
* @param int $pos Position number in query
* @return false|string Field name or false for not found
*/
public function dbGetFieldName(int $pos): false|string
{
return $this->field_names[$pos] ?? false;
}
/**
* Return a field type for a field name or pos,
* will return false if field is not found in list
*
* @param string|int $name_pos Field name or pos to get the type for
* @return false|string Either the field type or
* false for not found in list
*/
public function dbGetFieldType(int|string $name_pos): false|string
{
if (is_numeric($name_pos)) {
$field_type = $this->field_types[$name_pos] ?? false;
} else {
$field_type = $this->field_name_types[$name_pos] ?? false;
}
return $field_type;
}
/**
* Returns the value for given key in statement
* Will write error if statemen id does not exist

View File

@@ -0,0 +1,63 @@
<?php
/**
* AUTOR: Clemens Schwaighofer
* CREATED: 2023/6/9
* DESCRIPTION:
* DB Options for convert type
*
* off: no conversion (all string)
* on: int/bool only
* json: json/jsonb to array
* numeric: any numeric or float to float
* bytes: decode bytea to string
*/
declare(strict_types=1);
namespace CoreLibs\DB\Options;
enum Convert: int
{
/** do not convert */
case off = 0;
/** convert only int/bool */
case on = 1;
/** also convert json to php array */
case json = 2;
/** also convert any float/real/numeric to php float */
case numeric = 4;
/** also decode bytea string to php string */
case bytea = 8;
/**
* get internal name from string value
*
* @param non-empty-string $name
* @return self
*/
public static function fromName(string $name): self
{
return match ($name) {
'Off', 'off', 'OFF', 'convert_off', 'CONVERT_OFF' => self::off,
'On', 'on', 'ON', 'convert_on', 'CONVERT_ON' => self::on,
'Json', 'json', 'JSON', 'convert_json', 'CONVERT_JSON' => self::json,
'Numeric', 'numeric', 'NUMERIC', 'convert_numeric', 'CONVERT_NUMERIC' => self::numeric,
'Bytea', 'bytea', 'BYTEA', 'convert_bytea', 'CONVERT_BYTEA' => self::bytea,
default => self::off,
};
}
/**
* Get internal name from int value
*
* @param int $value
* @return self
*/
public static function fromValue(int $value): self
{
return self::from($value);
}
}
// __END__

View File

@@ -528,7 +528,7 @@ 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
*
* @param string $level_str Log level we will write to
* @param Level $level Log level we will write to
* @param string|Stringable $message The message to write
* @param mixed[] $context Any additional info we want to attach in any format
* @param string $group_id A group id, only used in DEBUG level,
@@ -536,11 +536,15 @@ class Logging
* @return string
*/
private function prepareLog(
string $level_str,
Level $level,
string|\Stringable $message,
array $context = [],
string $group_id = '',
): string {
// only prepare if to write log level is in set log level
if (!$this->checkLogLevel($level)) {
return '';
}
// file + line: call not this but one before (the one that calls this)
$file_line = Support::getCallerFileLine(2) ??
System::getPageName(System::FULL_PATH);
@@ -555,7 +559,7 @@ class Logging
$timestamp = Support::printTime();
// if group id is empty replace it with current level
$group_str = $level_str;
$group_str = $level->getName();
if (!empty($group_id)) {
$group_str .= ':' . $group_id;
}
@@ -563,7 +567,7 @@ class Logging
$context_str = '';
if ($context != []) {
// TODO this here has to be changed to something better
$context_str = ' ' . print_r($context, true);
$context_str = ' :' . print_r($context, true);
}
// build log string
return '[' . $timestamp . '] '
@@ -906,6 +910,42 @@ class Logging
// MAIN CALLS
// *********************************************************************
/**
* Commong log interface
*
* extended with group_id, prefix that are ONLY used for debug level
*
* @param Level $level
* @param string|\Stringable $message
* @param mixed[] $context
* @param string $group_id
* @param string $prefix
* @return bool
*/
public function log(
Level $level,
string|\Stringable $message,
array $context = [],
string $group_id = '',
string $prefix = '',
): bool {
// if we are not debug, ignore group_id and prefix
if ($level != Level::Debug) {
$group_id = '';
$prefix = '';
}
return $this->writeErrorMsg(
$level,
$this->prepareLog(
$level,
$prefix . $message,
$context,
$group_id
),
$group_id
);
}
/**
* DEBUG: 100
*
@@ -913,23 +953,23 @@ class Logging
*
* @param string $group_id id for error message, groups messages together
* @param string|Stringable $message the actual error message
* @param mixed[] $context
* @param string $prefix Attach some block before $string.
* Will not be stripped even
* when strip is true
* if strip is false, recommended to add that to $string
* @param mixed[] $context
* @return bool True if logged, false if not logged
*/
public function debug(
string $group_id,
string|\Stringable $message,
string $prefix = '',
array $context = []
array $context = [],
string $prefix = ''
): bool {
return $this->writeErrorMsg(
$this->log_level,
Level::Debug,
$this->prepareLog(
Level::Debug->getName(),
Level::Debug,
$prefix . $message,
$context,
$group_id
@@ -950,7 +990,7 @@ class Logging
return $this->writeErrorMsg(
Level::Info,
$this->prepareLog(
Level::Info->getName(),
Level::Info,
$message,
$context,
)
@@ -969,7 +1009,7 @@ class Logging
return $this->writeErrorMsg(
Level::Notice,
$this->prepareLog(
Level::Notice->getName(),
Level::Notice,
$message,
$context,
)
@@ -988,7 +1028,7 @@ class Logging
return $this->writeErrorMsg(
Level::Warning,
$this->prepareLog(
Level::Warning->getName(),
Level::Warning,
$message,
$context,
)
@@ -1007,7 +1047,7 @@ class Logging
return $this->writeErrorMsg(
Level::Error,
$this->prepareLog(
Level::Error->getName(),
Level::Error,
$message,
$context,
)
@@ -1026,7 +1066,7 @@ class Logging
return $this->writeErrorMsg(
Level::Critical,
$this->prepareLog(
Level::Critical->getName(),
Level::Critical,
$message,
$context,
)
@@ -1045,7 +1085,7 @@ class Logging
return $this->writeErrorMsg(
Level::Alert,
$this->prepareLog(
Level::Alert->getName(),
Level::Alert,
$message,
$context,
)
@@ -1064,7 +1104,7 @@ class Logging
return $this->writeErrorMsg(
Level::Emergency,
$this->prepareLog(
Level::Emergency->getName(),
Level::Emergency,
$message,
$context,
)

View File

@@ -308,7 +308,8 @@ class Generate extends \CoreLibs\DB\Extended\ArrayIO
/**
* construct form generator
*
* @param array<mixed> $db_config db config array, mandatory
* phpcs:ignore
* @param array{db_name:string,db_user:string,db_pass:string,db_host:string,db_port:int,db_schema:string,db_encoding:string,db_type:string,db_ssl:string,db_convert_type?:string[]} $db_config db config array, mandatory
* @param \CoreLibs\Logging\Logging $log Logging class
* @param \CoreLibs\Language\L10n $l10n l10n language class
* @param array<string,mixed> $login_acl Login ACL array,

View File

@@ -36,6 +36,7 @@ class Image
): string|false {
// get image type flags
$image_types = [
0 => 'UNKOWN-IMAGE',
1 => 'gif',
2 => 'jpg',
3 => 'png'
@@ -69,7 +70,7 @@ class Image
}
// does this picture exist and is it a picture
if (file_exists($filename) && is_file($filename)) {
[$width, $height, $type] = getimagesize($filename) ?: [];
[$width, $height, $type] = getimagesize($filename) ?: [0, 0, 0];
$convert_prefix = '';
$create_file = false;
$delete_filename = '';
@@ -98,7 +99,7 @@ class Image
if (!is_file($filename)) {
$filename .= '-0';
}
[$width, $height, $type] = getimagesize($filename) ?: [];
[$width, $height, $type] = getimagesize($filename) ?: [0, 0, 0];
}
// if no size given, set size to original
if (!$size_x || $size_x < 1) {
@@ -117,7 +118,7 @@ class Image
$status = exec($convert_string, $output, $return);
// get the size of the converted data, if converted
if (is_file($thumbnail)) {
[$width, $height, $type] = getimagesize($thumbnail) ?: [];
[$width, $height, $type] = getimagesize($thumbnail) ?: [0, 0, 0];
}
}
if ($height > $size_y) {
@@ -217,7 +218,7 @@ class Image
return $thumbnail;
}
// $this->debug('IMAGE PREPARE', "FILENAME OK, THUMB WIDTH/HEIGHT OK");
[$inc_width, $inc_height, $img_type] = getimagesize($filename) ?: [];
[$inc_width, $inc_height, $img_type] = getimagesize($filename) ?: [0, 0, null];
$thumbnail_write_path = null;
$thumbnail_web_path = null;
// path set first
@@ -447,7 +448,7 @@ class Image
if (!function_exists('exif_read_data') || !is_writeable($filename)) {
return;
}
[$inc_width, $inc_height, $img_type] = getimagesize($filename) ?: [];
[$inc_width, $inc_height, $img_type] = getimagesize($filename) ?: [0, 0, null];
// add @ to avoid "file not supported error"
$exif = @exif_read_data($filename);
$orientation = null;

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

@@ -39,6 +39,7 @@ return array(
'CoreLibs\\Create\\Uids' => $baseDir . '/lib/CoreLibs/Create/Uids.php',
'CoreLibs\\DB\\Extended\\ArrayIO' => $baseDir . '/lib/CoreLibs/DB/Extended/ArrayIO.php',
'CoreLibs\\DB\\IO' => $baseDir . '/lib/CoreLibs/DB/IO.php',
'CoreLibs\\DB\\Options\\Convert' => $baseDir . '/lib/CoreLibs/DB/Options/Convert.php',
'CoreLibs\\DB\\SQL\\Interface\\SqlFunctions' => $baseDir . '/lib/CoreLibs/DB/SQL/Interface/SqlFunctions.php',
'CoreLibs\\DB\\SQL\\PgSQL' => $baseDir . '/lib/CoreLibs/DB/SQL/PgSQL.php',
'CoreLibs\\Debug\\FileWriter' => $baseDir . '/lib/CoreLibs/Debug/FileWriter.php',
@@ -76,6 +77,12 @@ return array(
'CoreLibs\\Security\\CreateKey' => $baseDir . '/lib/CoreLibs/Security/CreateKey.php',
'CoreLibs\\Security\\Password' => $baseDir . '/lib/CoreLibs/Security/Password.php',
'CoreLibs\\Security\\SymmetricEncryption' => $baseDir . '/lib/CoreLibs/Security/SymmetricEncryption.php',
'CoreLibs\\Template\\HtmlBuilder\\Block' => $baseDir . '/lib/CoreLibs/Template/HtmlBuilder/Block.php',
'CoreLibs\\Template\\HtmlBuilder\\Element' => $baseDir . '/lib/CoreLibs/Template/HtmlBuilder/Element.php',
'CoreLibs\\Template\\HtmlBuilder\\General\\Error' => $baseDir . '/lib/CoreLibs/Template/HtmlBuilder/General/Error.php',
'CoreLibs\\Template\\HtmlBuilder\\General\\HtmlBuilderExcpetion' => $baseDir . '/lib/CoreLibs/Template/HtmlBuilder/General/HtmlBuilderExcpetion.php',
'CoreLibs\\Template\\HtmlBuilder\\General\\Settings' => $baseDir . '/lib/CoreLibs/Template/HtmlBuilder/General/Settings.php',
'CoreLibs\\Template\\HtmlBuilder\\StringReplace' => $baseDir . '/lib/CoreLibs/Template/HtmlBuilder/StringReplace.php',
'CoreLibs\\Template\\SmartyExtend' => $baseDir . '/lib/CoreLibs/Template/SmartyExtend.php',
'FileUpload\\Core\\qqUploadedFile' => $baseDir . '/lib/FileUpload/Core/qqUploadedFile.php',
'FileUpload\\Core\\qqUploadedFileForm' => $baseDir . '/lib/FileUpload/Core/qqUploadedFileForm.php',

View File

@@ -90,6 +90,7 @@ class ComposerStaticInit10fe8fe2ec4017b8644d2b64bcf398b9
'CoreLibs\\Create\\Uids' => __DIR__ . '/../..' . '/lib/CoreLibs/Create/Uids.php',
'CoreLibs\\DB\\Extended\\ArrayIO' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/Extended/ArrayIO.php',
'CoreLibs\\DB\\IO' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/IO.php',
'CoreLibs\\DB\\Options\\Convert' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/Options/Convert.php',
'CoreLibs\\DB\\SQL\\Interface\\SqlFunctions' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/SQL/Interface/SqlFunctions.php',
'CoreLibs\\DB\\SQL\\PgSQL' => __DIR__ . '/../..' . '/lib/CoreLibs/DB/SQL/PgSQL.php',
'CoreLibs\\Debug\\FileWriter' => __DIR__ . '/../..' . '/lib/CoreLibs/Debug/FileWriter.php',
@@ -127,6 +128,12 @@ class ComposerStaticInit10fe8fe2ec4017b8644d2b64bcf398b9
'CoreLibs\\Security\\CreateKey' => __DIR__ . '/../..' . '/lib/CoreLibs/Security/CreateKey.php',
'CoreLibs\\Security\\Password' => __DIR__ . '/../..' . '/lib/CoreLibs/Security/Password.php',
'CoreLibs\\Security\\SymmetricEncryption' => __DIR__ . '/../..' . '/lib/CoreLibs/Security/SymmetricEncryption.php',
'CoreLibs\\Template\\HtmlBuilder\\Block' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/HtmlBuilder/Block.php',
'CoreLibs\\Template\\HtmlBuilder\\Element' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/HtmlBuilder/Element.php',
'CoreLibs\\Template\\HtmlBuilder\\General\\Error' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/HtmlBuilder/General/Error.php',
'CoreLibs\\Template\\HtmlBuilder\\General\\HtmlBuilderExcpetion' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/HtmlBuilder/General/HtmlBuilderExcpetion.php',
'CoreLibs\\Template\\HtmlBuilder\\General\\Settings' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/HtmlBuilder/General/Settings.php',
'CoreLibs\\Template\\HtmlBuilder\\StringReplace' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/HtmlBuilder/StringReplace.php',
'CoreLibs\\Template\\SmartyExtend' => __DIR__ . '/../..' . '/lib/CoreLibs/Template/SmartyExtend.php',
'FileUpload\\Core\\qqUploadedFile' => __DIR__ . '/../..' . '/lib/FileUpload/Core/qqUploadedFile.php',
'FileUpload\\Core\\qqUploadedFileForm' => __DIR__ . '/../..' . '/lib/FileUpload/Core/qqUploadedFileForm.php',