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}
+
+ );
}
}