diff --git a/Core/API/UserAPI.class.php b/Core/API/UserAPI.class.php index e2d2ea8..b221433 100644 --- a/Core/API/UserAPI.class.php +++ b/Core/API/UserAPI.class.php @@ -343,7 +343,6 @@ namespace Core\API\User { $this->result["session"] = $this->context->getSession()->jsonSerialize(); } - $sql = $this->context->getSQL(); $res = $sql->select("method", "groups") ->from("ApiPermission") diff --git a/Core/Driver/SQL/SQL.class.php b/Core/Driver/SQL/SQL.class.php index 1e9bc32..7659a52 100644 --- a/Core/Driver/SQL/SQL.class.php +++ b/Core/Driver/SQL/SQL.class.php @@ -150,6 +150,7 @@ abstract class SQL { } $logLevel = Logger::LOG_LEVEL_ERROR; + // $logLevel = Logger::LOG_LEVEL_DEBUG; if ($query instanceof Insert && $query->getTableName() === "SystemLog") { $logLevel = Logger::LOG_LEVEL_NONE; } diff --git a/Core/Localization/de_DE/general.php b/Core/Localization/de_DE/general.php index ed79026..3e1d575 100644 --- a/Core/Localization/de_DE/general.php +++ b/Core/Localization/de_DE/general.php @@ -15,6 +15,7 @@ return [ "request" => "Anfordern", "cancel" => "Abbrechen", "confirm" => "Bestätigen", + "add" => "Hinzufügen", "ok" => "OK", "language" => "Sprache", "loading" => "Laden", diff --git a/Core/Localization/en_US/general.php b/Core/Localization/en_US/general.php index e52d7e8..b24169e 100644 --- a/Core/Localization/en_US/general.php +++ b/Core/Localization/en_US/general.php @@ -27,6 +27,7 @@ return [ "request" => "Request", "cancel" => "Cancel", "confirm" => "Confirm", + "add" => "Add", "close" => "Close", "ok" => "OK", "remove" => "Remove", diff --git a/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php b/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php index 7459555..5d01378 100644 --- a/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php +++ b/Core/Objects/DatabaseEntity/Controller/DatabaseEntity.class.php @@ -3,6 +3,7 @@ namespace Core\Objects\DatabaseEntity\Controller; use ArrayAccess; +use Core\Driver\SQL\Condition\Compare; use Core\Driver\SQL\Condition\Condition; use Core\Driver\SQL\Expression\Count; use Core\Driver\SQL\SQL; @@ -134,25 +135,23 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable { }, $entities); } + // hooks public function preInsert(array &$row) { } public function postFetch(SQL $sql, array $row) { } public function postUpdate() { } public static function getPredefinedValues(): array { return []; } public function postDelete() { } - public static function fromRow(SQL $sql, array $row): static { - $handler = self::getHandler($sql); - return $handler->entityFromRow($row); - } - - public static function newInstance(\ReflectionClass $reflectionClass) { + public static function newInstance(\ReflectionClass $reflectionClass, array $row) { return $reflectionClass->newInstanceWithoutConstructor(); } public static function find(SQL $sql, int $id, bool $fetchEntities = false, bool $fetchRecursive = false): static|bool|null { $handler = self::getHandler($sql); if ($fetchEntities) { + $context = new DatabaseEntityQueryContext(); return DatabaseEntityQuery::fetchOne(self::getHandler($sql)) + ->withContext($context) ->whereEq($handler->getTableName() . ".id", $id) ->fetchEntities($fetchRecursive) ->execute(); @@ -162,13 +161,8 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable { } public static function exists(SQL $sql, int $id): bool { - $handler = self::getHandler($sql); - $res = $sql->select(new Count()) - ->from($handler->getTableName()) - ->whereEq($handler->getTableName() . ".id", $id) - ->execute(); - - return $res !== false && $res[0]["count"] !== 0; + $count = self::count($sql, new Compare("id", $id)); + return $count !== false && $count !== 0; } public static function findBy(DatabaseEntityQuery $dbQuery): static|array|bool|null { @@ -176,15 +170,22 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable { } public static function findAll(SQL $sql, ?Condition $condition = null): ?array { - $handler = self::getHandler($sql); - return $handler->fetchMultiple($condition); + + $query = self::createBuilder($sql, false); + if ($condition) { + $query->where($condition); + } + + return $query->execute(); } public static function createBuilder(SQL $sql, bool $one): DatabaseEntityQuery { + $context = new DatabaseEntityQueryContext(); + if ($one) { - return DatabaseEntityQuery::fetchOne(self::getHandler($sql)); + return DatabaseEntityQuery::fetchOne(self::getHandler($sql))->withContext($context); } else { - return DatabaseEntityQuery::fetchAll(self::getHandler($sql)); + return DatabaseEntityQuery::fetchAll(self::getHandler($sql))->withContext($context); } } diff --git a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php index 9f31b92..29b061d 100644 --- a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php +++ b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityHandler.php @@ -12,7 +12,6 @@ use Core\Driver\SQL\Column\IntColumn; use Core\Driver\SQL\Column\JsonColumn; use Core\Driver\SQL\Column\StringColumn; use Core\Driver\SQL\Condition\CondIn; -use Core\Driver\SQL\Condition\Condition; use Core\Driver\SQL\Column\DoubleColumn; use Core\Driver\SQL\Column\FloatColumn; use Core\Driver\SQL\Condition\CondNot; @@ -329,7 +328,9 @@ class DatabaseEntityHandler implements Persistable { return $rel_row; } - private function getValueFromRow(array $row, string $propertyName, mixed &$value, int $fetchEntities = DatabaseEntityQuery::FETCH_NONE): bool { + private function getValueFromRow(array $row, string $propertyName, mixed &$value, + int $fetchEntities = DatabaseEntityQuery::FETCH_NONE, + ?DatabaseEntityQueryContext $context = null): bool { $column = $this->columns[$propertyName] ?? null; if (!$column) { return false; @@ -355,12 +356,42 @@ class DatabaseEntityHandler implements Persistable { if ($relId !== null) { if ($fetchEntities !== DatabaseEntityQuery::FETCH_NONE) { if ($this === $relationHandler) { - $value = DatabaseEntityQuery::fetchOne($this) + + if ($context) { + $value = $context->queryCache($this, $relId); + if ($value) { + return true; + } + } + + $subQuery = DatabaseEntityQuery::fetchOne($this) ->fetchEntities($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE) - ->whereEq($this->getColumnName("id"), $relId) - ->execute(); + ->whereEq($this->getColumnName("id"), $relId); + + if ($context) { + $subQuery->withContext($context); + } + + $value = $subQuery->execute(); + if ($value !== null && $context !== null) { + $context->addCache($this, $value); + } } else { - $value = $relationHandler->entityFromRow(self::getPrefixedRow($row, $relColumnPrefix), [], true); + $prefixedRow = self::getPrefixedRow($row, $relColumnPrefix); + + if ($context) { + $value = $context->queryCache($relationHandler, $prefixedRow["id"]); + if ($value) { + return true; + } + } + + $subFetchMode = $fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE ? $fetchEntities : DatabaseEntityQuery::FETCH_NONE; + $value = $relationHandler->entityFromRow($prefixedRow, [], $subFetchMode, $context); + + if ($context && $value) { + $context->addCache($relationHandler, $value); + } } } else { return false; @@ -385,7 +416,9 @@ class DatabaseEntityHandler implements Persistable { return true; } - public function entityFromRow(array $row, array $additionalColumns = [], int $fetchEntities = DatabaseEntityQuery::FETCH_NONE): ?DatabaseEntity { + public function entityFromRow(array $row, array $additionalColumns = [], + int $fetchEntities = DatabaseEntityQuery::FETCH_NONE, + ?DatabaseEntityQueryContext $context = null): ?DatabaseEntity { try { $constructorClass = $this->entityClass; @@ -397,7 +430,7 @@ class DatabaseEntityHandler implements Persistable { } } - $entity = call_user_func($constructorClass->getName() . "::newInstance", $constructorClass); + $entity = call_user_func($constructorClass->getName() . "::newInstance", $constructorClass, $row); if (!($entity instanceof DatabaseEntity)) { $this->logger->error("Created Object is not of type DatabaseEntity"); return null; @@ -405,7 +438,7 @@ class DatabaseEntityHandler implements Persistable { foreach ($this->properties as $property) { $propertyName = $property->getName(); - if ($this->getValueFromRow($row, $propertyName, $value, $fetchEntities)) { + if ($this->getValueFromRow($row, $propertyName, $value, $fetchEntities, $context)) { $property->setAccessible(true); $property->setValue($entity, $value); } @@ -542,112 +575,122 @@ class DatabaseEntityHandler implements Persistable { return $success; } - public function fetchNMRelations(array $entities, int $fetchEntities = DatabaseEntityQuery::FETCH_DIRECT) { + public function fetchNMRelations(array $entities, int $fetchEntities = DatabaseEntityQuery::FETCH_DIRECT, + ?DatabaseEntityQueryContext $context = null) { if ($fetchEntities === DatabaseEntityQuery::FETCH_NONE) { return; } + $entityIds = array_keys($entities); + + // N:M + if (!empty($this->nmRelations) && !empty($entityIds)) { + + foreach ($this->nmRelations as $nmProperty => $nmRelation) { + $nmTable = $nmRelation->getTableName(); + $property = $this->properties[$nmProperty]; + $property->setAccessible(true); + + if ($nmRelation instanceof NMRelation) { + $thisIdColumn = $nmRelation->getIdColumn($this); + $otherHandler = $nmRelation->getOtherHandler($this); + $refIdColumn = $nmRelation->getIdColumn($otherHandler); + $refTableName = $otherHandler->getTableName(); + + $relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler) + ->addJoin(new InnerJoin($nmTable, "$nmTable.$refIdColumn", "$refTableName.id")) + ->addSelectValue(new Column($thisIdColumn)) + ->where(new CondIn(new Column($thisIdColumn), $entityIds)); + + $relEntityQuery->fetchEntities($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE); + $rows = $relEntityQuery->executeSQL(); + if (!is_array($rows)) { + $this->logger->error("Error fetching n:m relations from table: '$nmTable': " . $this->sql->getLastError()); + return; + } + + $relEntities = []; + foreach ($rows as $row) { + $relEntity = $context?->queryCache($otherHandler, $row["id"]); + if (!$relEntity) { + $relEntity = $otherHandler->entityFromRow($row, [], $fetchEntities, $context); + $context?->addCache($otherHandler, $relEntity); + $relEntities[$relEntity->getId()] = $relEntity; + } + + $thisEntity = $entities[$row[$thisIdColumn]]; + $targetArray = $property->getValue($thisEntity); + $targetArray[$relEntity->getId()] = $relEntity; + $property->setValue($thisEntity, $targetArray); + } + + if ($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE) { + $otherHandler->fetchNMRelations($relEntities, $fetchEntities, $context); + } + } else if ($nmRelation instanceof NMRelationReference) { + + $otherHandler = $nmRelation->getRelHandler(); + $thisIdColumn = $otherHandler->getColumnName($nmRelation->getThisProperty(), false); + $relIdColumn = $otherHandler->getColumnName($nmRelation->getRefProperty(), false); + + $relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler) + ->where(new CondIn(new Column($thisIdColumn), $entityIds)); + + $relEntityQuery->fetchEntities($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE); + $rows = $relEntityQuery->executeSQL(); + if (!is_array($rows)) { + $this->logger->error("Error fetching n:m relations from table: '$nmTable': " . $this->sql->getLastError()); + return; + } + + $thisIdProperty = $otherHandler->properties[$nmRelation->getThisProperty()]; + $thisIdProperty->setAccessible(true); + + $relEntities = []; + foreach ($rows as $row) { + $relEntity = $context?->queryCache($otherHandler, $row["id"]); + if (!$relEntity) { + $relEntity = $otherHandler->entityFromRow($row, [], $fetchEntities, $context); + $context?->addCache($otherHandler, $relEntity); + $relEntities[$relEntity->getId()] = $relEntity; + } + + $thisEntity = $entities[$row[$thisIdColumn]]; + $thisIdProperty->setValue($relEntity, $thisEntity); + $targetArray = $property->getValue($thisEntity); + $targetArray[$row[$relIdColumn]] = $relEntity; + $property->setValue($thisEntity, $targetArray); + } + + if ($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE) { + $otherHandler->fetchNMRelations($relEntities, $fetchEntities, $context); + } + } else { + $this->logger->error("fetchNMRelations for type '" . get_class($nmRelation) . "' is not implemented"); + } + } + } + + // if fetch mode is recursive, fetch all relations of our fetched relations as well... if ($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE) { foreach ($entities as $entity) { foreach ($this->relations as $propertyName => $relHandler) { $property = $this->properties[$propertyName]; if ($property->isInitialized($entity)) { - $relEntity = $this->properties[$propertyName]->getValue($entity); + $relEntity = $property->getValue($entity); if ($relEntity) { - $relHandler->fetchNMRelations([$relEntity->getId() => $relEntity], true); + $relEntityId = $relEntity->getId(); // $fetchedEntities + $relTableName = $relHandler->getTableName(); + + if (!isset($fetchedEntities[$relTableName]) || !isset($fetchedEntities[$relTableName][$relEntityId])) { + $relHandler->fetchNMRelations([$relEntityId => $relEntity], DatabaseEntityQuery::FETCH_RECURSIVE, $context); + } } } } } } - - if (empty($this->nmRelations)) { - return; - } - - $entityIds = array_keys($entities); - if (empty($entityIds)) { - return; - } - - foreach ($this->nmRelations as $nmProperty => $nmRelation) { - $nmTable = $nmRelation->getTableName(); - $property = $this->properties[$nmProperty]; - $property->setAccessible(true); - - if ($nmRelation instanceof NMRelation) { - $thisIdColumn = $nmRelation->getIdColumn($this); - $otherHandler = $nmRelation->getOtherHandler($this); - $refIdColumn = $nmRelation->getIdColumn($otherHandler); - $refTableName = $otherHandler->getTableName(); - - $relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler) - ->addJoin(new InnerJoin($nmTable, "$nmTable.$refIdColumn", "$refTableName.id")) - ->addSelectValue(new Column($thisIdColumn)) - ->where(new CondIn(new Column($thisIdColumn), $entityIds)); - - $relEntityQuery->fetchEntities($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE); - $rows = $relEntityQuery->executeSQL(); - if (!is_array($rows)) { - $this->logger->error("Error fetching n:m relations from table: '$nmTable': " . $this->sql->getLastError()); - return; - } - - $relEntities = []; - foreach ($rows as $row) { - $relId = $row["id"]; - if (!isset($relEntities[$relId])) { - $relEntity = $otherHandler->entityFromRow($row, [], $fetchEntities); - $relEntities[$relId] = $relEntity; - } - - $thisEntity = $entities[$row[$thisIdColumn]]; - $relEntity = $relEntities[$relId]; - - $targetArray = $property->getValue($thisEntity); - $targetArray[$relEntity->getId()] = $relEntity; - $property->setValue($thisEntity, $targetArray); - } - } else if ($nmRelation instanceof NMRelationReference) { - - $otherHandler = $nmRelation->getRelHandler(); - $thisIdColumn = $otherHandler->getColumnName($nmRelation->getThisProperty(), false); - $relIdColumn = $otherHandler->getColumnName($nmRelation->getRefProperty(), false); - $relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler) - ->where(new CondIn(new Column($thisIdColumn), $entityIds)); - - $relEntityQuery->fetchEntities($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE); - $rows = $relEntityQuery->executeSQL(); - if (!is_array($rows)) { - $this->logger->error("Error fetching n:m relations from table: '$nmTable': " . $this->sql->getLastError()); - return; - } - - $thisIdProperty = $otherHandler->properties[$nmRelation->getThisProperty()]; - $thisIdProperty->setAccessible(true); - - foreach ($rows as $row) { - $relEntity = $otherHandler->entityFromRow($row, [], $fetchEntities); - $thisEntity = $entities[$row[$thisIdColumn]]; - $thisIdProperty->setValue($relEntity, $thisEntity); - $targetArray = $property->getValue($thisEntity); - $targetArray[$row[$relIdColumn]] = $relEntity; - $property->setValue($thisEntity, $targetArray); - } - } else { - $this->logger->error("fetchNMRelations for type '" . get_class($nmRelation) . "' is not implemented"); - continue; - } - - // TODO whats that here lol - if ($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE) { - foreach ($entities as $entity) { - $relEntities = $property->getValue($entity); - // $otherHandler->fetchNMRelations($relEntities, $fetchEntities); - } - } - } } public function getSelectQuery(): Select { @@ -663,38 +706,11 @@ class DatabaseEntityHandler implements Persistable { if ($res !== false && $res !== null) { $res = $this->entityFromRow($res); - if ($res instanceof DatabaseEntity) { - $this->fetchNMRelations([$res->getId() => $res]); - } } return $res; } - public function fetchMultiple(?Condition $condition = null): ?array { - $query = $this->getSelectQuery(); - - if ($condition) { - $query->where($condition); - } - - $res = $query->execute(); - if ($res === false) { - return null; - } else { - $entities = []; - foreach ($res as $row) { - $entity = $this->entityFromRow($row); - if ($entity) { - $entities[$entity->getId()] = $entity; - } - } - - $this->fetchNMRelations($entities); - return $entities; - } - } - public function getCreateQueries(SQL $sql): array { $queries = []; diff --git a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php index f8b50ec..e6e27be 100644 --- a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php +++ b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQuery.class.php @@ -7,7 +7,6 @@ use Core\Driver\SQL\Column\Column; use Core\Driver\SQL\Expression\Alias; use Core\Driver\SQL\Query\Select; use Core\Driver\SQL\SQL; -use Core\External\PHPMailer\Exception; /** * this class is similar to \Driver\SQL\Query\Select but with reduced functionality @@ -26,6 +25,7 @@ class DatabaseEntityQuery extends Select { private array $additionalColumns; private int $fetchSubEntities; + private ?DatabaseEntityQueryContext $context; private function __construct(DatabaseEntityHandler $handler, int $resultType) { parent::__construct($handler->getSQL(), ...$handler->getColumnNames()); @@ -33,6 +33,7 @@ class DatabaseEntityQuery extends Select { $this->logger = new Logger("DB-EntityQuery", $handler->getSQL()); $this->resultType = $resultType; $this->logVerbose = false; + $this->context = null; $this->additionalColumns = []; $this->from($handler->getTableName()); @@ -53,6 +54,11 @@ class DatabaseEntityQuery extends Select { return $this; } + public function withContext(DatabaseEntityQueryContext $context): self { + $this->context = $context; + return $this; + } + public function addCustomValue(mixed $selectValue): DatabaseEntityQuery { if (is_string($selectValue)) { $this->additionalColumns[] = $selectValue; @@ -89,25 +95,24 @@ class DatabaseEntityQuery extends Select { // TODO: clean this up public function fetchEntities(bool $recursive = false): DatabaseEntityQuery { - // $this->selectQuery->dump(); $this->fetchSubEntities = ($recursive ? self::FETCH_RECURSIVE : self::FETCH_DIRECT); - - $relIndex = 1; + $visited = [$this->handler->getTableName()]; foreach ($this->handler->getRelations() as $propertyName => $relationHandler) { - if ($this->handler !== $relationHandler || !$recursive) { - $this->fetchRelation($propertyName, $this->handler->getTableName(), $this->handler, $relationHandler, $relIndex, $recursive); - } + $this->fetchRelation($propertyName, $this->handler->getTableName(), $this->handler, $relationHandler, + $recursive, "", $visited); } return $this; } private function fetchRelation(string $propertyName, string $tableName, DatabaseEntityHandler $src, DatabaseEntityHandler $relationHandler, - int &$relIndex = 1, bool $recursive = false, string $relationColumnPrefix = "") { + bool $recursive = false, string $relationColumnPrefix = "", array &$visited = []) { - // TODO: fix recursion here... - if ($src === $relationHandler && $recursive) { + $relIndex = count($visited); + if (in_array($relationHandler->getTableName(), $visited)) { return; + } else { + $visited[] = $relationHandler->getTableName(); } $columns = $src->getColumns(); @@ -117,7 +122,6 @@ class DatabaseEntityQuery extends Select { $referencedTable = $relationHandler->getTableName(); $isNullable = !$foreignColumn->notNull(); $alias = "t$relIndex"; // t1, t2, t3, ... - $relIndex++; if ($isNullable) { $this->leftJoin($referencedTable, "$tableName.$foreignColumnName", "$alias.id", $alias); @@ -132,7 +136,8 @@ class DatabaseEntityQuery extends Select { if (!isset($recursiveRelations[$relPropertyName]) || $recursive) { $this->addValue("$alias.$relColumnName as $relationColumnPrefix$relColumnName"); if (isset($recursiveRelations[$relPropertyName]) && $recursive) { - $this->fetchRelation($relPropertyName, $alias, $relationHandler, $recursiveRelations[$relPropertyName], $relIndex, $recursive, $relationColumnPrefix); + $this->fetchRelation($relPropertyName, $alias, $relationHandler, $recursiveRelations[$relPropertyName], + $recursive, $relationColumnPrefix, $visited); } } } @@ -153,22 +158,51 @@ class DatabaseEntityQuery extends Select { if ($this->resultType === SQL::FETCH_ALL) { $entities = []; + $entitiesNM = []; + foreach ($res as $row) { - $entity = $this->handler->entityFromRow($row, $this->additionalColumns, $this->fetchSubEntities); + + $cached = false; + $entity = null; + + if ($this->context) { + $entity = $this->context->queryCache($this->handler, $row["id"]); + $cached = $entity !== null; + } + + if (!$cached) { + $entity = $this->handler->entityFromRow($row, $this->additionalColumns, $this->fetchSubEntities, $this->context); + $this->context?->addCache($this->handler, $entity); + $entitiesNM[$entity->getId()] = $entity; + } + if ($entity) { $entities[$entity->getId()] = $entity; } } - if ($this->fetchSubEntities !== self::FETCH_NONE) { - $this->handler->fetchNMRelations($entities, $this->fetchSubEntities); + if (!empty($entitiesNM) && $this->fetchSubEntities !== self::FETCH_NONE) { + $this->handler->fetchNMRelations($entitiesNM, $this->fetchSubEntities, $this->context); } return $entities; } else if ($this->resultType === SQL::FETCH_ONE) { - $entity = $this->handler->entityFromRow($res, $this->additionalColumns, $this->fetchSubEntities); - if ($entity instanceof DatabaseEntity && $this->fetchSubEntities !== self::FETCH_NONE) { - $this->handler->fetchNMRelations([$entity->getId() => $entity], $this->fetchSubEntities); + + $cached = false; + $entity = null; + + if ($this->context) { + $entity = $this->context->queryCache($this->handler, $res["id"]); + $cached = $entity !== null; + } + + if (!$cached) { + $entity = $this->handler->entityFromRow($res, $this->additionalColumns, $this->fetchSubEntities, $this->context); + if ($entity instanceof DatabaseEntity && $this->fetchSubEntities !== self::FETCH_NONE) { + $this->handler->fetchNMRelations([$entity->getId() => $entity], $this->fetchSubEntities, $this->context); + } + + $this->context?->addCache($this->handler, $entity); } return $entity; diff --git a/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQueryContext.class.php b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQueryContext.class.php new file mode 100644 index 0000000..8814dbf --- /dev/null +++ b/Core/Objects/DatabaseEntity/Controller/DatabaseEntityQueryContext.class.php @@ -0,0 +1,31 @@ + [ entityId => entity ] + private array $entityCache; + + public function __construct() { + $this->entityCache = []; + } + + public function queryCache(DatabaseEntityHandler $handler, int $id): ?DatabaseEntity { + $tableName = $handler->getTableName(); + if (isset($this->entityCache[$tableName])) { + return $this->entityCache[$tableName][$id] ?? null; + } + + return null; + } + + public function addCache(DatabaseEntityHandler $handler, DatabaseEntity $entity): void { + $tableName = $handler->getTableName(); + if (!isset($this->entityCache[$tableName])) { + $this->entityCache[$tableName] = []; + } + + $this->entityCache[$tableName][$entity->getId()] = $entity; + } +} \ No newline at end of file diff --git a/Core/Objects/DatabaseEntity/TwoFactorToken.class.php b/Core/Objects/DatabaseEntity/TwoFactorToken.class.php index e0e8c47..6ec3649 100644 --- a/Core/Objects/DatabaseEntity/TwoFactorToken.class.php +++ b/Core/Objects/DatabaseEntity/TwoFactorToken.class.php @@ -81,4 +81,15 @@ abstract class TwoFactorToken extends DatabaseEntity { return $jsonData; } + + public static function newInstance(\ReflectionClass $reflectionClass, array $row) { + $type = $row["type"] ?? null; + if ($type === "totp") { + return (new \ReflectionClass(TimeBasedTwoFactorToken::class))->newInstanceWithoutConstructor(); + } else if ($type === "fido") { + return (new \ReflectionClass(KeyBasedTwoFactorToken::class))->newInstanceWithoutConstructor(); + } else { + return parent::newInstance($reflectionClass, $row); + } + } } \ No newline at end of file diff --git a/react/shared/elements/data-table.js b/react/shared/elements/data-table.js index 59673ef..013036d 100644 --- a/react/shared/elements/data-table.js +++ b/react/shared/elements/data-table.js @@ -5,7 +5,7 @@ import React, {useCallback, useContext, useEffect, useState} from "react"; import "./data-table.css"; import {LocaleContext} from "../locale"; import clsx from "clsx"; -import {Box, IconButton} from "@mui/material"; +import {Box, IconButton, TextField} from "@mui/material"; import {formatDate, formatDateTime} from "../util"; import CachedIcon from "@material-ui/icons/Cached"; @@ -72,6 +72,8 @@ export function DataTable(props) { for (const [index, column] of columns.entries()) { if (!(column instanceof DataColumn)) { throw new Error("DataTable can only have DataColumn-objects as column definition, got: " + typeof column); + } else if (column.hidden) { + continue; } if (sortable && column.sortable) { @@ -147,6 +149,7 @@ export class DataColumn { this.field = field; this.sortable = !params.hasOwnProperty("sortable") || !!params.sortable; this.align = params.align || "left"; + this.hidden = !!params.hidden; this.params = params; } @@ -167,8 +170,14 @@ export class StringColumn extends DataColumn { renderData(L, entry, index) { let data = super.renderData(L, entry, index); + if (this.params.maxLength && data?.length && data.length > this.params.maxLength) { + data = data.substring(0, this.params.maxLength) + "..."; + } + if (this.params.style) { - data = {data} + let style = (typeof this.params.style === 'function' + ? this.params.style(entry) : this.params.style); + data = {data} } return data; @@ -241,6 +250,25 @@ export class BoolColumn extends DataColumn { } } +export class InputColumn extends DataColumn { + constructor(label, field, type, onChange, params = {}) { + super(label, field, { ...params, sortable: false }); + this.type = type; + this.onChange = onChange; + this.props = params.props || {}; + } + + renderData(L, entry, index) { + let value = super.renderData(L, entry, index); + if (this.type === 'text') { + return this.onChange(entry, index, e.target.value)} /> + } + + return <>[Invalid type: {this.type}] + } +} + export class ControlsColumn extends DataColumn { constructor(label, buttons = [], params = {}) { super(label, null, { align: "center", ...params, sortable: false }); diff --git a/react/shared/elements/dialog.jsx b/react/shared/elements/dialog.jsx index 4144210..d20db35 100644 --- a/react/shared/elements/dialog.jsx +++ b/react/shared/elements/dialog.jsx @@ -7,7 +7,7 @@ import { DialogContent, DialogContentText, DialogTitle, - Input, TextField + Input, List, ListItem, TextField } from "@mui/material"; export default function Dialog(props) { @@ -57,6 +57,18 @@ export default function Dialog(props) { onChange={e => setInputData({ ...inputData, [input.name]: e.target.value })} />) break; + case 'list': + delete inputProps.items; + let listItems = input.items.map((item, index) => {item}); + inputElements.push( + + {listItems} + + ); } }