This commit is contained in:
Dennis Eichhorn 2023-09-10 18:58:35 +00:00
parent 4469789af4
commit 018b9ce981
23 changed files with 388 additions and 133 deletions

View File

@ -166,8 +166,8 @@ final class DBSCAN
/** /**
* Find neighbors of a point * Find neighbors of a point
* *
* @param PointInterface $point Base point for potential neighbors * @param PointInterface $point Base point for potential neighbors
* @param float $epsion Max distance to neighbor * @param float $epsilon Max distance to neighbor
* *
* @return array * @return array
* *
@ -210,6 +210,7 @@ final class DBSCAN
} }
} }
/** @var float[] $distances */
return $distances; return $distances;
} }
@ -228,10 +229,7 @@ final class DBSCAN
foreach ($this->clusters as $c => $cluster) { foreach ($this->clusters as $c => $cluster) {
$points = []; $points = [];
foreach ($cluster as $p) { foreach ($cluster as $p) {
$points[] = [ $points[] = $p->coordinates;
'x' => \reset($p->coordinates),
'y' => \end($p->coordinates),
];
} }
$this->convexHulls[$c] = MonotoneChain::createConvexHull($points); $this->convexHulls[$c] = MonotoneChain::createConvexHull($points);
@ -239,14 +237,7 @@ final class DBSCAN
} }
foreach ($this->convexHulls as $c => $hull) { foreach ($this->convexHulls as $c => $hull) {
if (Polygon::isPointInPolygon( if (Polygon::isPointInPolygon($point->coordinates, $hull) <= 0) {
[
'x' => \reset($point->coordinates),
'y' => \end($point->coordinates),
],
$hull
) <= 0
) {
return $c; return $c;
} }
} }

View File

@ -239,8 +239,8 @@ final class MeanShift
/** /**
* Find the closest cluster/group of a point * Find the closest cluster/group of a point
* *
* @param PointInterface $point Point to find the cluster for * @param PointInterface $point Point to find the cluster for
* @param PointInterface[] $group Clusters * @param array<PointInterface[]> $groups Clusters
* *
* @return int * @return int
* *

View File

@ -18,8 +18,9 @@ namespace phpOMS\Algorithm\Clustering;
/** /**
* Point interface. * Point interface.
* *
* @property int $group Group * @property int $group Group
* @property string $name Name * @property string $name Name
* @property array $coordinates Coordinates
* *
* @package phpOMS\Algorithm\Clustering; * @package phpOMS\Algorithm\Clustering;
* @license OMS License 2.0 * @license OMS License 2.0

View File

@ -72,7 +72,7 @@ final class Apriori
* *
* @param array<array> $sets Sets of a set (e.g. [[1,2,3,4], [1,2], [1]]) * @param array<array> $sets Sets of a set (e.g. [[1,2,3,4], [1,2], [1]])
* *
* @return array<array> * @return array
* *
* @since 1.0.0 * @since 1.0.0
*/ */

View File

@ -0,0 +1,29 @@
<?php
/**
* Jingga
*
* PHP Version 8.1
*
* @package phpOMS\Algorithm\Graph
* @copyright Dennis Eichhorn
* @license OMS License 2.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace phpOMS\Algorithm\Graph;
/**
* Markov chain
*
* @package phpOMS\Algorithm\Graph
* @license OMS License 2.0
* @link https://jingga.app
* @since 1.0.0
*
* @todo Implement
*/
final class MarkovChain
{
}

View File

@ -143,8 +143,8 @@ class GeneticOptimization
$fitnesses = []; $fitnesses = [];
foreach ($population as $parameters) { foreach ($population as $key => $parameters) {
$fitnesses[$population] = ($fitness)($parameters); $fitnesses[$key] = ($fitness)($parameters);
} }
\asort($fitnesses); \asort($fitnesses);

View File

