DB Entity: Inheriting/Extending
This commit is contained in:
parent
3b2b5984d6
commit
26a22f5299
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Objects\DatabaseEntity\Attribute;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class ExtendingEnum extends EnumArr {
|
||||
|
||||
private array $mappings;
|
||||
|
||||
public function __construct(array $values) {
|
||||
parent::__construct(array_keys($values));
|
||||
$this->mappings = $values;
|
||||
}
|
||||
|
||||
public function getMappings(): array {
|
||||
return $this->mappings;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
@ -67,6 +68,8 @@ class DatabaseEntityHandler implements Persistable {
|
||||
$this->relations = []; // property name => DatabaseEntityHandler
|
||||
$this->constraints = []; // \Driver\SQL\Constraint\Constraint
|
||||
$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,21 +269,18 @@ class DatabaseEntityHandler implements Persistable {
|
||||
return $rel_row;
|
||||
}
|
||||
|
||||
public function entityFromRow(array $row): ?DatabaseEntity {
|
||||
try {
|
||||
|
||||
$entity = call_user_func($this->entityClass->getName() . "::newInstance", $this->entityClass, $row);
|
||||
if (!($entity instanceof DatabaseEntity)) {
|
||||
$this->logger->error("Created Object is not of type DatabaseEntity");
|
||||
return null;
|
||||
private function getValueFromRow(array $row, string $propertyName, mixed &$value): bool {
|
||||
$column = $this->columns[$propertyName] ?? null;
|
||||
if (!$column) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($this->columns as $propertyName => $column) {
|
||||
$columnName = $column->getName();
|
||||
if (array_key_exists($columnName, $row)) {
|
||||
$value = $row[$columnName];
|
||||
$property = $this->properties[$propertyName];
|
||||
if (!array_key_exists($columnName, $row)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$value = $row[$columnName];
|
||||
if ($column instanceof DateTimeColumn) {
|
||||
$value = new \DateTime($value);
|
||||
} else if ($column instanceof JsonColumn) {
|
||||
@ -277,13 +295,36 @@ class DatabaseEntityHandler implements Persistable {
|
||||
} else if (!$column->notNull()) {
|
||||
$value = null;
|
||||
} else {
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function entityFromRow(array $row): ?DatabaseEntity {
|
||||
try {
|
||||
|
||||
$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->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));
|
||||
|
@ -137,4 +137,8 @@ class DatabaseEntityQuery extends Select {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function executeSQL() {
|
||||
return parent::execute();
|
||||
}
|
||||
}
|
@ -1,19 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Objects\Router;
|
||||
namespace Core\Objects\DatabaseEntity;
|
||||
|
||||
use Core\API\Parameter\Parameter;
|
||||
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||
use Core\Objects\DatabaseEntity\Attribute\ExtendingEnum;
|
||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||
use Core\Objects\DatabaseEntity\Attribute\Unique;
|
||||
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||
use Core\Objects\Router\DocumentRoute;
|
||||
use Core\Objects\Router\RedirectRoute;
|
||||
use Core\Objects\Router\Router;
|
||||
use Core\Objects\Router\StaticFileRoute;
|
||||
|
||||
abstract class AbstractRoute {
|
||||
abstract class Route extends DatabaseEntity {
|
||||
|
||||
const PARAMETER_PATTERN = "/^{([^:]+)(:(.*?)(\?)?)?}$/";
|
||||
const ROUTE_TYPES = [
|
||||
"redirect_temporary" => 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,
|
||||
];
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
9
Core/Objects/Router/RedirectPermanentlyRoute.class.php
Normal file
9
Core/Objects/Router/RedirectPermanentlyRoute.class.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Objects\Router;
|
||||
|
||||
class RedirectPermanentlyRoute extends RedirectRoute {
|
||||
public function __construct(string $pattern, bool $exact, string $destination) {
|
||||
parent::__construct("redirect_permanently", $pattern, $exact, $destination, 308);
|
||||
}
|
||||
}
|
@ -2,24 +2,29 @@
|
||||
|
||||
namespace Core\Objects\Router;
|
||||
|
||||
class RedirectRoute extends AbstractRoute {
|
||||
use Core\Objects\DatabaseEntity\Route;
|
||||
use JetBrains\PhpStorm\Pure;
|
||||
|
||||
class RedirectRoute extends Route {
|
||||
|
||||
private string $destination;
|
||||
private int $code;
|
||||
|
||||
public function __construct(string $pattern, bool $exact, string $destination, int $code = 307) {
|
||||
parent::__construct($pattern, $exact);
|
||||
$this->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]);
|
||||
}
|
||||
}
|
9
Core/Objects/Router/RedirectTemporaryRoute.class.php
Normal file
9
Core/Objects/Router/RedirectTemporaryRoute.class.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace Core\Objects\Router;
|
||||
|
||||
class RedirectTemporaryRoute extends RedirectRoute {
|
||||
public function __construct(string $pattern, bool $exact, string $destination) {
|
||||
parent::__construct("redirect_temporary", $pattern, $exact, $destination, 307);
|
||||
}
|
||||
}
|
@ -4,12 +4,13 @@ namespace Core\Objects\Router;
|
||||
|
||||
use Core\Driver\Logger\Logger;
|
||||
use Core\Objects\Context;
|
||||
use Core\Objects\DatabaseEntity\Route;
|
||||
|
||||
class Router {
|
||||
|
||||
private Context $context;
|
||||
private Logger $logger;
|
||||
private ?AbstractRoute $activeRoute;
|
||||
private ?Route $activeRoute;
|
||||
private ?string $requestedUri;
|
||||
protected array $routes;
|
||||
protected array $statusCodeRoutes;
|
||||
@ -31,7 +32,7 @@ class Router {
|
||||
}
|
||||
}
|
||||
|
||||
public function getActiveRoute(): ?AbstractRoute {
|
||||
public function getActiveRoute(): ?Route {
|
||||
return $this->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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user