This commit is contained in:
Dennis Eichhorn 2024-02-04 20:34:13 +00:00
parent 84fa78359b
commit e7b708185e
41 changed files with 377 additions and 334 deletions

View File

@ -144,9 +144,7 @@ final class MarkovChain
}
// Couldn't find possible key
if ($new === null) {
$new = $orderValues[\array_rand($orderValues)];
}
$new ??= $orderValues[\array_rand($orderValues)];
$output[] = $new;
$key[] = $new;

View File

@ -25,7 +25,8 @@ use phpOMS\Math\Stochastic\Distribution\NormalDistribution;
* @since 1.0.0
* @see https://www.moserware.com/assets/computing-your-skill/The%20Math%20Behind%20TrueSkill.pdf
*
* @todo implement https://github.com/sublee/trueskill/blob/master/trueskill/__init__.py
* @todo Implement https://github.com/sublee/trueskill/blob/master/trueskill/__init__.py
* https://github.com/Karaka-Management/phpOMS/issues/337
*/
class TrueSkill
{

View File

@ -1,5 +0,0 @@
<?php
declare(strict_types=1);
// @todo implement address validation (google?)

View File

@ -18,7 +18,7 @@ namespace phpOMS\Business\Sales;
* Market share calculations (Zipf function)
*
* This class can be used to calculate the market share based on a rank or vice versa
* the rank based on a marketshare in a Zipf distributed market.
* the rank based on a market share in a Zipf distributed market.
*
* @package phpOMS\Business\Sales
* @license OMS License 2.0

View File

@ -0,0 +1,44 @@
<?php
/**
* Jingga
*
* PHP Version 8.1
*
* @package phpOMS\Business\Sales
* @copyright Dennis Eichhorn
* @license OMS License 2.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace phpOMS\Business\Sales;
/**
* Order suggestion calculations
*
* @package phpOMS\Business\Sales
* @license OMS License 2.0
* @link https://jingga.app
* @since 1.0.0
*/
final class OrderSuggestion
{
/**
* Constructor
*
* @since 1.0.0
* @codeCoverageIgnore
*/
private function __construct()
{
}
/**
* Calculate the optimal order quantity using the Andler formula
*/
public static function andler(float $annualQuantity, float $orderCosts, float $unitPrice, float $warehousingCostRatio) : float
{
return \sqrt(2 * $annualQuantity * $orderCosts / ($unitPrice * $warehousingCostRatio));
}
}

View File

@ -509,9 +509,7 @@ class DataMapperFactory
$propertyName = $member ?? static::COLUMNS[static::PRIMARYFIELD]['internal'];
if (static::COLUMNS[static::PRIMARYFIELD]['private'] ?? false) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$refClass ??= new \ReflectionClass($obj);
$refProp = $refClass->getProperty($propertyName);
@ -538,9 +536,7 @@ class DataMapperFactory
\settype($objId, static::COLUMNS[static::PRIMARYFIELD]['type']);
if (static::COLUMNS[static::PRIMARYFIELD]['private'] ?? false) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$refClass ??= new \ReflectionClass($obj);
$refProp = $refClass->getProperty($propertyName);
$refProp->setValue($obj, $objId);

View File

@ -78,10 +78,10 @@ final class DeleteMapper extends DataMapperAbstract
return null;
}
$this->deleteSingleRelation($obj, $this->mapper::BELONGS_TO, $refClass);
$this->deleteSingleRelation($obj, $this->mapper::OWNS_ONE, $refClass);
$this->deleteHasMany($obj, $objId, $refClass);
$this->deleteModel($objId);
$this->deleteSingleRelation($obj, $this->mapper::OWNS_ONE, $refClass);
$this->deleteSingleRelation($obj, $this->mapper::BELONGS_TO, $refClass);
return $objId;
}
@ -141,9 +141,7 @@ final class DeleteMapper extends DataMapperAbstract
$value = null;
if ($isPrivate) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$refClass ??= new \ReflectionClass($obj);
$refProp = $refClass->getProperty($member);
$value = $refProp->getValue($obj);
@ -173,7 +171,6 @@ final class DeleteMapper extends DataMapperAbstract
}
foreach ($this->mapper::HAS_MANY as $member => $rel) {
// always
if (!isset($this->with[$member]) && !isset($rel['external'])) {
continue;
}
@ -183,9 +180,7 @@ final class DeleteMapper extends DataMapperAbstract
$values = null;
if ($isPrivate) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$refClass ??= new \ReflectionClass($obj);
$refProp = $refClass->getProperty($member);
$values = $refProp->getValue($obj);
@ -252,7 +247,7 @@ final class DeleteMapper extends DataMapperAbstract
->where($this->mapper::HAS_MANY[$member]['table'] . '.' . $this->mapper::HAS_MANY[$member]['self'], '=', $objId);
if ($objIds !== null) {
$relQuery->where($this->mapper::HAS_MANY[$member]['table'] . '.' . $this->mapper::HAS_MANY[$member]['external'], 'in', $objIds);
$relQuery->where($this->mapper::HAS_MANY[$member]['table'] . '.' . $this->mapper::HAS_MANY[$member]['external'], 'IN', $objIds);
}
$sth = $this->db->con->prepare($relQuery->toSql());

View File

@ -26,8 +26,9 @@ use phpOMS\Utils\ArrayUtils;
* @link https://jingga.app
* @since 1.0.0
*
* @todo Add memory cache per read mapper parent call (These should be cached: attribute types, file types, etc.)
* @todo Add getArray functions to get array instead of object
* https://github.com/Karaka-Management/phpOMS/issues/350
*
* @todo Allow to define columns in all functions instead of members?
*
* @template R
@ -558,6 +559,9 @@ final class ReadMapper extends DataMapperAbstract
// This is necessary for special cases, e.g. when joining in the other direction
// Example: Show all profiles who have written a news article.
// "with()" only allows to go from articles to accounts but we want to go the other way
// @feature Create join functionality for mappers which supports joining and filtering based on other tables
// Example: show all profiles which have written a news article
// https://github.com/Karaka-Management/phpOMS/issues/253
foreach ($this->join as $member => $values) {
if (($col = $this->mapper::getColumnByMember($member)) === null) {
continue;
@ -573,20 +577,22 @@ final class ReadMapper extends DataMapperAbstract
if (isset($join['mapper']::HAS_MANY[$join['value']])) {
if (isset($join['mapper']::HAS_MANY[$join['value']]['external'])) {
$relJoinTable = $join['mapper']::HAS_MANY[$join['value']]['table'];
// join with relation table
$query->join($join['mapper']::HAS_MANY[$join['value']]['table'], $join['type'], $join['mapper']::HAS_MANY[$join['value']]['table'] . '_d' . ($this->depth + 1) . $this->joinAlias)
$query->join($relJoinTable, $join['type'], $relJoinTable . '_d' . ($this->depth + 1) . $this->joinAlias)
->on(
$this->mapper::TABLE . '_d' . $this->depth . $this->joinAlias . '.' . $col,
'=',
$join['mapper']::HAS_MANY[$join['value']]['table'] . '_d' . ($this->depth + 1) . $this->joinAlias . '.' . $join['mapper']::HAS_MANY[$join['value']]['external'],
$relJoinTable . '_d' . ($this->depth + 1) . $this->joinAlias . '.' . $join['mapper']::HAS_MANY[$join['value']]['external'],
'AND',
$join['mapper']::HAS_MANY[$join['value']]['table'] . '_d' . ($this->depth + 1) . $this->joinAlias
$relJoinTable . '_d' . ($this->depth + 1) . $this->joinAlias
);
// join with model table
$query->join($join['mapper']::TABLE, $join['type'], $join['mapper']::TABLE . '_d' . ($this->depth + 1) . $this->joinAlias)
->on(
$join['mapper']::HAS_MANY[$join['value']]['table'] . '_d' . ($this->depth + 1) . $this->joinAlias . '.' . $join['mapper']::HAS_MANY[$join['value']]['self'],
$relJoinTable . '_d' . ($this->depth + 1) . $this->joinAlias . '.' . $join['mapper']::HAS_MANY[$join['value']]['self'],
'=',
$join['mapper']::TABLE . '_d' . ($this->depth + 1) . $this->joinAlias . '.' . $join['mapper']::PRIMARYFIELD,
'AND',
@ -755,8 +761,9 @@ final class ReadMapper extends DataMapperAbstract
$query->orderBy($this->mapper::TABLE . '_d' . $this->depth . $this->joinAlias . '.' . $column, $sort['order']);
// @bug It looks like that only one sort parameter is supported despite SQL supporting multiple
// https://github.com/Karaka-Management/phpOMS/issues/364
break; // there is only one root element (one element with child === '')
// @todo Is this true? sort can have multiple sort components!!!
}
}
@ -792,28 +799,28 @@ final class ReadMapper extends DataMapperAbstract
{
$refClass = null;
$aValue = null;
$arrayPath = '';
foreach ($this->mapper::COLUMNS as $column => $def) {
$alias = $column . '_d' . $this->depth . $this->joinAlias;
if (!\array_key_exists($alias, $result)) {
continue;
}
$value = $result[$alias];
$value = $result[$alias];
$hasPath = false;
$aValue = [];
$arrayPath = '';
$refProp = null;
$isPrivate = $def['private'] ?? false;
$member = '';
$member = $def['internal'];
if ($isPrivate && $refClass === null) {
$refClass = new \ReflectionClass($obj);
}
if (\stripos($def['internal'], '/') !== false) {
if (\stripos($member, '/') !== false) {
$hasPath = true;
$path = \explode('/', \ltrim($def['internal'], '/'));
$path = \explode('/', \ltrim($member, '/'));
$member = $path[0];
if ($isPrivate) {
@ -825,15 +832,12 @@ final class ReadMapper extends DataMapperAbstract
\array_shift($path);
$arrayPath = \implode('/', $path);
} else {
if ($isPrivate) {
$refProp = $refClass->getProperty($def['internal']);
}
$member = $def['internal'];
} elseif ($isPrivate) {
$refProp = $refClass->getProperty($member);
}
if (isset($this->mapper::OWNS_ONE[$def['internal']])) {
$type = $def['type'];
if (isset($this->mapper::OWNS_ONE[$member])) {
$default = null;
if (!isset($this->with[$member])
&& ($isPrivate ? $refProp->isInitialized($obj) : isset($obj->{$member}))
@ -841,15 +845,8 @@ final class ReadMapper extends DataMapperAbstract
$default = $isPrivate ? $refProp->getValue($obj) : $obj->{$member};
}
$value = $this->populateOwnsOne($def['internal'], $result, $default);
if (empty($value)) {
// @todo find better solution. this was because of a bug with the sales billing list query depth = 4.
// The address was set (from the client, referral or creator) but then somehow there was a second address element which was all null and null cannot be assigned to a string variable (e.g. country).
// The problem with this solution is that if the model expects an initialization (e.g. at lest set the elements to null, '', 0 etc.) this is now not done.
$value = $isPrivate ? $refProp->getValue($obj) : $obj->{$member};
}
} elseif (isset($this->mapper::BELONGS_TO[$def['internal']])) {
$value = $this->populateOwnsOne($member, $result, $default);
} elseif (isset($this->mapper::BELONGS_TO[$member])) {
$default = null;
if (!isset($this->with[$member])
&& ($isPrivate ? $refProp->isInitialized($obj) : isset($obj->{$member}))
@ -857,39 +854,39 @@ final class ReadMapper extends DataMapperAbstract
$default = $isPrivate ? $refProp->getValue($obj) : $obj->{$member};
}
$value = $this->populateBelongsTo($def['internal'], $result, $default);
} elseif (\in_array($def['type'], ['string', 'compress', 'int', 'float', 'bool'])) {
if ($value !== null && $def['type'] === 'compress') {
$def['type'] = 'string';
$value = $this->populateBelongsTo($member, $result, $default);
} elseif (\in_array($type, ['string', 'compress', 'int', 'float', 'bool'])) {
if ($value !== null && $type === 'compress') {
$type = 'string';
$value = \gzinflate($value);
}
$mValue = $isPrivate ? $refProp->getValue($obj) : $obj->{$member};
if ($value !== null || $mValue !== null) {
\settype($value, $def['type']);
if ($value !== null
|| ($isPrivate ? $refProp->getValue($obj) !== null : $obj->{$member} !== null)
) {
\settype($value, $type);
}
if ($hasPath) {
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true);
}
} elseif ($def['type'] === 'DateTime') {
} elseif ($type === 'DateTime') {
$value = $value === null ? null : new \DateTime($value);
if ($hasPath) {
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true);
}
} elseif ($def['type'] === 'DateTimeImmutable') {
} elseif ($type === 'DateTimeImmutable') {
$value = $value === null ? null : new \DateTimeImmutable($value);
if ($hasPath) {
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true);
}
} elseif ($def['type'] === 'Json') {
} elseif ($type === 'Json') {
if ($hasPath) {
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true);
}
$value = \json_decode($value, true);
} elseif ($def['type'] === 'Serializable') {
} elseif ($type === 'Serializable') {
$mObj = $isPrivate ? $refProp->getValue($obj) : $obj->{$member};
if ($mObj !== null && $value !== null) {
@ -905,10 +902,19 @@ final class ReadMapper extends DataMapperAbstract
}
}
// @todo How is this allowed? at the bottom we set $obj->hasMany = value. A hasMany should be always an array?!
if (empty($this->with)) {
return $obj;
}
// This is only for hasMany elements where only one hasMany child object is loaded
// Example: A model usually only loads one l11n element despite having localizations for multiple languages
// @todo The code below is basically a copy of the foreach from above.
// Maybe we can combine them in a smart way without adding much overhead
foreach ($this->mapper::HAS_MANY as $member => $def) {
// Only if column is defined do we have a pseudo 1-to-1 relation
// The content of the column will be loaded directly in the member variable
if (!isset($this->with[$member])
|| !isset($def['column']) // @todo is this required? The code below indicates that this might be stupid
|| !isset($def['column'])
) {
continue;
}
@ -922,8 +928,6 @@ final class ReadMapper extends DataMapperAbstract
$value = $result[$alias];
$hasPath = false;
$aValue = null;
$arrayPath = '/';
$refProp = null;
$isPrivate = $def['private'] ?? false;
@ -933,16 +937,18 @@ final class ReadMapper extends DataMapperAbstract
if (\stripos($member, '/') !== false) {
$hasPath = true;
$path = \explode('/', $member);
$path = \explode('/', \ltrim($member, '/'));
$member = $path[0];
if ($isPrivate) {
$refProp = $refClass->getProperty($path[0]);
$aValue = $refProp->getValue($obj);
} else {
$aValue = $obj->{$path[0]};
}
\array_shift($path);
$arrayPath = \implode('/', $path);
$aValue = $isPrivate ? $refProp->getValue($obj) : $obj->{$path[0]};
} elseif ($isPrivate) {
$refProp = $refClass->getProperty($member);
}
@ -964,12 +970,12 @@ final class ReadMapper extends DataMapperAbstract
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true);
}
} elseif ($type === 'DateTime') {
$value ??= new \DateTime($value);
$value = $value === null ? null : new \DateTime($value);
if ($hasPath) {
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true);
}
} elseif ($type === 'DateTimeImmutable') {
$value ??= new \DateTimeImmutable($value);
$value = $value === null ? null : new \DateTimeImmutable($value);
if ($hasPath) {
$value = ArrayUtils::setArray($arrayPath, $aValue, $value, '/', true);
}
@ -1022,13 +1028,9 @@ final class ReadMapper extends DataMapperAbstract
} else {
return $default;
}
}
if (isset($this->mapper::OWNS_ONE[$member]['column'])) {
} elseif (isset($this->mapper::OWNS_ONE[$member]['column'])) {
return $result[$mapper::getColumnByMember($this->mapper::OWNS_ONE[$member]['column']) . '_d' . $this->depth . '_' . $member];
}
if (!isset($result[$mapper::PRIMARYFIELD . '_d' . ($this->depth + 1) . '_' . $member])) {
} elseif (!isset($result[$mapper::PRIMARYFIELD . '_d' . ($this->depth + 1) . '_' . $member])) {
return $mapper::createNullModel();
}
@ -1057,28 +1059,23 @@ final class ReadMapper extends DataMapperAbstract
$mapper = $this->mapper::BELONGS_TO[$member]['mapper'];
if (!isset($this->with[$member])) {
if (\array_key_exists($this->mapper::BELONGS_TO[$member]['external'] . '_d' . $this->depth . '_' . $member, $result)) {
if (\array_key_exists($this->mapper::BELONGS_TO[$member]['external'] . '_d' . $this->depth . $this->joinAlias, $result)) {
return isset($this->mapper::BELONGS_TO[$member]['column'])
? $result[$this->mapper::BELONGS_TO[$member]['external'] . '_d' . $this->depth . '_' . $member]
: $mapper::createNullModel($result[$this->mapper::BELONGS_TO[$member]['external'] . '_d' . $this->depth . '_' . $member]);
? $result[$this->mapper::BELONGS_TO[$member]['external'] . '_d' . $this->depth . $this->joinAlias]
: $mapper::createNullModel($result[$this->mapper::BELONGS_TO[$member]['external'] . '_d' . $this->depth . $this->joinAlias]);
} else {
return $default;
}
}
if (isset($this->mapper::BELONGS_TO[$member]['column'])) {
} elseif (isset($this->mapper::BELONGS_TO[$member]['column'])) {
return $result[$mapper::getColumnByMember($this->mapper::BELONGS_TO[$member]['column']) . '_d' . $this->depth . '_' . $member];
}
if (!isset($result[$mapper::PRIMARYFIELD . '_d' . ($this->depth + 1) . '_' . $member])) {
} elseif (!isset($result[$mapper::PRIMARYFIELD . '_d' . ($this->depth + 1) . '_' . $member])) {
return $mapper::createNullModel();
}
} elseif (isset($this->mapper::BELONGS_TO[$member]['by'])) {
// get the belongs to based on a different column (not primary key)
// this is often used if the value is actually a different model:
// you want the profile but the account id is referenced
// in this case you can get the profile by loading the profile based on the account reference column
// get the belongs to based on a different column (not primary key)
// this is often used if the value is actually a different model:
// you want the profile but the account id is referenced
// in this case you can get the profile by loading the profile based on the account reference column
if (isset($this->mapper::BELONGS_TO[$member]['by'])) {
/** @var self $belongsToMapper */
$belongsToMapper = $this->createRelationMapper($mapper::get($this->db), $member);
$belongsToMapper->depth = $this->depth + 1;
@ -1086,7 +1083,7 @@ final class ReadMapper extends DataMapperAbstract
$belongsToMapper->where(
$this->mapper::BELONGS_TO[$member]['by'],
$result[$mapper::getColumnByMember($this->mapper::BELONGS_TO[$member]['by']) . '_d' . ($this->depth + 1) . $this->joinAlias],
$result[$mapper::getColumnByMember($this->mapper::BELONGS_TO[$member]['by']) . '_d' . ($this->depth + 1) . '_' . $member],
'='
);
@ -1162,9 +1159,7 @@ final class ReadMapper extends DataMapperAbstract
}
if ($isPrivate) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$refClass ??= new \ReflectionClass($obj);
foreach ($primaryKeys as $idx => $key) {
if (!isset($objects[$key])) {
@ -1208,9 +1203,7 @@ final class ReadMapper extends DataMapperAbstract
$tempObjs = [];
if ($isPrivate) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$refClass ??= new \ReflectionClass($obj);
$refProp = $refClass->getProperty($member);
@ -1250,8 +1243,9 @@ final class ReadMapper extends DataMapperAbstract
$refClass = null;
// @todo check if there are more cases where the relation is already loaded with joins etc.
// there can be pseudo hasMany elements like localizations. They are hasMany but these are already loaded with joins!
// @performance Check if there are more cases where the relation is already loaded with joins etc.
// There can be pseudo hasMany elements like localizations. They are hasMany but these are already loaded with joins!
// Variation of https://github.com/Karaka-Management/phpOMS/issues/363
foreach ($this->with as $member => $withData) {
if (isset($this->mapper::HAS_MANY[$member])) {
$many = $this->mapper::HAS_MANY[$member];
@ -1259,7 +1253,6 @@ final class ReadMapper extends DataMapperAbstract
continue;
}
// @todo withData doesn't store this directly, it is in [0]['private] ?!?!
$isPrivate = $many['private'] ?? false;
$objectMapper = $this->createRelationMapper($many['mapper']::exists(db: $this->db), $member);
@ -1295,9 +1288,7 @@ final class ReadMapper extends DataMapperAbstract
$isPrivate = $relation['private'] ?? false;
if ($isPrivate) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$refClass ??= new \ReflectionClass($obj);
$refProp = $refClass->getProperty($member);
return $relMapper->hasManyRelations($refProp->getValue($obj));

View File

@ -115,7 +115,10 @@ final class UpdateMapper extends DataMapperAbstract
->where($this->mapper::TABLE . '.' . $this->mapper::PRIMARYFIELD, '=', $objId);
foreach ($this->mapper::COLUMNS as $column) {
$propertyName = \stripos($column['internal'], '/') !== false ? \explode('/', $column['internal'])[0] : $column['internal'];
$propertyName = \stripos($column['internal'], '/') !== false
? \explode('/', $column['internal'])[0]
: $column['internal'];
if (isset($this->mapper::HAS_MANY[$propertyName])
|| $column['internal'] === $this->mapper::PRIMARYFIELD
|| (($column['readonly'] ?? false) && !isset($this->with[$propertyName]))
@ -129,9 +132,7 @@ final class UpdateMapper extends DataMapperAbstract
$tValue = null;
if ($isPrivate) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$refClass ??= new \ReflectionClass($obj);
$property = $refClass->getProperty($propertyName);
$tValue = $property->getValue($obj);
@ -273,9 +274,7 @@ final class UpdateMapper extends DataMapperAbstract
$values = null;
if ($isPrivate) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$refClass ??= new \ReflectionClass($obj);
$property = $refClass->getProperty($propertyName);
$values = $property->getValue($obj);

View File

@ -119,9 +119,7 @@ final class WriteMapper extends DataMapperAbstract
$tValue = null;
if ($column['private'] ?? false) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$refClass ??= new \ReflectionClass($obj);
$property = $refClass->getProperty($propertyName);
$tValue = $property->getValue($obj);
@ -227,7 +225,6 @@ final class WriteMapper extends DataMapperAbstract
if (isset($this->mapper::BELONGS_TO[$propertyName]['by'])) {
// has by (obj is stored as a different model e.g. model = profile but reference/db is account)
if ($this->mapper::BELONGS_TO[$propertyName]['private'] ?? false) {
$refClass = new \ReflectionClass($obj);
$refProp = $refClass->getProperty($this->mapper::BELONGS_TO[$propertyName]['by']);
@ -241,7 +238,8 @@ final class WriteMapper extends DataMapperAbstract
$mapper = $this->mapper::BELONGS_TO[$propertyName]['mapper'];
$primaryKey = $mapper::getObjectId($obj);
// @todo the $mapper::create() might cause a problem if 'by' is set. because we don't want to create this obj but the child obj.
// @bug The $mapper::create() might cause a problem if 'by' is set.
// This is because we don't want to create this obj but the child obj.
return empty($primaryKey)
? $mapper::create(db: $this->db)->execute($obj)
: $primaryKey;
@ -272,9 +270,7 @@ final class WriteMapper extends DataMapperAbstract
$values = null;
if ($isPrivate) {
if ($refClass === null) {
$refClass = new \ReflectionClass($obj);
}
$refClass ??= new \ReflectionClass($obj);
$property = $refClass->getProperty($propertyName);
$values = $property->getValue($obj);
@ -285,13 +281,11 @@ final class WriteMapper extends DataMapperAbstract
/** @var class-string<DataMapperFactory> $mapper */
$mapper = $this->mapper::HAS_MANY[$propertyName]['mapper'];
$internalName = $mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['internal'] ?? 'ERROR-BAD-SELF';
// @todo this or $isRelPrivate is wrong, don't know which one.
$isInternalPrivate = $mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['private'] ?? false;
$isRelPrivate = $mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['private'] ?? false;
if (\is_object($values)) {
// conditionals
if ($isInternalPrivate) {
if ($isRelPrivate) {
$relReflectionClass = new \ReflectionClass($values);
$relProperty = $relReflectionClass->getProperty($internalName);
@ -302,16 +296,13 @@ final class WriteMapper extends DataMapperAbstract
$mapper::create(db: $this->db)->execute($values);
continue;
}
if (!\is_array($values)) {
// @todo conditionals???
} elseif (!\is_array($values) || empty($values)) {
// @todo conditionals?
continue;
}
$objsIds = [];
$isRelPrivate = $mapper::COLUMNS[$this->mapper::HAS_MANY[$propertyName]['self']]['private'] ?? false;
$relReflectionClass = $isRelPrivate && !empty($values) ? new \ReflectionClass(\reset($values)) : null;
$relReflectionClass = $isRelPrivate ? new \ReflectionClass(\reset($values)) : null;
foreach ($values as $key => $value) {
if (!\is_object($value)) {
@ -337,7 +328,6 @@ final class WriteMapper extends DataMapperAbstract
$relProperty = $relReflectionClass->getProperty($internalName);
}
// @todo maybe consider to just set the column type to object, and then check for that (might be faster)
if (isset($mapper::BELONGS_TO[$internalName])
|| isset($mapper::OWNS_ONE[$internalName])
) {
@ -353,11 +343,12 @@ final class WriteMapper extends DataMapperAbstract
}
}
// @todo This inserts one element at a time. SQL allows to insert multiple rows.
// The problem with this is, that we then need to manually calculate the objIds
// since lastInsertId returns the first generated id.
// However, the current use case in Jingga only rarely has multiple hasMany during the creation
// since we are calling the API individually.
// @performance This inserts one element at a time. SQL allows to insert multiple rows.
// The problem with this is, that we then need to manually calculate the objIds
// since lastInsertId returns the first generated id.
// However, the current use case in Jingga only rarely has multiple hasMany during the creation
// since we are calling the API individually.
// https://github.com/Karaka-Management/phpOMS/issues/370
$objsIds[$key] = $mapper::create(db: $this->db)->execute($value);
}

View File

@ -841,10 +841,6 @@ class Builder extends BuilderAbstract
*/
public function count(string $table = '*') : self
{
/**
* @todo Don't do this as a string, create a new object $this->select(new Count($table)).
* The parser should be able to handle this much better
*/
return $this->select('COUNT(' . $table . ')');
}

View File

@ -459,9 +459,10 @@ class Grammar extends GrammarAbstract
$expression .= '(' . \rtrim($this->compileWhereQuery($element['column']), ';') . ')';
}
// @todo on doesn't allow string values as value (only table column names). This is bad and needs to be fixed!
// Solution could be to use ColumnName as internal object and then pass it to compileValue in all cases
// Other types such as int etc. are kind of possible
// @bug The on part of a join doesn't allow string values because they conflict with column name
// Other data types are possible because they don't conflict with the data type of columns (string)
// Consider to create a ColumnName() class.
// https://github.com/Karaka-Management/phpOMS/issues/369
if (isset($element['value'])) {
$expression .= ' ' . \strtoupper($element['operator']) . ' '
. (\is_string($element['value']) ? $this->compileSystem($element['value']) : $element['value']);

View File

@ -14,6 +14,7 @@ declare(strict_types=1);
namespace phpOMS\DataStorage\Database\Schema\Grammar;
use phpOMS\DataStorage\Database\BuilderAbstract;
use phpOMS\DataStorage\Database\Query\Builder;
use phpOMS\DataStorage\Database\Schema\Builder as SchemaBuilder;
@ -44,14 +45,7 @@ class SQLiteGrammar extends Grammar
public string $systemIdentifierEnd = '`';
/**
* Compile from.
*
* @param SchemaBuilder $query Builder
* @param array $table Tables
*
* @return string
*
* @since 1.0.0
* {@inheritdoc}
*/
protected function compileSelectTables(SchemaBuilder $query, array $table) : string
{
@ -64,14 +58,7 @@ class SQLiteGrammar extends Grammar
}
/**
* Compile from.
*
* @param SchemaBuilder $query Builder
* @param string $table Tables
*
* @return string
*
* @since 1.0.0
* {@inheritdoc}
*/
protected function compileSelectFields(SchemaBuilder $query, string $table) : string
{
@ -84,22 +71,55 @@ class SQLiteGrammar extends Grammar
}
/**
* Compile create table fields query.
* Parses the sql data types to the appropriate SQLite data types
*
* @param SchemaBuilder $query Query
* @param array $fields Fields to create
* @param string $type Data type
*
* @return string
*
* @since 1.0.0
*/
private function parseFieldType(string $type) : string
{
$type = \strtoupper($type);
if (\str_starts_with($type, 'INT')
|| \str_starts_with($type, 'TINYINT')
|| \str_starts_with($type, 'SMALLINT')
|| \str_starts_with($type, 'BIGINT')
) {
return 'INTEGER';
} elseif (\str_starts_with($type, 'VARCHAR')) {
return 'TEXT';
} elseif (\str_starts_with($type, 'DATETIME')) {
return 'TEXT';
} elseif (\str_starts_with($type, 'DECIMAL')) {
return 'REAL';
} elseif (\stripos($type, 'BINARY') !== false) {
return 'BLOB';
}
return $type;
}
/**
* {@inheritdoc}
*/
protected function compileCreateTable(BuilderAbstract $query, string $table) : string
{
return 'CREATE TABLE ' . $this->expressionizeTableColumn([$table]);
}
/**
* {@inheritdoc}
*/
protected function compileCreateFields(SchemaBuilder $query, array $fields) : string
{
$fieldQuery = '';
$keys = '';
foreach ($fields as $name => $field) {
$fieldQuery .= ' ' . $this->expressionizeTableColumn([$name]) . ' ' . $field['type'];
$fieldQuery .= ' ' . $this->expressionizeTableColumn([$name]) . ' ' . $this->parseFieldType($field['type']);
if (isset($field['default']) || ($field['default'] === null && ($field['null'] ?? false))) {
$fieldQuery .= ' DEFAULT ' . $this->compileValue($query, $field['default']);
@ -109,16 +129,16 @@ class SQLiteGrammar extends Grammar
$fieldQuery .= ' ' . ($field['null'] ? '' : 'NOT ') . 'NULL';
}
if ($field['primary'] ?? false) {
$keys .= ' PRIMARY KEY';
}
if ($field['autoincrement'] ?? false) {
$fieldQuery .= ' AUTO_INCREMENT';
$fieldQuery .= ' AUTOINCREMENT';
}
$fieldQuery .= ',';
if ($field['primary'] ?? false) {
$keys .= ' PRIMARY KEY (' . $this->expressionizeTableColumn([$name]) . '),';
}
if ($field['unique'] ?? false) {
$keys .= ' UNIQUE KEY (' . $this->expressionizeTableColumn([$name]) . '),';
}

View File

@ -40,11 +40,12 @@ class BaseStringL11n implements \JsonSerializable
*/
public string $name = '';
// @todo Karaka/phpOMS#357 this feels like $name and $type accomplish the same thing
// maybe we can always use $type and remove $name.
// This would require some smart mapper adjustment where the name is part of the l11n model,
// maybe use the path definition in the mapper which is used by arrays (e.g. type/name)
// More maybe: $name might have been intended as internal value? -> Makes no sense because only string
// @question Check if $name and $type accomplish the same thing
// Maybe we can always use $type and remove $name.
// This would require some smart mapper adjustment where the name is part of the l11n model,
// Maybe use the path definition in the mapper which is used by arrays (e.g. type/name)
// More maybe: $name might have been intended as internal value? -> Makes no sense because only string
// https://github.com/Karaka-Management/phpOMS/issues/357
public ?BaseStringL11nType $type = null;
/**

View File

@ -69,9 +69,7 @@ abstract class NgramParser
*/
private function tokenize(string $str) : array
{
if ($this->tokenizer === null) {
$this->tokenizer = new WhitespaceTokenizer();
}
$this->tokenizer ??= new WhitespaceTokenizer();
return $this->tokenizer->tokenize($str);
}

View File

@ -69,7 +69,6 @@ final class GrahamScan
/** @var array<int, array{x:int|float, y:int|float}> $subpoints */
$subpoints = \array_slice($points, 2, $n);
\usort($subpoints, function (array $a, array $b) use ($c) : int {
// @todo Might be wrong order of comparison
return \atan2($a['y'] - $c['y'], $a['x'] - $c['x']) <=> \atan2($b['y'] - $c['y'], $b['x'] - $c['x']);
});

View File

@ -124,7 +124,7 @@ final class Vector extends Matrix
}
/**
* Calculate the eucledian dot product
* Calculate the euclidean dot product
*
* @param self $vector Vector
*
@ -200,4 +200,25 @@ final class Vector extends Matrix
return self::fromArray($crossArray);
}
/*
public function cross(self $vector) : float
{
$mat = [];
for ($i = 0; $i < $this->n; ++$i) {
for ($j = 0; $j < $this->n; ++$j) {
$mat[$i][$j] = ($i === 0)
? $this->matrix[$j][0]
: (($i === 1)
? $vector->matrix[$j][0]
: 0
);
}
}
$matrix = Matrix::fromArray($mat);
return $matrix->det();
}
*/
}

View File

@ -370,8 +370,11 @@ final class Simplex
$this->b = $b;
$this->c = $c;
// @todo createSlackForm() required?
// @todo create minimize
// @question Consider to generate slack form. It this required?
// https://github.com/Karaka-Management/phpOMS/issues/349
// @feature Handle minimize and maximize in Simplex
// https://github.com/Karaka-Management/phpOMS/issues/368
$this->m = \count($A);
if ($this->m < 1) {

View File

@ -61,7 +61,7 @@ final class Illinois
public static function root(callable $func, float $a, float $b, int $maxIterations = 100) : float
{
if ($func($a) * $func($b) >= 0) {
throw new \Exception("Function values at endpoints must have opposite signs.");
throw new \Exception('Function values at endpoints must have opposite signs.');
}
$c = $b;
@ -76,9 +76,7 @@ final class Illinois
return $c;
}
// @todo c might be wrong, could be that if and else must be switched
// @see https://en.wikipedia.org/wiki/Regula_falsi#The_Illinois_algorithm
if ($y * $fa < 0) {
if ($fa < 0) {
$c = $sign === (int) ($y >= 0)
? (0.5 * $a * $fb - $b * $fa) / (0.5 * $fb - $fa)
: ($a * $fb - $b * $fa) / ($fb - $fa);

View File

@ -220,9 +220,7 @@ final class HttpRequest extends RequestAbstract
// @codeCoverageIgnoreStart
// Tested but coverage doesn't show up
if (\str_starts_with($lineRaw, '--')) {
if ($boundary === null) {
$boundary = \rtrim($lineRaw);
}
$boundary ??= \rtrim($lineRaw);
continue;
}

View File

@ -78,8 +78,6 @@ final class Rest
break;
}
// @todo how to implement GET request with $request->data (should it alter the uri or still get put into the body?)
// handle none-get
if ($request->getMethod() !== RequestMethod::GET && !empty($request->data)) {
// handle different content types
@ -92,11 +90,12 @@ final class Rest
} elseif (\in_array(MimeType::M_MULT, $contentType)) {
$boundary = '----' . \uniqid();
/* @phpstan-ignore-next-line */
$data = self::createMultipartData($boundary, $request->data);
// @todo Replace boundary/ with the correct boundary= in the future.
// Currently this cannot be done due to a bug. If we do it now the server cannot correctly populate php://input
// Currently this cannot be done due to a bug.
// If we do it now the server cannot correctly populate php://input
// https://github.com/Karaka-Management/phpOMS/issues/345
$headers['Content-Type'] = 'Content-Type: multipart/form-data; boundary/' . $boundary;
$headers['content-length'] = 'Content-Length: ' . \strlen($data);

View File

@ -118,6 +118,14 @@ class Email implements MessageInterface
*/
public string $mailer = SubmitType::MAIL;
/**
* Template strings
*
* @var array<string, string>
* @since 1.0.0
*/
public array $template = [];
/**
* Mail from.
*
@ -615,6 +623,20 @@ class Email implements MessageInterface
return $addresses;
}
public function parseTemplate() : void
{
if (empty($this->template)) {
return;
}
$keys = \array_keys($this->template);
$values = \array_values($this->template);
$this->subject = \str_replace($keys, $values, $this->subject);
$this->body = \str_replace($keys, $values, $this->body);
$this->bodyAlt = \str_replace($keys, $values, $this->bodyAlt);
}
/**
* Pre-send preparations
*
@ -626,12 +648,20 @@ class Email implements MessageInterface
*/
public function preSend(string $mailer) : bool
{
if (empty($this->from)
|| (empty($this->to) && empty($this->cc) && empty($this->bcc))
) {
return false;
}
$this->header = '';
$this->mailer = $mailer;
if (empty($this->to) && empty($this->cc) && empty($this->bcc)) {
return false;
}
$tempSubject = $this->subject;
$tempBody = $this->body;
$tempBodyAlt = $this->bodyAlt;
$this->parseTemplate();
if (!empty($this->bodyAlt)) {
$this->contentType = MimeType::M_ALT;
@ -642,9 +672,9 @@ class Email implements MessageInterface
$this->headerMime = '';
$this->bodyMime = $this->createBody();
$tempheaders = $this->headerMime;
$tempHeaders = $this->headerMime;
$this->headerMime = $this->createHeader();
$this->headerMime .= $tempheaders;
$this->headerMime .= $tempHeaders;
if ($this->mailer === SubmitType::MAIL) {
$this->header .= empty($this->to)
@ -674,6 +704,10 @@ class Email implements MessageInterface
self::normalizeBreaks($headerDkim, self::$LE) . self::$LE;
}
$this->subject = $tempSubject;
$this->body = $tempBody;
$this->bodyAlt = $tempBodyAlt;
return true;
}

View File

@ -480,17 +480,13 @@ class MailHandler
*/
public function smtpConnect(?array $options = null) : bool
{
if ($this->smtp === null) {
$this->smtp = new Smtp();
}
$this->smtp ??= new Smtp();
if ($this->smtp->isConnected()) {
return true;
}
if ($options === null) {
$options = $this->smtpOptions;
}
$options ??= $this->smtpOptions;
$this->smtp->timeout = $this->timeout;
$this->smtp->doVerp = $this->useVerp;

View File

@ -163,9 +163,7 @@ class Smtp
protected function getSMTPConnection(string $host, int $port = 25, int $timeout = 30, array $options = []) : mixed
{
static $streamok;
if ($streamok === null) {
$streamok = \function_exists('stream_socket_client');
}
$streamok ??= \function_exists('stream_socket_client');
$errno = 0;
$errstr = '';

View File

@ -275,9 +275,7 @@ abstract class RequestAbstract implements MessageInterface
}
$json = \json_decode($this->data[$key], true); /** @phpstan-ignore-line */
if ($json === null) {
$json = $this->data[$key];
}
$json ??= $this->data[$key];
return \is_array($json) ? $json : [$json];
}

View File

@ -309,6 +309,25 @@ abstract class ResponseAbstract implements \JsonSerializable, MessageInterface
$this->data[$key] = $response;
}
/**
* Add response.
*
* @param mixed $key Response id
* @param mixed $response Response to add
*
* @return void
*
* @since 1.0.0
*/
public function add(mixed $key, mixed $response) : void
{
if (!isset($this->data[$key])) {
$this->data[$key] = [];
}
$this->data[$key][] = $response;
}
/**
* {@inheritdoc}
*/

View File

@ -901,11 +901,8 @@ abstract class ModuleAbstract
*
* @return void
*
* @feature Implement softDelete functionality.
* Models which have a soft delete cannot be used, read or modified unless a person has soft delete permissions
* In addition to DELETE permissions we now need SOFTDELETE as well.
* There also needs to be an undo function for this soft delete
* In a backend environment a soft delete would be very helpful!!!
* @question Consider to implement softDelete functionality
* https://github.com/Karaka-Management/Karaka/issues/276
*
* @since 1.0.0
*/
@ -975,7 +972,7 @@ abstract class ModuleAbstract
*
* @param int $account Account id
* @param mixed $rel1 Object relation1
* @param mixed $rel2 Object relation2
* @param mixed $rel2 Object relation2 (null = remove all related to $rel1)
* @param string $mapper Object mapper
* @param string $field Relation field
* @param string $trigger Trigger for the event manager
@ -990,7 +987,13 @@ abstract class ModuleAbstract
$trigger = static::NAME . '-' . $trigger . '-relation-delete';
$this->app->eventManager->triggerSimilar('PRE:Module:' . $trigger, '', $rel1);
$mapper::remover()->deleteRelationTable($field, \is_array($rel2) ? $rel2 : [$rel2], $rel1);
$mapper::remover()->deleteRelationTable(
$field,
$rel2 === null
? null
: (\is_array($rel2) ? $rel2 : [$rel2]),
$rel1
);
$data = [
$account,

View File

@ -657,10 +657,11 @@ final class ModuleManager
* @return object|\phpOMS\Module\ModuleAbstract
*
* @todo Remove docblock type hint hack "object".
* The return type object is only used to stop the annoying warning that a method doesn't exist
* if you chain call the methods part of the returned ModuleAbstract implementation.
* Remove it once alternative inline type hinting is possible for the specific returned implementation.
* This also causes phpstan type inspection errors, which we have to live with or ignore in the settings
* The return type object is only used to stop the annoying warning that a method doesn't exist
* if you chain call the methods part of the returned ModuleAbstract implementation.
* Remove it once alternative inline type hinting is possible for the specific returned implementation.
* This also causes phpstan type inspection errors, which we have to live with or ignore in the settings
* https://github.com/Karaka-Management/phpOMS/issues/300
*
* @since 1.0.0
*/

View File

@ -186,9 +186,7 @@ class FloatInt implements SerializableInterface
throw new \Exception(); // @codeCoverageIgnore
}
if ($decimals === null) {
$decimals = \strlen(\rtrim($right, '0'));
}
$decimals ??= \strlen(\rtrim($right, '0'));
return $decimals > 0
? \number_format((float) $left, 0, $this->decimal, $this->thousands) . $this->decimal . \substr($right, 0, $decimals)
@ -224,9 +222,7 @@ class FloatInt implements SerializableInterface
throw new \Exception(); // @codeCoverageIgnore
}
if ($decimals === null) {
$decimals = \strlen(\rtrim($right, '0'));
}
$decimals ??= \strlen(\rtrim($right, '0'));
return $decimals > 0
? \number_format((float) $left, 0, $this->decimal, '') . $this->decimal . \substr($right, 0, $decimals)

View File

@ -365,13 +365,13 @@ class SmartDateTime extends \DateTime
*
* @param int $month Start of the year (i.e. fiscal year)
*
* @return \DateTime
* @return SmartDateTime
*
* @since 1.0.0
*/
public static function startOfYear(int $month = 1) : \DateTime
public static function startOfYear(int $month = 1) : SmartDateTime
{
return new \DateTime(\date('Y') . '-' . \sprintf('%02d', $month) . '-01');
return new SmartDateTime(\date('Y') . '-' . \sprintf('%02d', $month) . '-01');
}
/**
@ -379,37 +379,37 @@ class SmartDateTime extends \DateTime
*
* @param int $month Start of the year (i.e. fiscal year)
*
* @return \DateTime
* @return SmartDateTime
*
* @since 1.0.0
*/
public static function endOfYear(int $month = 1) : \DateTime
public static function endOfYear(int $month = 1) : SmartDateTime
{
return new \DateTime(\date('Y') . '-' . self::calculateMonthIndex(13 - $month, $month) . '-31');
return new SmartDateTime(\date('Y') . '-' . self::calculateMonthIndex(13 - $month, $month) . '-31');
}
/**
* Get the start of the month
*
* @return \DateTime
* @return SmartDateTime
*
* @since 1.0.0
*/
public static function startOfMonth() : \DateTime
public static function startOfMonth() : SmartDateTime
{
return new \DateTime(\date('Y-m') . '-01');
return new SmartDateTime(\date('Y-m') . '-01');
}
/**
* Get the end of the month
*
* @return \DateTime
* @return SmartDateTime
*
* @since 1.0.0
*/
public static function endOfMonth() : \DateTime
public static function endOfMonth() : SmartDateTime
{
return new \DateTime(\date('Y-m-t'));
return new SmartDateTime(\date('Y-m-t'));
}
/**

View File

@ -404,8 +404,11 @@ final class HttpUri implements UriInterface
$this->queryString .= $toAdd;
// @todo handle existing string at the end of uri (e.g. #fragment)
$this->uri .= $toAdd;
if (empty($this->fragment)) {
$this->uri .= $toAdd;
} else {
$this->uri = \substr_replace($this->uri, $toAdd, \strrpos($this->uri, '#'), 0);
}
}
/**

View File

@ -81,6 +81,7 @@ abstract class TwoDAbstract extends CodeAbstract
$locationX = $this->margin;
// @todo Allow manual dimensions
// https://github.com/Karaka-Management/phpOMS/issues/346
for ($posX = 0; $posX < $width; ++$posX) {
$locationY = $this->margin;

View File

@ -692,13 +692,8 @@ class Repository
*/
public function getContributors(?\DateTime $start = null, ?\DateTime $end = null) : array
{
if ($start === null) {
$start = new \DateTime('1970-12-31');
}
if ($end === null) {
$end = new \DateTime('now');
}
$start ??= new \DateTime('1970-12-31');
$end ??= new \DateTime('now');
$lines = $this->run('shortlog -s -n --since="' . $start->format('Y-m-d') . '" --before="' . $end->format('Y-m-d') . '" --all');
$contributors = [];
@ -732,13 +727,8 @@ class Repository
*/
public function getCommitsCount(?\DateTime $start = null, ?\DateTime $end = null) : array
{
if ($start === null) {
$start = new \DateTime('1970-12-31');
}
if ($end === null) {
$end = new \DateTime('now');
}
$start ??= new \DateTime('1970-12-31');
$end ??= new \DateTime('now');
$lines = $this->run('shortlog -s -n --since="' . $start->format('Y-m-d') . '" --before="' . $end->format('Y-m-d') . '" --all');
$commits = [];
@ -768,13 +758,8 @@ class Repository
*/
public function getAdditionsRemovalsByContributor(Author $author, ?\DateTime $start = null, ?\DateTime $end = null) : array
{
if ($start === null) {
$start = new \DateTime('1900-01-01');
}
if ($end === null) {
$end = new \DateTime('now');
}
$start ??= new \DateTime('1900-01-01');
$end ??= new \DateTime('now');
$addremove = ['added' => 0, 'removed' => 0];
$lines = $this->run(
@ -819,13 +804,8 @@ class Repository
*/
public function getCommitsBy(?\DateTime $start = null, ?\DateTime $end = null, ?Author $author = null) : array
{
if ($start === null) {
$start = new \DateTime('1970-12-31');
}
if ($end === null) {
$end = new \DateTime('now');
}
$start ??= new \DateTime('1970-12-31');
$end ??= new \DateTime('now');
$author = $author === null ? '' : ' --author=' . \escapeshellarg($author->name) . '';

View File

@ -33,7 +33,7 @@ use phpOMS\Uri\UriFactory;
* @see https://github.com/doowzs/parsedown-extreme
* @since 1.0.0
*
* @todo Add
* @todo Add special markdown content
* 1. Calendar (own widget)
* 2. Event (own widget)
* 3. Tasks (own widget)
@ -49,6 +49,7 @@ use phpOMS\Uri\UriFactory;
* 13. Checklist (own widget)
* 14. Gallery
* 15. Form (own widget)
* https://github.com/Karaka-Management/phpOMS/issues/290
*/
class Markdown
{
@ -2560,39 +2561,18 @@ class Markdown
return null;
}
// @todo We are parsing the language here and further down. Shouldn't one time be enough?
// Both variations seem to result in the same result?!
$language = \trim(\preg_replace('/^`{3}([^\s]+)(.+)?/s', '$1', $line['text']));
// Handle diagrams
if (!($this->options['diagrams'] ?? true)
|| !\in_array($language, ['mermaid', 'chart'])
) {
$infostring = \trim(\substr($line['text'], $openerLength), "\t ");
if (\strpos($infostring, '`') !== false) {
return null;
}
// Is code block
$element = [
'name' => 'code',
'text' => '',
];
if ($infostring !== '') {
/**
* https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
* Every HTML element may have a class attribute specified.
* The attribute, if specified, must have a value that is a set
* of space-separated tokens representing the various classes
* that the element belongs to.
* [...]
* The space characters, for the purposes of this specification,
* are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
* U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
* U+000D CARRIAGE RETURN (CR).
*/
$language = \substr($infostring, 0, \strcspn($infostring, " \t\n\f\r"));
if ($language !== '```' && !empty($language)) {
$element['attributes'] = ['class' => "language-{$language}"];
}
@ -2604,26 +2584,7 @@ class Markdown
'element' => $element,
],
];
}
if (\strtolower($language) === 'mermaid') {
// Mermaid.js https://mermaidjs.github.io
$element = [
'text' => '',
];
return [
'char' => $marker,
'openerLength' => $openerLength,
'element' => [
'element' => $element,
'name' => 'div',
'attributes' => [
'class' => 'mermaid',
],
],
];
} elseif (\strtolower($language) === 'chart') {
} elseif (\strtolower($language) === 'chartjs') {
// Chart.js https://www.chartjs.org/
$element = [
'text' => '',
@ -2640,8 +2601,27 @@ class Markdown
],
],
];
} elseif (\in_array(\strtolower($language), ['mermaid', 'tuichart'])) {
// Mermaid.js https://mermaidjs.github.io
// TUI.chart https://github.com/nhn/tui.chart
$element = [
'text' => '',
];
return [
'char' => $marker,
'openerLength' => $openerLength,
'element' => [
'element' => $element,
'name' => 'div',
'attributes' => [
'class' => \strtolower($language),
],
],
];
}
return null;
}
@ -2677,6 +2657,8 @@ class Markdown
return null;
}
// @performance Optimize away the child <span> element for spoilers (if reasonable)
// https://github.com/Karaka-Management/phpOMS/issues/367
return [
'char' => $marker,
'openerLength' => $openerLength,
@ -2690,7 +2672,7 @@ class Markdown
'text' => $summary,
],
[
'name' => 'span', // @todo check if without span possible
'name' => 'span',
'text' => '',
],
],

View File

@ -65,9 +65,7 @@ final class File
*/
public static function generateExtension(?array $source = null) : string
{
if ($source === null) {
$source = self::$extensions;
}
$source ??= self::$extensions;
$key = \array_rand($source, 1);

View File

@ -56,9 +56,7 @@ final class Phone
$numberString = $struct;
if ($isInt) {
if ($countries === null) {
$countries = ['de' => 49, 'us' => 1];
}
$countries ??= ['de' => 49, 'us' => 1];
$numberString = \str_replace(
'$1',

View File

@ -103,9 +103,7 @@ final class Text
return '';
}
if ($words === null) {
$words = self::LOREM_IPSUM;
}
$words ??= self::LOREM_IPSUM;
$punctuation = $this->generatePunctuation($length);
$punctuationCount = \array_count_values(

View File

@ -39,10 +39,6 @@ class Schedule extends TaskAbstract
*/
public static function createWith(array $jobData) : TaskAbstract
{
/**
* @todo Karaka/phpOMS#231
* Use the interval for generating a schedule
*/
$job = new self($jobData[1], $jobData[8], $jobData[7]);
$job->status = (int) $jobData[3];

View File

@ -170,7 +170,6 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase
$iS = $con->getGrammar()->systemIdentifierStart;
$iE = $con->getGrammar()->systemIdentifierEnd;
// @todo fix, this is not correct for sqlite
$query = new Builder($con);
$sql = '';
@ -181,7 +180,7 @@ final class BuilderTest extends \PHPUnit\Framework\TestCase
} elseif ($con instanceof SqlServerConnection) {
$sql = 'CREATE TABLE IF NOT EXISTS [user_roles] ([user_id] INT AUTO_INCREMENT, [role_id] VARCHAR(10) DEFAULT \'1\' NULL, PRIMARY KEY ([user_id]), FOREIGN KEY ([user_id]) REFERENCES [users] ([ext1_id]), FOREIGN KEY ([role_id]) REFERENCES [roles] ([ext2_id]));';
} elseif ($con instanceof SQLiteConnection) {
$sql = 'CREATE TABLE IF NOT EXISTS [user_roles] ([user_id] INT AUTO_INCREMENT, [role_id] VARCHAR(10) DEFAULT \'1\' NULL, PRIMARY KEY ([user_id]), FOREIGN KEY ([user_id]) REFERENCES [users] ([ext1_id]), FOREIGN KEY ([role_id]) REFERENCES [roles] ([ext2_id]));';
$sql = 'CREATE TABLE [user_roles] ([user_id] INTEGER PRIMARY KEY AUTOINCREMENT, [role_id] TEXT DEFAULT \'1\' NULL, PRIMARY KEY ([user_id]), FOREIGN KEY ([user_id]) REFERENCES [users] ([ext1_id]), FOREIGN KEY ([role_id]) REFERENCES [roles] ([ext2_id]));';
}
$sql = \strtr($sql, '[]', $iS . $iE);

View File

@ -143,7 +143,8 @@ final class NodeTest extends \PHPUnit\Framework\TestCase
* @covers phpOMS\Stdlib\Graph\Node
* @group framework
*
* @todo is there bug where directed graphs return invalid neighbors?
* @bug Directed graphs may return invalid neighbors
* https://github.com/Karaka-Management/phpOMS/issues/366
*/
public function testNeighborsInputOutput() : void
{

View File

@ -39,8 +39,6 @@ final class ScheduleTest extends \PHPUnit\Framework\TestCase
* @testdox A task can be created from an array and rendered
* @covers phpOMS\Utils\TaskSchedule\Schedule
*
* @todo the interval has to be implemented!
*
* @group framework
*/
public function testCreateJobWithData() : void