@ -81,7 +81,7 @@ class TabuSearch
for ($i = 0; $i < $iterations; ++$i) { for ($i = 0; $i < $iterations; ++$i) {
$neighbors = []; $neighbors = [];
for ($i = 0; $i < $tabuListSize; ++$i) { for ($j = 0; $j < $tabuListSize; ++$j) {
$neighbor = ($neighbor)($currentSolution); $neighbor = ($neighbor)($currentSolution);
$neighbors[] = $neighbor; $neighbors[] = $neighbor;
} }

View File

@ -20,8 +20,8 @@ namespace phpOMS\Algorithm\Rating;
* @package phpOMS\Algorithm\Rating * @package phpOMS\Algorithm\Rating
* @license OMS License 2.0 * @license OMS License 2.0
* @link https://jingga.app * @link https://jingga.app
* @since 1.0.0
* @see https://en.wikipedia.org/wiki/Bradley%E2%80%93Terry_model * @see https://en.wikipedia.org/wiki/Bradley%E2%80%93Terry_model
* @since 1.0.0
*/ */
final class BradleyTerry final class BradleyTerry
{ {

View File

@ -61,7 +61,7 @@ final class Glicko1
* *
* @see calculateC(); * @see calculateC();
* *
* @var int * @var float
* @since 1.0.0 * @since 1.0.0
*/ */
public float $DEFAULT_C = 34.6; public float $DEFAULT_C = 34.6;
@ -127,7 +127,6 @@ final class Glicko1
) : array ) : array
{ {
// Step 1: // Step 1:
$s = [];
$E = []; $E = [];
$gRD = []; $gRD = [];
@ -145,8 +144,8 @@ final class Glicko1
// Step 2: // Step 2:
foreach ($oElo as $id => $e) { foreach ($oElo as $id => $e) {
$gRD_t = 1 / (\sqrt(1 + 3 * self::Q * self::Q * $oRd[$id] * $oRd[$id] / (\M_PI * \M_PI))); $gRD_t = 1 / (\sqrt(1 + 3 * self::Q * self::Q * $oRd[$id] * $oRd[$id] / (\M_PI * \M_PI)));
$gRD[] = $gRD_t; $gRD[$id] = $gRD_t;
$E[] = 1 / (1 + \pow(10, $gRD_t * ($elo - $e) / -400)); $E[$id] = 1 / (1 + \pow(10, $gRD_t * ($elo - $e) / -400));
} }
$d = 0; $d = 0;

View File

@ -123,13 +123,13 @@ final class Glicko2
// Step 1: // Step 1:
$g = []; $g = [];
foreach ($oRd as $rd) { foreach ($oRd as $idx => $rd) {
$g[] = 1 / \sqrt(1 + 3 * $rd * $rd / (\M_PI * \M_PI)); $g[$idx] = 1 / \sqrt(1 + 3 * $rd * $rd / (\M_PI * \M_PI));
} }
$E = []; $E = [];
foreach ($oElo as $idx => $elo) { foreach ($oElo as $idx => $elo) {
$E[] = 1 / (1 + \exp(-$g[$idx] * ($elo - $elo))); $E[$idx] = 1 / (1 + \exp(-$g[$idx] * ($elo - $elo)));
} }
$v = 0; $v = 0;

View File

@ -51,7 +51,7 @@ final class CreditSafe implements CreditRatingInterface
$response = Rest::request($request); $response = Rest::request($request);
return $response->header->status === 200 return $response->header->status === 200
? ($response->get('token') ?? '') ? ($response->getDataString('token') ?? '')
: ''; : '';
} }
@ -212,7 +212,7 @@ final class CreditSafe implements CreditRatingInterface
$response = Rest::request($request); $response = Rest::request($request);
return $response->get('orderID') ?? ''; return $response->getDataString('orderID') ?? '';
} }
/** /**

View File

@ -20,8 +20,8 @@ namespace phpOMS\Business\Recommendation;
* @package phpOMS\Business\Recommendation * @package phpOMS\Business\Recommendation
* @license OMS License 2.0 * @license OMS License 2.0
* @link https://jingga.app * @link https://jingga.app
* @since 1.0.0
* @see https://arxiv.org/ftp/arxiv/papers/1205/1205.2618.pdf * @see https://arxiv.org/ftp/arxiv/papers/1205/1205.2618.pdf
* @since 1.0.0
* *
* @todo Implement, current implementation probably wrong * @todo Implement, current implementation probably wrong
*/ */
@ -40,13 +40,15 @@ final class BayesianPersonalizedRanking
// num_factors determines the dimensionality of the latent factor space. // num_factors determines the dimensionality of the latent factor space.
// learning_rate controls the step size for updating the latent factors during optimization. // learning_rate controls the step size for updating the latent factors during optimization.
// regularization prevents overfitting by adding a penalty for large parameter values. // regularization prevents overfitting by adding a penalty for large parameter values.
public function __construct(int $numFactors, float $learningRate, float $regularization) { public function __construct(int $numFactors, float $learningRate, float $regularization)
{
$this->numFactors = $numFactors; $this->numFactors = $numFactors;
$this->learningRate = $learningRate; $this->learningRate = $learningRate;
$this->regularization = $regularization; $this->regularization = $regularization;
} }
private function generateRandomFactors() { private function generateRandomFactors()
{
$factors = []; $factors = [];
for ($i = 0; $i < $this->numFactors; ++$i) { for ($i = 0; $i < $this->numFactors; ++$i) {
$factors[$i] = \mt_rand() / \mt_getrandmax(); $factors[$i] = \mt_rand() / \mt_getrandmax();
@ -67,7 +69,8 @@ final class BayesianPersonalizedRanking
return $score; return $score;
} }
public function updateFactors($userId, $posItemId, $negItemId) : void { public function updateFactors($userId, $posItemId, $negItemId) : void
{
if (!isset($this->userFactors[$userId])) { if (!isset($this->userFactors[$userId])) {
$this->userFactors[$userId] = $this->generateRandomFactors(); $this->userFactors[$userId] = $this->generateRandomFactors();
} }

View File

@ -35,8 +35,8 @@ final class JWT
/** /**
* Create JWT signature part * Create JWT signature part
* *
* @param string $secret Secret (at least 256 bit) * @param string $secret Secret (at least 256 bit)
* @param array{alg:string, typ:string} $header Header * @param array{alg:string, typ:string} $header Header
* @param array{sub:string, ?uid:string, ?name:string, iat:string} $payload Payload * @param array{sub:string, ?uid:string, ?name:string, iat:string} $payload Payload
* *
* @return string hmac(Header64 . Payload64, secret) * @return string hmac(Header64 . Payload64, secret)
@ -60,15 +60,15 @@ final class JWT
/** /**
* Create JWT token * Create JWT token
* *
* @param string $secret Secret (at least 256 bit) * @param string $secret Secret (at least 256 bit)
* @param array{alg:string, typ:string} $header Header * @param array{alg:string, typ:string} $header Header
* @param array{sub:string, ?uid:string, ?name:string, iat:string} $payload Payload * @param array{sub:string, ?uid:string, ?name:string, iat:string} $payload Payload
* *
* @return string Header64 . Payload64 . hmac(Header64 . Payload64, secret) * @return string Header64 . Payload64 . hmac(Header64 . Payload64, secret)
* *
* @since 1.0.0 * @since 1.0.0
*/ */
public static function createJWT(string $secret, array $header = [], array $payload = []) : string public static function createJWT(string $secret, array $header, array $payload) : string
{ {
$header64 = Base64Url::encode(\json_encode($header)); $header64 = Base64Url::encode(\json_encode($header));
$payload64 = Base64Url::encode(\json_encode($payload)); $payload64 = Base64Url::encode(\json_encode($payload));
@ -94,11 +94,7 @@ final class JWT
return []; return [];
} }
try { return \json_decode(Base64Url::decode($explode[0]), true);
return \json_decode(Base64Url::decode($explode[0]), true);
} catch (\Throwable $_) {
return [];
}
} }
/** /**
@ -118,11 +114,7 @@ final class JWT
return []; return [];
} }
try { return \json_decode(Base64Url::decode($explode[1]), true);
return \json_decode(Base64Url::decode($explode[1]), true);
} catch (\Throwable $_) {
return [];
}
} }
/** /**

View File

@ -171,11 +171,15 @@ final class EventManager implements \Countable
*/ */
public function triggerSimilar(string $group, string $id = '', mixed $data = null) : bool public function triggerSimilar(string $group, string $id = '', mixed $data = null) : bool
{ {
if (empty($this->callbacks)) {
return false;
}
$groupIsRegex = \stripos($group, '/') === 0; $groupIsRegex = \stripos($group, '/') === 0;
$idIsRegex = \stripos($id, '/') === 0; $idIsRegex = \stripos($id, '/') === 0;
$groups = []; $groups = [];
foreach ($this->groups as $groupName => $value) { foreach ($this->groups as $groupName => $_) {
$groupNameIsRegex = \stripos($groupName, '/') === 0; $groupNameIsRegex = \stripos($groupName, '/') === 0;
if ($groupIsRegex) { if ($groupIsRegex) {
@ -189,8 +193,8 @@ final class EventManager implements \Countable
} }
} }
foreach ($groups as $groupName => $groupValues) { foreach ($groups as $groupName => $_) {
foreach ($this->groups[$groupName] as $idName => $value) { foreach ($this->groups[$groupName] as $idName => $_2) {
$idNameIsRegex = \stripos($idName, '/') === 0; $idNameIsRegex = \stripos($idName, '/') === 0;
if ($idIsRegex) { if ($idIsRegex) {

View File

@ -35,6 +35,7 @@ trait ISO3166Trait
*/ */
public static function getBy2Code(string $code) : mixed public static function getBy2Code(string $code) : mixed
{ {
/** @var string $code3 */
$code3 = ISO3166TwoEnum::getName($code); $code3 = ISO3166TwoEnum::getName($code);
return self::getByName($code3); return self::getByName($code3);

View File

@ -99,7 +99,7 @@ final class Vector extends Matrix
*/ */
public function cosine(self $v) : float public function cosine(self $v) : float
{ {
$dotProduct = 0; $dotProduct = 0.0;
for ($i = 0; $i < $this->m; ++$i) { for ($i = 0; $i < $this->m; ++$i) {
$dotProduct += $this->matrix[$i][0] * $v[$i]; $dotProduct += $this->matrix[$i][0] * $v[$i];
} }
@ -110,7 +110,7 @@ final class Vector extends Matrix
} }
$magnitude1 = \sqrt($sumOfSquares); $magnitude1 = \sqrt($sumOfSquares);
$sumOfSquares = 0; $sumOfSquares = 0.0;
foreach ($v->matrix as $value) { foreach ($v->matrix as $value) {
$sumOfSquares += $value[0] * $value[0]; $sumOfSquares += $value[0] * $value[0];
} }

View File

@ -107,12 +107,12 @@ final class MetricsND
*/ */
public static function cosine(array $a, array $b) : float public static function cosine(array $a, array $b) : float
{ {
if (\count($a) !== \count($b)) { if (($length = \count($a)) !== \count($b)) {
throw new InvalidDimensionException(\count($a) . 'x' . \count($b)); throw new InvalidDimensionException(\count($a) . 'x' . \count($b));
} }
$dotProduct = 0; $dotProduct = 0;
for ($i = 0; $i < \count($a); ++$i) { for ($i = 0; $i < $length; ++$i) {
$dotProduct += $a[$i] * $b[$i]; $dotProduct += $a[$i] * $b[$i];
} }
@ -128,7 +128,7 @@ final class MetricsND
} }
$magnitude2 = \sqrt($sumOfSquares); $magnitude2 = \sqrt($sumOfSquares);
if ($magnitude1 === 0 || $magnitude2 === 0) { if ($magnitude1 == 0 || $magnitude2 == 0) {
return \PHP_FLOAT_MAX; return \PHP_FLOAT_MAX;
} }

View File

@ -45,15 +45,232 @@ abstract class ResponseAbstract implements \JsonSerializable, MessageInterface
/** /**
* Get response by ID. * Get response by ID.
* *
* @param mixed $id Response ID * @param mixed $key Response ID
* *
* @return mixed * @return mixed
* *
* @since 1.0.0 * @since 1.0.0
*/ */
public function get(mixed $id) : mixed public function get(mixed $key, string $type = null) : mixed
{ {
return $this->data[$id] ?? null; if ($key === null) {
return $this->data;
}
$key = \is_string($key) ? \mb_strtolower($key) : $key;
if (!isset($this->data[$key])) {
return null;
}
switch ($type) {
case null:
return $this->data[$key];
case 'int':
return (int) $this->data[$key];
case 'string':
return (string) $this->data[$key];
case 'float':
return (float) $this->data[$key];
case 'bool':
return (bool) $this->data[$key];
case 'DateTime':
return new \DateTime((string) $this->data[$key]);
default:
return $this->data[$key];
}
}
/**
* Get data.
*
* @param string $key Data key
*
* @return null|string
*
* @since 1.0.0
*/
public function getDataString(string $key) : ?string
{
$key = \mb_strtolower($key);
if (($this->data[$key] ?? '') === '') {
return null;
}
return (string) $this->data[$key];
}
/**
* Get data.
*
* @param string $key Data key
*
* @return null|int
*
* @since 1.0.0
*/
public function getDataInt(string $key) : ?int
{
$key = \mb_strtolower($key);
if (($this->data[$key] ?? '') === '') {
return null;
}
return (int) $this->data[$key];
}
/**
* Get data.
*
* @param string $key Data key
*
* @return null|float
*
* @since 1.0.0
*/
public function getDataFloat(string $key) : ?float
{
$key = \mb_strtolower($key);
if (($this->data[$key] ?? '') === '') {
return null;
}
return (float) $this->data[$key];
}
/**
* Get data.
*
* @param string $key Data key
*
* @return null|bool
*
* @since 1.0.0
*/
public function getDataBool(string $key) : ?bool
{
$key = \mb_strtolower($key);
if (($this->data[$key] ?? '') === '') {
return null;
}
return (bool) $this->data[$key];
}
/**
* Get data.
*
* @param string $key Data key
*
* @return null|\DateTime
*
* @since 1.0.0
*/
public function getDataDateTime(string $key) : ?\DateTime
{
$key = \mb_strtolower($key);
return empty($this->data[$key] ?? null)
? null
: new \DateTime((string) $this->data[$key]);
}
/**
* Get data.
*
* @param string $key Data key
*
* @return array
*
* @since 1.0.0
*/
public function getDataJson(string $key) : array
{
$key = \mb_strtolower($key);
if (($this->data[$key] ?? '') === '') {
return [];
}
$json = \json_decode($this->data[$key], true); /** @phpstan-ignore-line */
return \is_array($json) ? $json : [];
}
/**
* Get data.
*
* @param string $key Data key
* @param string $delim Data delimiter
*
* @return array
*
* @since 1.0.0
*/
public function getDataList(string $key, string $delim = ',') : array
{
$key = \mb_strtolower($key);
if (($this->data[$key] ?? '') === '') {
return [];
}
/* @phpstan-ignore-next-line */
$list = \explode($delim, $this->data[$key]);
if ($list === false) {
return []; // @codeCoverageIgnore
}
foreach ($list as $i => $e) {
$list[$i] = \trim($e);
}
return $list;
}
/**
* Get data based on wildcard.
*
* @param string $regex Regex data key
*
* @return array
*
* @since 1.0.0
*/
public function getLike(string $regex) : array
{
$data = [];
foreach ($this->data as $key => $value) {
if (\preg_match('/' . $regex . '/', (string) $key) === 1) {
$data[$key] = $value;
}
}
return $data;
}
/**
* Check if has data.
*
* The following empty values are considered as not set (null, '', 0)
*
* @param string $key Data key
*
* @return bool
*
* @since 1.0.0
*/
public function hasData(string $key) : bool
{
$key = \mb_strtolower($key);
return isset($this->data[$key])
&& $this->data[$key] !== ''
&& $this->data[$key] !== null;
} }
/** /**

View File

@ -697,6 +697,8 @@ abstract class ModuleAbstract
* *
* @return void * @return void
* *
* @todo find a way to offload this to the cli in a different process (same for other similar functions)
*
* @since 1.0.0 * @since 1.0.0
*/ */
protected function createModel(int $account, mixed $obj, string | \Closure $mapper, string $trigger, string $ip) : void protected function createModel(int $account, mixed $obj, string | \Closure $mapper, string $trigger, string $ip) : void
@ -712,17 +714,18 @@ abstract class ModuleAbstract
$mapper(); $mapper();
} }
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data = [
[ $account,
$account, null, $obj,
null, $obj, StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger,
StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger, static::NAME,
static::NAME, (string) $id,
(string) $id, null,
null, $ip,
$ip, ];
]
); $this->app->moduleManager->get('Auditor', 'Api')->eventLogCreate(...$data);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data);
} }
/** /**
@ -756,17 +759,18 @@ abstract class ModuleAbstract
$mapper(); $mapper();
} }
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data = [
[ $account,
$account, null, $obj,
null, $obj, StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger,
StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger, static::NAME,
static::NAME, (string) $id,
(string) $id, null,
null, $ip,
$ip, ];
]
); $this->app->moduleManager->get('Auditor', 'Api')->eventLogCreate(...$data);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data);
} }
} }
@ -801,17 +805,18 @@ abstract class ModuleAbstract
$mapper(); $mapper();
} }
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data = [
[ $account,
$account, $old, $new,
$old, $new, StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger,
StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger, static::NAME,
static::NAME, (string) $id,
(string) $id, null,
null, $ip,
$ip, ];
]
); $this->app->moduleManager->get('Auditor', 'Api')->eventLogUpdate(...$data);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data);
} }
/** /**
@ -844,17 +849,18 @@ abstract class ModuleAbstract
$mapper(); $mapper();
} }
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data = [
[ $account,
$account, $obj, null,
$obj, null, StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger,
StringUtils::intHash(\is_string($mapper) ? $mapper : \get_class($mapper)), $trigger, static::NAME,
static::NAME, (string) $id,
(string) $id, null,
null, $ip,
$ip, ];
]
); $this->app->moduleManager->get('Auditor', 'Api')->eventLogDelete(...$data);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data);
} }
/** /**
@ -890,17 +896,19 @@ abstract class ModuleAbstract
$this->app->eventManager->triggerSimilar('PRE:Module:' . $trigger, '', $rel1); $this->app->eventManager->triggerSimilar('PRE:Module:' . $trigger, '', $rel1);
$mapper::writer()->createRelationTable($field, \is_array($rel2) ? $rel2 : [$rel2], $rel1); $mapper::writer()->createRelationTable($field, \is_array($rel2) ? $rel2 : [$rel2], $rel1);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '',
[ $data = [
$account, $account,
'', [$rel1 => $rel2], '', [$rel1 => $rel2],
StringUtils::intHash($mapper), $trigger, StringUtils::intHash($mapper), $trigger,
static::NAME, static::NAME,
null, null,
null, null,
$ip, $ip,
] ];
);
$this->app->moduleManager->get('Auditor', 'Api')->eventLogRelationCreate(...$data);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data);
} }
/** /**
@ -928,16 +936,18 @@ abstract class ModuleAbstract
$this->app->eventManager->triggerSimilar('PRE:Module:' . $trigger, '', $rel1); $this->app->eventManager->triggerSimilar('PRE:Module:' . $trigger, '', $rel1);
$mapper::remover()->deleteRelationTable($field, \is_array($rel2) ? $rel2 : [$rel2], $rel1); $mapper::remover()->deleteRelationTable($field, \is_array($rel2) ? $rel2 : [$rel2], $rel1);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '',
[ $data = [
$account, $account,
[$rel1 => $rel2], '', [$rel1 => $rel2], '',
StringUtils::intHash($mapper), $trigger, StringUtils::intHash($mapper), $trigger,
static::NAME, static::NAME,
null, null,
null, null,
$ip, $ip,
] ];
);
$this->app->moduleManager->get('Auditor', 'Api')->eventLogRelationDelete(...$data);
$this->app->eventManager->triggerSimilar('POST:Module:' . $trigger, '', $data);
} }
} }

View File

@ -293,8 +293,8 @@ final class FileUtils
public static function makeSafeFileName(string $name) : string public static function makeSafeFileName(string $name) : string
{ {
$name = \preg_replace("/[^A-Za-z0-9\-_.]/", '_', $name); $name = \preg_replace("/[^A-Za-z0-9\-_.]/", '_', $name);
$name = \preg_replace("/_+/", '_', $name); $name = \preg_replace("/_+/", '_', $name ?? '');
$name = \trim($name, '_'); $name = \trim($name ?? '', '_');
$name = \strtolower($name); $name = \strtolower($name);
return $name; return $name;

View File

@ -319,7 +319,9 @@ final class ArrayUtils
return null; return null;
} }
return \trim($args[(int) $key + 1], '" '); $value = $args[(int) $key + 1];
return \is_string($value) ? \trim($value, '" ') : $value;
} }
/** /**

View File

@ -42,6 +42,8 @@ class HtmlFormatter
$dom->preserveWhiteSpace = false; $dom->preserveWhiteSpace = false;
$dom->formatOutput = true; $dom->formatOutput = true;
return $dom->saveXML($dom->documentElement); $formatted = $dom->saveXML($dom->documentElement);
return $formatted === false ? '' : $formatted;
} }
} }

View File

@ -312,13 +312,17 @@ final class ImageUtils
$color1Avg = self::getAverageColor($src1, $i, $j, $imageDim2[0], $imageDim2[1], $diffArea); $color1Avg = self::getAverageColor($src1, $i, $j, $imageDim2[0], $imageDim2[1], $diffArea);
$color2Avg = self::getAverageColor($src2, $i, $j, $newDim[0], $newDim[1], $diffArea); $color2Avg = self::getAverageColor($src2, $i, $j, $newDim[0], $newDim[1], $diffArea);
$color1 = \imagecolorat($src1, $i, $j); //$color1 = \imagecolorat($src1, $i, $j);
$color2 = \imagecolorat($src2, $i, $j); $color2 = \imagecolorat($src2, $i, $j);
if (\abs($color1Avg - $color2Avg) / $color1Avg > 0.05 && $color1Avg > 0 && $color2Avg > 0) { if (\abs($color1Avg - $color2Avg) / $color1Avg > 0.05 && $color1Avg > 0 && $color2Avg > 0) {
++$difference; ++$difference;
if ($diff === 0) { if ($diff === 0) {
if ($color2 === false) {
continue;
}
/** @var \GdImage $dst */ /** @var \GdImage $dst */
\imagesetpixel($dst, $i, $j, $color2); \imagesetpixel($dst, $i, $j, $color2);
} elseif ($diff === 1) { } elseif ($diff === 1) {