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; | ||||
| 
 | ||||
| @ -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)); | ||||
|  | ||||
| @ -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
	 Roman Hergenreder
						Roman Hergenreder