mirror of
https://github.com/Karaka-Management/phpOMS.git
synced 2026-01-16 20:08:41 +00:00
Went through todos
This commit is contained in:
parent
b2d001c0ae
commit
a7451d330d
|
|
@ -29,6 +29,10 @@ use phpOMS\Utils\ArrayUtils;
|
|||
*
|
||||
* @todo Lock data for concurrency (e.g. table row lock or heartbeat)
|
||||
* https://github.com/Karaka-Management/Karaka/issues/152
|
||||
*
|
||||
* @performance Database inserts happen one at a time.
|
||||
* Try to find a way to optimize this with multiple inserts in one go.
|
||||
* https://github.com/Karaka-Management/phpOMS/issues/370
|
||||
*/
|
||||
final class WriteMapper extends DataMapperAbstract
|
||||
{
|
||||
|
|
|
|||
|
|
@ -159,6 +159,10 @@ class Builder extends BuilderAbstract
|
|||
*
|
||||
* @return self
|
||||
*
|
||||
* @todo Allow to create db indices in json files
|
||||
* e.g. "index": {"idx1": ["col1"], "idx2": ["col1", "col2"] }
|
||||
* https://github.com/Karaka-Management/phpOMS/issues/355
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function createFromSchema(array $definition, ConnectionAbstract $connection) : self
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ final class Sphere implements D3ShapeInterface
|
|||
/**
|
||||
* Calculating the distance between two points on a sphere
|
||||
*
|
||||
* Geocoding
|
||||
*
|
||||
* @param float $latStart Latitude of start point in deg
|
||||
* @param float $longStart Longitude of start point in deg
|
||||
* @param float $latEnd Latitude of target point in deg
|
||||
|
|
@ -77,6 +79,27 @@ final class Sphere implements D3ShapeInterface
|
|||
return $angle * $radius;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a bounding box around a lat/lon defined by a distance in meter
|
||||
*
|
||||
* @param float $lat Latitude
|
||||
* @param float $lon Longitude
|
||||
* @param float $distance Radius in meter
|
||||
*
|
||||
* @return array {a:array{lat:float, lon:float}, b:array{lat:float, lon:float}, c:array{lat:float, lon:float}, d:array{lat:float, lon:float}}
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function boundingBox(float $lat, float $lon, float $distance) : array
|
||||
{
|
||||
return [
|
||||
'a' => ['lat' => $lat + (1 / 111133.0 / 2 * $distance), 'lon' => $lon - (1 / 111320.0 * \cos(\deg2rad($lat - (1 / 111133.0 / 2 * $distance))) * $distance)],
|
||||
'b' => ['lat' => $lat + (1 / 111133.0 / 2 * $distance), 'lon' => $lon + (1 / 111320.0 * \cos(\deg2rad($lat - (1 / 111133.0 / 2 * $distance))) * $distance)],
|
||||
'c' => ['lat' => $lat - (1 / 111133.0 / 2 * $distance), 'lon' => $lon - (1 / 111320.0 * \cos(\deg2rad($lat - (1 / 111133.0 / 2 * $distance))) * $distance)],
|
||||
'd' => ['lat' => $lat - (1 / 111133.0 / 2 * $distance), 'lon' => $lon + (1 / 111320.0 * \cos(\deg2rad($lat - (1 / 111133.0 / 2 * $distance))) * $distance)],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sphere by radius
|
||||
*
|
||||
|
|
|
|||
|
|
@ -124,4 +124,46 @@ final class Numbers
|
|||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remap numbers between 0 and X to 0 and 100
|
||||
*
|
||||
* @param int $number Number to remap
|
||||
* @param int $max Max possible number
|
||||
* @param float $exp Exponential modifier
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function remapRangeExponentially(int $number, int $max, float $exp = 1.0) : float
|
||||
{
|
||||
if ($number > $max) {
|
||||
$number = $max;
|
||||
}
|
||||
|
||||
$exponent = ($number / $max) * $exp;
|
||||
$mapped = (exp($exponent) - 1) / (exp($exp) - 1) * 100;
|
||||
|
||||
return $mapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remap numbers between 0 and X to 0 and 100
|
||||
*
|
||||
* @param int $number Number to remap
|
||||
* @param int $max Max possible number
|
||||
*
|
||||
* @return float
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function remapRangeLog(int $number, int $max) : float
|
||||
{
|
||||
if ($number > $max) {
|
||||
$number = $max;
|
||||
}
|
||||
|
||||
return (log($number + 1) / log($max + 1)) * 100;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,134 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Jingga
|
||||
*
|
||||
* PHP Version 8.2
|
||||
*
|
||||
* @package phpOMS\Model\Message
|
||||
* @copyright Dennis Eichhorn
|
||||
* @license OMS License 2.0
|
||||
* @version 1.0.0
|
||||
* @link https://jingga.app
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpOMS\Model\Message;
|
||||
|
||||
use phpOMS\Contract\SerializableInterface;
|
||||
|
||||
/**
|
||||
* Reload class.
|
||||
*
|
||||
* @package phpOMS\Model\Message
|
||||
* @license OMS License 2.0
|
||||
* @link https://jingga.app
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Reload implements \JsonSerializable, SerializableInterface
|
||||
{
|
||||
/**
|
||||
* Message type.
|
||||
*
|
||||
* @var string
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public const TYPE = 'reload';
|
||||
|
||||
/**
|
||||
* Delay in ms.
|
||||
*
|
||||
* @var int
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private int $delay = 0;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param int $delay Delay in ms
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __construct(int $delay = 0)
|
||||
{
|
||||
$this->delay = $delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set delay.
|
||||
*
|
||||
* @param int $delay Delay in ms
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function setDelay(int $delay) : void
|
||||
{
|
||||
$this->delay = $delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render message.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function serialize() : string
|
||||
{
|
||||
return $this->__toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function unserialize(mixed $raw) : void
|
||||
{
|
||||
if (!\is_string($raw)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$unserialized = \json_decode($raw, true);
|
||||
if (!\is_array($unserialized)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->delay = $unserialized['time'] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stringify.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return (string) \json_encode($this->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate message array.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public function toArray() : array
|
||||
{
|
||||
return [
|
||||
'type' => self::TYPE,
|
||||
'time' => $this->delay,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function jsonSerialize() : mixed
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,18 @@ abstract class StatusAbstract
|
|||
*/
|
||||
public const PATH = '';
|
||||
|
||||
/**
|
||||
* Routes.
|
||||
*
|
||||
* Include consideres the state of the file during script execution.
|
||||
* This means setting it to empty has no effect if it was not empty before.
|
||||
* There are also other merging bugs that can happen.
|
||||
*
|
||||
* @var array<string, array>
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private static array $routes = [];
|
||||
|
||||
/**
|
||||
* Deactivate module.
|
||||
*
|
||||
|
|
@ -105,14 +117,17 @@ abstract class StatusAbstract
|
|||
throw new PermissionException($destRoutePath); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
$appRoutes = include $destRoutePath;
|
||||
if (!isset(self::$routes[$destRoutePath])) {
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
self::$routes[$destRoutePath] = include $destRoutePath;
|
||||
}
|
||||
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
$moduleRoutes = include $srcRoutePath;
|
||||
|
||||
$appRoutes = \array_merge_recursive($appRoutes, $moduleRoutes);
|
||||
self::$routes[$destRoutePath] = \array_merge_recursive(self::$routes[$destRoutePath], $moduleRoutes);
|
||||
|
||||
\file_put_contents($destRoutePath, '<?php return ' . ArrayParser::serializeArray($appRoutes) . ';', \LOCK_EX);
|
||||
\file_put_contents($destRoutePath, '<?php return ' . ArrayParser::serializeArray(self::$routes[$destRoutePath]) . ';', \LOCK_EX);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -150,22 +165,31 @@ abstract class StatusAbstract
|
|||
if ($child instanceof Directory) {
|
||||
/** @var File $file */
|
||||
foreach ($child as $file) {
|
||||
if (!\is_dir(__DIR__ . '/../../' . $child->getName() . '/' . \basename($file->getName(), '.php'))
|
||||
|| ($appInfo !== null && \basename($file->getName(), '.php') !== $appInfo->getInternalName())
|
||||
$appName = \basename($file->getName(), '.php');
|
||||
|
||||
if (!\is_dir(__DIR__ . '/../../' . $child->getName() . '/' . $appName)
|
||||
|| ($appInfo !== null && $appName !== $appInfo->getInternalName())
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::installRoutesHooks(__DIR__ . '/../../' . $child->getName() . '/' . \basename($file->getName(), '.php') . '/' . $type . '.php', $file->getPath());
|
||||
self::installRoutesHooks(
|
||||
__DIR__ . '/../../' . $child->getName() . '/' . $appName . '/' . $type . '.php',
|
||||
$file->getPath()
|
||||
);
|
||||
}
|
||||
} elseif ($child instanceof File) {
|
||||
$appName = \basename($child->getName(), '.php');
|
||||
if (!\is_dir(__DIR__ . '/../../' . $child->getName())
|
||||
|| ($appInfo !== null && \basename($child->getName(), '.php') !== $appInfo->getInternalName())
|
||||
|| ($appInfo !== null && $appName !== $appInfo->getInternalName())
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self::installRoutesHooks(__DIR__ . '/../../' . $child->getName() . '/' . $type . '.php', $child->getPath());
|
||||
self::installRoutesHooks(
|
||||
__DIR__ . '/../../' . $child->getName() . '/' . $type . '.php',
|
||||
$child->getPath()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -227,7 +251,10 @@ abstract class StatusAbstract
|
|||
continue;
|
||||
}
|
||||
|
||||
self::uninstallRoutesHooks(__DIR__ . '/../../' . $child->getName() . '/' . \basename($file->getName(), '.php') . '/'. $type . '.php', $file->getPath());
|
||||
self::uninstallRoutesHooks(
|
||||
__DIR__ . '/../../' . $child->getName() . '/' . \basename($file->getName(), '.php') . '/'. $type . '.php',
|
||||
$file->getPath()
|
||||
);
|
||||
}
|
||||
} elseif ($child instanceof File) {
|
||||
if (!\is_dir(__DIR__ . '/../../' . $child->getName())
|
||||
|
|
@ -236,7 +263,10 @@ abstract class StatusAbstract
|
|||
continue;
|
||||
}
|
||||
|
||||
self::uninstallRoutesHooks(__DIR__ . '/../../' . $child->getName() . '/'. $type . '.php', $child->getPath());
|
||||
self::uninstallRoutesHooks(
|
||||
__DIR__ . '/../../' . $child->getName() . '/'. $type . '.php',
|
||||
$child->getPath()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,4 +114,81 @@ final class Guard
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isSafeFile(string $path) : bool
|
||||
{
|
||||
$tmp = \strtolower($path);
|
||||
if (\str_ends_with($tmp, '.csv')) {
|
||||
return self::isSafeXml($path);
|
||||
} elseif (\str_ends_with($tmp, '.xml')) {
|
||||
return self::isSafeCsv($path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isSafeXml(string $path) : bool
|
||||
{
|
||||
$maxEntityDepth = 7;
|
||||
$xml = \file_get_contents($path);
|
||||
|
||||
// Detect injections
|
||||
$injectionPatterns = [
|
||||
'/<!ENTITY\s+%(\w+)\s+"(.+)">/',
|
||||
'/<!ENTITY\s+%(\w+)\s+SYSTEM\s+"(.+)">/',
|
||||
'/<!ENTITY\s+(\w+)\s+"(.+)">/',
|
||||
'/<!DOCTYPE\s+(.+)\s+SYSTEM\s+"(.+)">/'
|
||||
];
|
||||
|
||||
foreach ($injectionPatterns as $pattern) {
|
||||
if (\preg_match($pattern, $xml) !== false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$reader = new \XMLReader();
|
||||
|
||||
$reader->XML($xml);
|
||||
$reader->setParserProperty(\XMLReader::SUBST_ENTITIES, true);
|
||||
|
||||
$foundBillionLaughsAttack = false;
|
||||
$entityCount = 0;
|
||||
|
||||
while ($reader->read()) {
|
||||
if ($reader->nodeType === \XMLReader::ENTITY_REF) {
|
||||
++$entityCount;
|
||||
|
||||
if ($entityCount > $maxEntityDepth) {
|
||||
$foundBillionLaughsAttack = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !$foundBillionLaughsAttack;
|
||||
}
|
||||
|
||||
public static function isSafeCsv(string $path) : bool
|
||||
{
|
||||
$input = \fopen($path, 'r');
|
||||
if (!$input) {
|
||||
return true;
|
||||
}
|
||||
|
||||
while (($row = \fgetcsv($input)) !== false) {
|
||||
foreach ($row as &$cell) {
|
||||
if (\preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x1F]+/', $cell) !== false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (\in_array($cell[0] ?? '', ['=', '+', '-', '@'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
\fclose($input);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ final class FileUtils
|
|||
*
|
||||
* @return int Extension type
|
||||
*
|
||||
* @question Consider to move directly to ExtensionType enum and create ::fromExtension()
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public static function getExtensionType(string $extension) : int
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ final class HttpUri implements UriInterface
|
|||
return ((!empty($_SERVER['HTTPS'] ?? '') && ($_SERVER['HTTPS'] ?? '') !== 'off')
|
||||
|| (($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https')
|
||||
|| (($_SERVER['HTTP_X_FORWARDED_SSL'] ?? '') === 'on') ? 'https' : 'http')
|
||||
. '://' . ($_SERVER['HTTP_HOST'] ?? ''). ($_SERVER['REQUEST_URI'] ?? '');
|
||||
. '://' . ($_SERVER['HTTP_HOST'] ?? '') . ($_SERVER['REQUEST_URI'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -281,8 +281,8 @@ final class UriFactory
|
|||
? '#' . \str_replace('\#', '#', $urlStructure['fragment']) : '');
|
||||
|
||||
return \str_replace(
|
||||
['%5C%7B', '%5C%7D', '%5C%3F', '%5C%23'],
|
||||
['{', '}', '?', '#'],
|
||||
['%5C%7B', '%5C%7D', '%5C%3F', '%5C%23', '%C2%B0'],
|
||||
['{', '}', '?', '#', '°'],
|
||||
$escaped
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,7 +126,10 @@ final class Zip implements ArchiveInterface
|
|||
|
||||
try {
|
||||
$zip = new \ZipArchive();
|
||||
if (!$zip->open($source)) {
|
||||
if (!$zip->open($source)
|
||||
|| $zip->numFiles > 10000
|
||||
|| (($zip->statIndex(0)['size'] ?? 0) + ($zip->statIndex(1)['size'] ?? 0)) > 2e+9
|
||||
) {
|
||||
return false; // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1274,6 +1274,9 @@ class Markdown
|
|||
return null;
|
||||
}
|
||||
|
||||
// @todo Optimize away the child <span> element for spoilers (if reasonable)
|
||||
// If possible don't forget to adjust scss
|
||||
// https://github.com/Karaka-Management/phpOMS/issues/367
|
||||
return [
|
||||
'extent' => \strlen($matches[0]),
|
||||
'element' => [
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* Jingga
|
||||
*
|
||||
* PHP Version 8.2
|
||||
*
|
||||
* @package tests
|
||||
* @copyright 2013 Dennis Eichhorn
|
||||
* @license OMS License 2.0
|
||||
* @version 1.0.0
|
||||
* @link https://jingga.app
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace phpOMS\tests\phpOMS\Model\Message;
|
||||
|
||||
use phpOMS\Model\Message\Reload;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\CoversClass(\phpOMS\Model\Message\Reload::class)]
|
||||
final class ReloadTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
#[\PHPUnit\Framework\Attributes\Group('framework')]
|
||||
public function testDefault() : void
|
||||
{
|
||||
$obj = new Reload();
|
||||
|
||||
/* Testing default values */
|
||||
self::assertEquals(0, $obj->toArray()['time']);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\Group('framework')]
|
||||
public function testSetGet() : void
|
||||
{
|
||||
$obj = new Reload(5);
|
||||
|
||||
self::assertEquals(['type' => 'reload', 'time' => 5], $obj->toArray());
|
||||
self::assertEquals(\json_encode(['type' => 'reload', 'time' => 5]), $obj->serialize());
|
||||
self::assertEquals(['type' => 'reload', 'time' => 5], $obj->jsonSerialize());
|
||||
|
||||
$obj->setDelay(6);
|
||||
self::assertEquals(['type' => 'reload', 'time' => 6], $obj->toArray());
|
||||
|
||||
$obj2 = new Reload();
|
||||
$obj2->unserialize($obj->serialize());
|
||||
self::assertEquals($obj, $obj2);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user