From 26a22f529930d63a2d18d9f76d17ac6cc6004964 Mon Sep 17 00:00:00 2001 From: Roman Hergenreder Date: Sun, 27 Nov 2022 12:33:27 +0100 Subject: [PATCH] DB Entity: Inheriting/Extending --- Core/API/RoutesAPI.class.php | 38 ++--- Core/Configuration/CreateDatabase.class.php | 38 +++-- Core/Driver/Logger/Logger.class.php | 20 ++- .../Attribute/ExtendingEnum.class.php | 18 +++ .../Controller/DatabaseEntity.class.php | 8 +- .../Controller/DatabaseEntityHandler.php | 134 ++++++++++++------ .../Controller/DatabaseEntityQuery.class.php | 4 + .../Route.class.php} | 56 +++++++- .../DatabaseEntity/TwoFactorToken.class.php | 20 +-- Core/Objects/Router/ApiRoute.class.php | 5 +- Core/Objects/Router/DocumentRoute.class.php | 34 +++-- Core/Objects/Router/EmptyRoute.class.php | 6 +- .../Router/RedirectPermanentlyRoute.class.php | 9 ++ Core/Objects/Router/RedirectRoute.class.php | 19 ++- .../Router/RedirectTemporaryRoute.class.php | 9 ++ Core/Objects/Router/Router.class.php | 7 +- Core/Objects/Router/StaticFileRoute.class.php | 23 ++- Core/Objects/Router/StaticRoute.class.php | 6 +- .../KeyBasedTwoFactorToken.class.php | 7 +- .../TimeBasedTwoFactorToken.class.php | 4 +- 20 files changed, 308 insertions(+), 157 deletions(-) create mode 100644 Core/Objects/DatabaseEntity/Attribute/ExtendingEnum.class.php rename Core/Objects/{Router/AbstractRoute.class.php => DatabaseEntity/Route.class.php} (73%) create mode 100644 Core/Objects/Router/RedirectPermanentlyRoute.class.php create mode 100644 Core/Objects/Router/RedirectTemporaryRoute.class.php diff --git a/Core/API/RoutesAPI.class.php b/Core/API/RoutesAPI.class.php index fc61e1b..b7b6cb2 100644 --- a/Core/API/RoutesAPI.class.php +++ b/Core/API/RoutesAPI.class.php @@ -69,8 +69,11 @@ namespace Core\API\Routes { use Core\Driver\SQL\Condition\Compare; use Core\Driver\SQL\Condition\CondBool; use Core\Objects\Context; + use Core\Objects\DatabaseEntity\Route; use Core\Objects\Router\DocumentRoute; + use Core\Objects\Router\RedirectPermanentlyRoute; use Core\Objects\Router\RedirectRoute; + use Core\Objects\Router\RedirectTemporaryRoute; use Core\Objects\Router\Router; use Core\Objects\Router\StaticFileRoute; @@ -350,41 +353,20 @@ namespace Core\API\Routes { protected function _execute(): bool { $sql = $this->context->getSQL(); - $res = $sql - ->select("id", "request", "action", "target", "extra", "exact") - ->from("Route") - ->where(new CondBool("active")) - ->orderBy("id")->ascending() - ->execute(); + $routes = Route::findBy(Route::createBuilder($sql, false) + ->whereTrue("active") + ->orderBy("id") + ->ascending()); - $this->success = $res !== false; + $this->success = $routes !== false; $this->lastError = $sql->getLastError(); if (!$this->success) { return false; } $this->router = new Router($this->context); - foreach ($res as $row) { - $request = $row["request"]; - $target = $row["target"]; - $exact = $sql->parseBool($row["exact"]); - switch ($row["action"]) { - case "redirect_temporary": - $this->router->addRoute(new RedirectRoute($request, $exact, $target, 307)); - break; - case "redirect_permanently": - $this->router->addRoute(new RedirectRoute($request, $exact, $target, 308)); - break; - case "static": - $this->router->addRoute(new StaticFileRoute($request, $exact, $target)); - break; - case "dynamic": - $extra = json_decode($row["extra"]) ?? []; - $this->router->addRoute(new DocumentRoute($request, $exact, $target, ...$extra)); - break; - default: - break; - } + foreach ($routes as $route) { + $this->router->addRoute($route); } $this->success = $this->router->writeCache($this->routerCachePath); diff --git a/Core/Configuration/CreateDatabase.class.php b/Core/Configuration/CreateDatabase.class.php index f693442..28693fa 100644 --- a/Core/Configuration/CreateDatabase.class.php +++ b/Core/Configuration/CreateDatabase.class.php @@ -6,6 +6,10 @@ use Core\Driver\SQL\SQL; use Core\Objects\DatabaseEntity\Controller\DatabaseEntity; use Core\Objects\DatabaseEntity\Group; use Core\Objects\DatabaseEntity\Language; +use Core\Objects\DatabaseEntity\Route; +use Core\Objects\Router\DocumentRoute; +use Core\Objects\Router\StaticFileRoute; +use Core\Objects\Router\StaticRoute; use PHPUnit\Util\Exception; class CreateDatabase extends DatabaseScript { @@ -32,27 +36,17 @@ class CreateDatabase extends DatabaseScript { ->addString("cookie", 26) ->unique("day", "cookie"); - $queries[] = $sql->createTable("Route") - ->addSerial("id") - ->addString("request", 128) - ->addEnum("action", array("redirect_temporary", "redirect_permanently", "static", "dynamic")) - ->addString("target", 128) - ->addString("extra", 64, true) - ->addBool("active", true) - ->addBool("exact", true) - ->primaryKey("id") - ->unique("request"); - - $queries[] = $sql->insert("Route", ["request", "action", "target", "extra", "exact"]) - ->addRow("/admin", "dynamic", "\\Core\\Documents\\Admin", NULL, false) - ->addRow("/register", "dynamic", "\\Core\\Documents\\Account", json_encode(["account/register.twig"]), true) - ->addRow("/confirmEmail", "dynamic", "\\Core\\Documents\\Account", json_encode(["account/confirm_email.twig"]), true) - ->addRow("/acceptInvite", "dynamic", "\\Core\\Documents\\Account", json_encode(["account/accept_invite.twig"]), true) - ->addRow("/resetPassword", "dynamic", "\\Core\\Documents\\Account", json_encode(["account/reset_password.twig"]), true) - ->addRow("/login", "dynamic", "\\Core\\Documents\\Account", json_encode(["account/login.twig"]), true) - ->addRow("/resendConfirmEmail", "dynamic", "\\Core\\Documents\\Account", json_encode(["account/resend_confirm_email.twig"]), true) - ->addRow("/debug", "dynamic", "\\Core\\Documents\\Info", NULL, true) - ->addRow("/", "static", "/static/welcome.html", NULL, true); + $queries[] = Route::getHandler($sql)->getInsertQuery([ + new DocumentRoute("/admin", false, \Core\Documents\Admin::class), + new DocumentRoute("/register", true, \Core\Documents\Account::class, "account/register.twig"), + new DocumentRoute("/confirmEmail", true, \Core\Documents\Account::class, "account/confirm_email.twig"), + new DocumentRoute("/acceptInvite", true, \Core\Documents\Account::class, "account/accept_invite.twig"), + new DocumentRoute("/resetPassword", true, \Core\Documents\Account::class, "account/reset_password.twig"), + new DocumentRoute("/login", true, \Core\Documents\Account::class, "account/login.twig"), + new DocumentRoute("/resendConfirmEmail", true, \Core\Documents\Account::class, "account/resend_confirm_email.twig"), + new DocumentRoute("/debug", true, \Core\Documents\Info::class), + new StaticFileRoute("/static", true, "/static/welcome.html"), + ]); $queries[] = $sql->createTable("Settings") ->addString("name", 32) @@ -140,7 +134,7 @@ class CreateDatabase extends DatabaseScript { $className = substr($file, 0, strlen($file) - strlen($suffix)); $className = "\\$baseDir\\Objects\\DatabaseEntity\\$className"; $reflectionClass = new \ReflectionClass($className); - if ($reflectionClass->isSubclassOf(DatabaseEntity::class)) { + if ($reflectionClass->getParentClass()?->getName() === DatabaseEntity::class) { $method = "$className::getHandler"; $handler = call_user_func($method, $sql); $persistables[$handler->getTableName()] = $handler; diff --git a/Core/Driver/Logger/Logger.class.php b/Core/Driver/Logger/Logger.class.php index 239cb65..7929e2b 100644 --- a/Core/Driver/Logger/Logger.class.php +++ b/Core/Driver/Logger/Logger.class.php @@ -7,12 +7,18 @@ use Core\Driver\SQL\SQL; class Logger { public const LOG_FILE_DATE_FORMAT = "Y-m-d_H-i-s_v"; + public const LOG_LEVEL_DEBUG = 0; + public const LOG_LEVEL_INFO = 1; + public const LOG_LEVEL_WARNING = 2; + public const LOG_LEVEL_ERROR = 3; + public const LOG_LEVEL_SEVERE = 4; + public const LOG_LEVELS = [ - 0 => "debug", - 1 => "info", - 2 => "warning", - 3 => "error", - 4 => "severe" + self::LOG_LEVEL_DEBUG => "debug", + self::LOG_LEVEL_INFO => "info", + self::LOG_LEVEL_WARNING => "warning", + self::LOG_LEVEL_ERROR => "error", + self::LOG_LEVEL_SEVERE => "severe" ]; public static Logger $INSTANCE; @@ -59,6 +65,10 @@ class Logger { return; } + if ($severity >= self::LOG_LEVEL_WARNING) { + error_log($message); + } + if ($this->sql !== null && $this->sql->isConnected()) { $success = $this->sql->insert("SystemLog", ["module", "message", "severity"]) ->addRow($this->module, $message, $severity) diff --git a/Core/Objects/DatabaseEntity/Attribute/ExtendingEnum.class.php b/Core/Objects/DatabaseEntity/Attribute/ExtendingEnum.class.php new file mode 100644 index 0000000..74ec22f --- /dev/null +++ b/Core/Objects/DatabaseEntity/Attribute/ExtendingEnum.class.php @@ -0,0 +1,18 @@ +mappings = $values; + } + + public function getMappings(): array { + return $this->mappings; + } +} \ No newline at end of file diff --git a/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php b/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php index c718c9b..12894b6 100644 --- a/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php +++ b/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php @@ -32,7 +32,7 @@ abstract class DatabaseEntity { return $handler->entityFromRow($row); } - public static function newInstance(\ReflectionClass $reflectionClass, array $row) { + public static function newInstance(\ReflectionClass $reflectionClass) { return $reflectionClass->newInstanceWithoutConstructor(); } @@ -127,6 +127,12 @@ abstract class DatabaseEntity { $class = $obj_or_class; } + // if we are in an extending context, get the database handler for the root entity, + // as we do not persist attributes of the inheriting class + while ($class->getParentClass()->getName() !== DatabaseEntity::class) { + $class = $class->getParentClass(); + } + $handler = self::$handlers[$class->getShortName()] ?? null; if (!$handler) { $handler = new DatabaseEntityHandler($sql, $class); diff --git a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php index fc91b08..ae1199e 100644 --- a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php +++ b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php @@ -10,7 +10,6 @@ use Core\Driver\SQL\Column\EnumColumn; use Core\Driver\SQL\Column\IntColumn; use Core\Driver\SQL\Column\JsonColumn; use Core\Driver\SQL\Column\StringColumn; -use Core\Driver\SQL\Condition\Compare; use Core\Driver\SQL\Condition\CondAnd; use Core\Driver\SQL\Condition\CondBool; use Core\Driver\SQL\Condition\CondIn; @@ -33,12 +32,12 @@ use Core\Driver\SQL\Type\CurrentColumn; use Core\Driver\SQL\Type\CurrentTable; use Core\Objects\DatabaseEntity\Attribute\Enum; use Core\Objects\DatabaseEntity\Attribute\DefaultValue; +use Core\Objects\DatabaseEntity\Attribute\ExtendingEnum; use Core\Objects\DatabaseEntity\Attribute\Json; use Core\Objects\DatabaseEntity\Attribute\MaxLength; use Core\Objects\DatabaseEntity\Attribute\Multiple; use Core\Objects\DatabaseEntity\Attribute\Transient; use Core\Objects\DatabaseEntity\Attribute\Unique; -use PHPUnit\Util\Exception; class DatabaseEntityHandler implements Persistable { @@ -49,6 +48,8 @@ class DatabaseEntityHandler implements Persistable { private array $relations; private array $constraints; private array $nmRelations; + private array $extendingClasses; + private ?\ReflectionProperty $extendingProperty; private SQL $sql; private Logger $logger; @@ -66,7 +67,9 @@ class DatabaseEntityHandler implements Persistable { $this->properties = []; // property name => \ReflectionProperty $this->relations = []; // property name => DatabaseEntityHandler $this->constraints = []; // \Driver\SQL\Constraint\Constraint - $this->nmRelations = []; // table name => NMRelation + $this->nmRelations = []; // table name => NMRelation + $this->extendingClasses = []; // enum value => \ReflectionClass + $this->extendingProperty = null; // only one attribute can hold the type of the extending class foreach ($this->entityClass->getProperties() as $property) { $propertyName = $property->getName(); @@ -91,6 +94,36 @@ class DatabaseEntityHandler implements Persistable { continue; } + $ext = self::getAttribute($property, ExtendingEnum::class); + if ($ext !== null) { + if ($this->extendingProperty !== null) { + $this->raiseError("Cannot have more than one extending property"); + } else { + $this->extendingProperty = $property; + $enumMappings = $ext->getMappings(); + foreach ($enumMappings as $key => $extendingClass) { + if (!is_string($key)) { + $type = gettype($key); + $this->raiseError("Extending enum must be an array of string => class, got type '$type' for key: " . print_r($key, true)); + } else if (!is_string($extendingClass)) { + $type = gettype($extendingClass); + $this->raiseError("Extending enum must be an array of string => class, got type '$type' for value: " . print_r($extendingClass, true)); + } + + try { + $requestedClass = new \ReflectionClass($extendingClass); + if (!$requestedClass->isSubclassOf($this->entityClass)) { + $this->raiseError("Class '$extendingClass' must be an inheriting from '" . $this->entityClass->getName() . "' for an extending enum"); + } else { + $this->extendingClasses[$key] = $requestedClass; + } + } catch (\ReflectionException $ex) { + $this->raiseError("Cannot persist extending enum for class $extendingClass: " . $ex->getMessage()); + } + } + } + } + $defaultValue = (self::getAttribute($property, DefaultValue::class))?->getValue(); $isUnique = !empty($property->getAttributes(Unique::class)); @@ -112,18 +145,6 @@ class DatabaseEntityHandler implements Persistable { $this->columns[$propertyName] = new BoolColumn($columnName, $defaultValue ?? false); } else if ($propertyTypeName === 'DateTime') { $this->columns[$propertyName] = new DateTimeColumn($columnName, $nullable, $defaultValue); - /*} else if ($propertyName === 'array') { - $many = self::getAttribute($property, Many::class); - if ($many) { - $requestedType = $many->getValue(); - if (isClass($requestedType)) { - $requestedClass = new \ReflectionClass($requestedType); - } else { - $this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $requestedType"); - } - } else { - $this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $propertyTypeName"); - }*/ } else if ($propertyTypeName === "array") { $multiple = self::getAttribute($property, Multiple::class); if (!$multiple) { @@ -248,42 +269,62 @@ class DatabaseEntityHandler implements Persistable { return $rel_row; } + private function getValueFromRow(array $row, string $propertyName, mixed &$value): bool { + $column = $this->columns[$propertyName] ?? null; + if (!$column) { + return false; + } + + $columnName = $column->getName(); + if (!array_key_exists($columnName, $row)) { + return false; + } + + $value = $row[$columnName]; + if ($column instanceof DateTimeColumn) { + $value = new \DateTime($value); + } else if ($column instanceof JsonColumn) { + $value = json_decode($value); + } else if (isset($this->relations[$propertyName])) { + $relColumnPrefix = self::getColumnName($propertyName) . "_"; + if (array_key_exists($relColumnPrefix . "id", $row)) { + $relId = $row[$relColumnPrefix . "id"]; + if ($relId !== null) { + $relationHandler = $this->relations[$propertyName]; + $value = $relationHandler->entityFromRow(self::getPrefixedRow($row, $relColumnPrefix)); + } else if (!$column->notNull()) { + $value = null; + } else { + return false; + } + } else { + return false; + } + } + + return true; + } + public function entityFromRow(array $row): ?DatabaseEntity { try { - $entity = call_user_func($this->entityClass->getName() . "::newInstance", $this->entityClass, $row); + $constructorClass = $this->entityClass; + if ($this->extendingProperty !== null) { + if ($this->getValueFromRow($row, $this->extendingProperty->getName(), $enumValue)) { + if ($enumValue && isset($this->extendingClasses[$enumValue])) { + $constructorClass = $this->extendingClasses[$enumValue]; + } + } + } + + $entity = call_user_func($constructorClass->getName() . "::newInstance", $constructorClass); if (!($entity instanceof DatabaseEntity)) { $this->logger->error("Created Object is not of type DatabaseEntity"); return null; } - foreach ($this->columns as $propertyName => $column) { - $columnName = $column->getName(); - if (array_key_exists($columnName, $row)) { - $value = $row[$columnName]; - $property = $this->properties[$propertyName]; - - if ($column instanceof DateTimeColumn) { - $value = new \DateTime($value); - } else if ($column instanceof JsonColumn) { - $value = json_decode($value); - } else if (isset($this->relations[$propertyName])) { - $relColumnPrefix = self::getColumnName($propertyName) . "_"; - if (array_key_exists($relColumnPrefix . "id", $row)) { - $relId = $row[$relColumnPrefix . "id"]; - if ($relId !== null) { - $relationHandler = $this->relations[$propertyName]; - $value = $relationHandler->entityFromRow(self::getPrefixedRow($row, $relColumnPrefix)); - } else if (!$column->notNull()) { - $value = null; - } else { - continue; - } - } else { - continue; - } - } - + foreach ($this->properties as $property) { + if ($this->getValueFromRow($row, $property->getName(), $value)) { $property->setAccessible(true); $property->setValue($entity, $value); } @@ -449,7 +490,7 @@ class DatabaseEntityHandler implements Persistable { } } - $rows = $relEntityQuery->execute(); + $rows = $relEntityQuery->executeSQL(); if (!is_array($rows)) { $this->logger->error("Error fetching n:m relations from table: '$nmTable': " . $this->sql->getLastError()); return; @@ -698,7 +739,7 @@ class DatabaseEntityHandler implements Persistable { private function raiseError(string $message) { $this->logger->error($message); - throw new Exception($message); + throw new \Exception($message); } public function getSQL(): SQL { @@ -713,6 +754,9 @@ class DatabaseEntityHandler implements Persistable { $firstEntity = (is_array($entities) ? current($entities) : $entities); $firstRow = $this->prepareRow($firstEntity, "insert"); + if ($firstRow === false) { + return null; + } $statement = $this->sql->insert($this->tableName, array_keys($firstRow)) ->addRow(...array_values($firstRow)); diff --git a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php index 00c8e3a..4de5bba 100644 --- a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php +++ b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php @@ -137,4 +137,8 @@ class DatabaseEntityQuery extends Select { return null; } } + + public function executeSQL() { + return parent::execute(); + } } \ No newline at end of file diff --git a/Core/Objects/Router/AbstractRoute.class.php b/Core/Objects/DatabaseEntity/Route.class.php similarity index 73% rename from Core/Objects/Router/AbstractRoute.class.php rename to Core/Objects/DatabaseEntity/Route.class.php index 7e95b23..6aa16e4 100644 --- a/Core/Objects/Router/AbstractRoute.class.php +++ b/Core/Objects/DatabaseEntity/Route.class.php @@ -1,19 +1,53 @@ RedirectRoute::class, + "redirect_permanently" => RedirectRoute::class, + "static" => StaticFileRoute::class, + "dynamic" => DocumentRoute::class + ]; + #[MaxLength(128)] + #[Unique] private string $pattern; + + #[ExtendingEnum(self::ROUTE_TYPES)] + private string $type; + + #[MaxLength(128)] + private string $target; + + #[MaxLength(64)] + protected ?string $extra; + + #[DefaultValue(true)] + private bool $active; + private bool $exact; - public function __construct(string $pattern, bool $exact = true) { + public function __construct(string $type, string $pattern, string $target, bool $exact = true) { + parent::__construct(); + $this->target = $target; $this->pattern = $pattern; $this->exact = $exact; + $this->type = $type; + $this->active = true; } private static function parseParamType(?string $type): ?int { @@ -37,6 +71,10 @@ abstract class AbstractRoute { return $this->pattern; } + public function getTarget(): string { + return $this->target; + } + public abstract function call(Router $router, array $params): string; protected function getArgs(): array { @@ -154,4 +192,16 @@ abstract class AbstractRoute { return $parameterNames; } + + public function jsonSerialize(): array { + return [ + "id" => $this->getId(), + "pattern" => $this->pattern, + "type" => $this->type, + "target" => $this->target, + "extra" => $this->extra, + "exact" => $this->exact, + "active" => $this->active, + ]; + } } \ No newline at end of file diff --git a/Core/Objects/DatabaseEntity/TwoFactorToken.class.php b/Core/Objects/DatabaseEntity/TwoFactorToken.class.php index 77c7049..9297f93 100644 --- a/Core/Objects/DatabaseEntity/TwoFactorToken.class.php +++ b/Core/Objects/DatabaseEntity/TwoFactorToken.class.php @@ -3,7 +3,7 @@ namespace Core\Objects\DatabaseEntity; use Core\Driver\SQL\SQL; -use Core\Objects\DatabaseEntity\Attribute\Enum; +use Core\Objects\DatabaseEntity\Attribute\ExtendingEnum; use Core\Objects\DatabaseEntity\Attribute\MaxLength; use Core\Objects\TwoFactor\KeyBasedTwoFactorToken; use Core\Objects\TwoFactor\TimeBasedTwoFactorToken; @@ -11,7 +11,12 @@ use Core\Objects\DatabaseEntity\Controller\DatabaseEntity; abstract class TwoFactorToken extends DatabaseEntity { - #[Enum('totp','fido')] private string $type; + const TWO_FACTOR_TOKEN_TYPES = [ + "totp" => TimeBasedTwoFactorToken::class, + "fido" => KeyBasedTwoFactorToken::class, + ]; + + #[ExtendingEnum(self::TWO_FACTOR_TOKEN_TYPES)] private string $type; private bool $confirmed; private bool $authenticated; #[MaxLength(512)] private string $data; @@ -62,17 +67,6 @@ abstract class TwoFactorToken extends DatabaseEntity { return $this->id; } - public static function newInstance(\ReflectionClass $reflectionClass, array $row) { - if ($row["type"] === TimeBasedTwoFactorToken::TYPE) { - return (new \ReflectionClass(TimeBasedTwoFactorToken::class))->newInstanceWithoutConstructor(); - } else if ($row["type"] === KeyBasedTwoFactorToken::TYPE) { - return (new \ReflectionClass(KeyBasedTwoFactorToken::class))->newInstanceWithoutConstructor(); - } else { - // TODO: error message - return null; - } - } - public function isAuthenticated(): bool { return $this->authenticated; } diff --git a/Core/Objects/Router/ApiRoute.class.php b/Core/Objects/Router/ApiRoute.class.php index c601e72..5e625a3 100644 --- a/Core/Objects/Router/ApiRoute.class.php +++ b/Core/Objects/Router/ApiRoute.class.php @@ -4,13 +4,14 @@ namespace Core\Objects\Router; use Core\API\Request; use Core\Elements\TemplateDocument; +use Core\Objects\DatabaseEntity\Route; use ReflectionClass; use ReflectionException; -class ApiRoute extends AbstractRoute { +class ApiRoute extends Route { public function __construct() { - parent::__construct("/api/{endpoint:?}/{method:?}", false); + parent::__construct("API", "/api/{endpoint:?}/{method:?}", false); } private static function checkClass(string $className): bool { diff --git a/Core/Objects/Router/DocumentRoute.class.php b/Core/Objects/Router/DocumentRoute.class.php index f7c5450..0c667c6 100644 --- a/Core/Objects/Router/DocumentRoute.class.php +++ b/Core/Objects/Router/DocumentRoute.class.php @@ -2,34 +2,44 @@ namespace Core\Objects\Router; +use Core\Driver\SQL\SQL; use Core\Elements\Document; use Core\Objects\Context; +use Core\Objects\DatabaseEntity\Route; use Core\Objects\Search\Searchable; use Core\Objects\Search\SearchQuery; +use JetBrains\PhpStorm\Pure; use ReflectionException; -class DocumentRoute extends AbstractRoute { +class DocumentRoute extends Route { use Searchable; - private string $className; private array $args; - private ?\ReflectionClass $reflectionClass; + private ?\ReflectionClass $reflectionClass = null; public function __construct(string $pattern, bool $exact, string $className, ...$args) { - parent::__construct($pattern, $exact); - $this->className = $className; + parent::__construct("dynamic", $pattern, $className, $exact); $this->args = $args; - $this->reflectionClass = null; + $this->extra = json_encode($args); + } + + public function postFetch(SQL $sql, array $row) { + parent::postFetch($sql, $row); + $this->args = json_decode($this->extra); + } + + #[Pure] private function getClassName(): string { + return $this->getTarget(); } private function loadClass(): bool { if ($this->reflectionClass === null) { try { - $file = getClassPath($this->className); + $file = getClassPath($this->getClassName()); if (file_exists($file)) { - $this->reflectionClass = new \ReflectionClass($this->className); + $this->reflectionClass = new \ReflectionClass($this->getClassName()); if ($this->reflectionClass->isSubclassOf(Document::class)) { return true; } @@ -56,20 +66,22 @@ class DocumentRoute extends AbstractRoute { } protected function getArgs(): array { - return array_merge(parent::getArgs(), [$this->className], $this->args); + return array_merge(parent::getArgs(), [$this->getClassName()], $this->args); } public function call(Router $router, array $params): string { + $className = $this->getClassName(); + try { if (!$this->loadClass()) { - return $router->returnStatusCode(500, [ "message" => "Error loading class: $this->className"]); + return $router->returnStatusCode(500, [ "message" => "Error loading class: $className"]); } $args = array_merge([$router], $this->args, $params); $document = $this->reflectionClass->newInstanceArgs($args); return $document->load($params); } catch (\ReflectionException $e) { - return $router->returnStatusCode(500, [ "message" => "Error loading class $this->className: " . $e->getMessage()]); + return $router->returnStatusCode(500, [ "message" => "Error loading class $className: " . $e->getMessage()]); } } diff --git a/Core/Objects/Router/EmptyRoute.class.php b/Core/Objects/Router/EmptyRoute.class.php index 39af23c..4d86748 100644 --- a/Core/Objects/Router/EmptyRoute.class.php +++ b/Core/Objects/Router/EmptyRoute.class.php @@ -2,10 +2,12 @@ namespace Core\Objects\Router; -class EmptyRoute extends AbstractRoute { +use Core\Objects\DatabaseEntity\Route; + +class EmptyRoute extends Route { public function __construct(string $pattern, bool $exact = true) { - parent::__construct($pattern, $exact); + parent::__construct("empty", $pattern, $exact); } public function call(Router $router, array $params): string { diff --git a/Core/Objects/Router/RedirectPermanentlyRoute.class.php b/Core/Objects/Router/RedirectPermanentlyRoute.class.php new file mode 100644 index 0000000..f8cafe1 --- /dev/null +++ b/Core/Objects/Router/RedirectPermanentlyRoute.class.php @@ -0,0 +1,9 @@ +destination = $destination; + public function __construct(string $type, string $pattern, bool $exact, string $destination, int $code = 307) { + parent::__construct($type, $pattern, $destination, $exact); $this->code = $code; } + #[Pure] private function getDestination(): string { + return $this->getTarget(); + } + public function call(Router $router, array $params): string { - header("Location: $this->destination"); + header("Location: " . $this->getDestination()); http_response_code($this->code); return ""; } protected function getArgs(): array { - return array_merge(parent::getArgs(), [$this->destination, $this->code]); + return array_merge(parent::getArgs(), [$this->getDestination(), $this->code]); } } \ No newline at end of file diff --git a/Core/Objects/Router/RedirectTemporaryRoute.class.php b/Core/Objects/Router/RedirectTemporaryRoute.class.php new file mode 100644 index 0000000..e77745a --- /dev/null +++ b/Core/Objects/Router/RedirectTemporaryRoute.class.php @@ -0,0 +1,9 @@ +activeRoute; } @@ -75,7 +76,7 @@ class Router { } } - public function addRoute(AbstractRoute $route) { + public function addRoute(Route $route) { if (preg_match("/^\/(\d+)$/", $route->getPattern(), $re)) { $this->statusCodeRoutes[$re[1]] = $route; } diff --git a/Core/Objects/Router/StaticFileRoute.class.php b/Core/Objects/Router/StaticFileRoute.class.php index d4a5251..b301ff8 100644 --- a/Core/Objects/Router/StaticFileRoute.class.php +++ b/Core/Objects/Router/StaticFileRoute.class.php @@ -2,22 +2,29 @@ namespace Core\Objects\Router; +use Core\Driver\SQL\SQL; use Core\Objects\Context; +use Core\Objects\DatabaseEntity\Route; use Core\Objects\Search\Searchable; use Core\Objects\Search\SearchQuery; use Core\Objects\Search\SearchResult; +use JetBrains\PhpStorm\Pure; -class StaticFileRoute extends AbstractRoute { +class StaticFileRoute extends Route { use Searchable; - private string $path; private int $code; public function __construct(string $pattern, bool $exact, string $path, int $code = 200) { - parent::__construct($pattern, $exact); - $this->path = $path; + parent::__construct("static", $pattern, $path, $exact); $this->code = $code; + $this->extra = json_encode($this->code); + } + + public function postFetch(SQL $sql, array $row) { + parent::postFetch($sql, $row); + $this->code = json_decode($this->extra); } public function call(Router $router, array $params): string { @@ -26,12 +33,16 @@ class StaticFileRoute extends AbstractRoute { return ""; } + #[Pure] private function getPath(): string { + return $this->getTarget(); + } + protected function getArgs(): array { - return array_merge(parent::getArgs(), [$this->path, $this->code]); + return array_merge(parent::getArgs(), [$this->getPath(), $this->code]); } public function getAbsolutePath(): string { - return WEBROOT . DIRECTORY_SEPARATOR . $this->path; + return WEBROOT . DIRECTORY_SEPARATOR . $this->getPath(); } public static function serveStatic(string $path, ?Router $router = null) { diff --git a/Core/Objects/Router/StaticRoute.class.php b/Core/Objects/Router/StaticRoute.class.php index 1b38e73..6fa509d 100644 --- a/Core/Objects/Router/StaticRoute.class.php +++ b/Core/Objects/Router/StaticRoute.class.php @@ -2,13 +2,15 @@ namespace Core\Objects\Router; -class StaticRoute extends AbstractRoute { +use Core\Objects\DatabaseEntity\Route; + +class StaticRoute extends Route { private string $data; private int $code; public function __construct(string $pattern, bool $exact, string $data, int $code = 200) { - parent::__construct($pattern, $exact); + parent::__construct("static", $pattern, $exact); $this->data = $data; $this->code = $code; } diff --git a/Core/Objects/TwoFactor/KeyBasedTwoFactorToken.class.php b/Core/Objects/TwoFactor/KeyBasedTwoFactorToken.class.php index 1577d3b..9c522a7 100644 --- a/Core/Objects/TwoFactor/KeyBasedTwoFactorToken.class.php +++ b/Core/Objects/TwoFactor/KeyBasedTwoFactorToken.class.php @@ -3,16 +3,15 @@ namespace Core\Objects\TwoFactor; use Cose\Algorithm\Signature\ECDSA\ECSignature; -use Core\Objects\DatabaseEntity\Attribute\Transient; use Core\Objects\DatabaseEntity\TwoFactorToken; class KeyBasedTwoFactorToken extends TwoFactorToken { const TYPE = "fido"; - #[Transient] private ?string $challenge; - #[Transient] private ?string $credentialId; - #[Transient] private ?PublicKey $publicKey; + private ?string $challenge; + private ?string $credentialId; + private ?PublicKey $publicKey; protected function readData(string $data) { if ($this->isConfirmed()) { diff --git a/Core/Objects/TwoFactor/TimeBasedTwoFactorToken.class.php b/Core/Objects/TwoFactor/TimeBasedTwoFactorToken.class.php index 1386373..c99b882 100644 --- a/Core/Objects/TwoFactor/TimeBasedTwoFactorToken.class.php +++ b/Core/Objects/TwoFactor/TimeBasedTwoFactorToken.class.php @@ -6,14 +6,12 @@ use Base32\Base32; use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QROptions; use Core\Objects\Context; -use Core\Objects\DatabaseEntity\Attribute\Transient; use Core\Objects\DatabaseEntity\TwoFactorToken; -use Core\Objects\DatabaseEntity\User; class TimeBasedTwoFactorToken extends TwoFactorToken { const TYPE = "totp"; - #[Transient] private string $secret; + private string $secret; public function __construct(string $secret) { parent::__construct(self::TYPE);