frontend, localization, bugfix
This commit is contained in:
parent
0418118841
commit
1d6ff17994
@ -160,6 +160,9 @@ class Parameter {
|
||||
if ($value instanceof DateTime) {
|
||||
$this->value = $value;
|
||||
$valid = true;
|
||||
} else if (is_int($value) || (is_string($value) && preg_match("/^\d+$/", $value))) {
|
||||
$this->value = (new \DateTime())->setTimestamp(intval($value));
|
||||
$valid = true;
|
||||
} else {
|
||||
$format = $this->getFormat();
|
||||
$d = DateTime::createFromFormat($format, $value);
|
||||
|
@ -13,6 +13,10 @@ class StringType extends Parameter {
|
||||
}
|
||||
|
||||
public function parseParam($value): bool {
|
||||
if (!parent::parseParam($value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_string($value)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ abstract class Request {
|
||||
|
||||
if ($this->externalCall) {
|
||||
$values = $_REQUEST;
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_SERVER["CONTENT_TYPE"]) && in_array("application/json", explode(";", $_SERVER["CONTENT_TYPE"]))) {
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && in_array("application/json", explode(";", $_SERVER["CONTENT_TYPE"] ?? ""))) {
|
||||
$jsonData = json_decode(file_get_contents('php://input'), true);
|
||||
if ($jsonData !== null) {
|
||||
$values = array_merge($values, $jsonData);
|
||||
@ -432,4 +432,42 @@ abstract class Request {
|
||||
return $this->createError("Error processing image: " . $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
protected function getFileUpload(string $name, bool $allowMultiple = false, ?array $extensions = null): false|array {
|
||||
if (!isset($_FILES[$name]) || (is_array($_FILES[$name]["name"]) && empty($_FILES[$name]["name"])) || empty($_FILES[$name]["name"])) {
|
||||
return $this->createError("Missing form-field '$name'");
|
||||
}
|
||||
|
||||
$files = [];
|
||||
if (is_array($_FILES[$name]["name"])) {
|
||||
$numFiles = count($_FILES[$name]["name"]);
|
||||
if (!$allowMultiple && $numFiles > 1) {
|
||||
return $this->createError("Only one file allowed for form-field '$name'");
|
||||
} else {
|
||||
for ($i = 0; $i < $numFiles; $i++) {
|
||||
$fileName = $_FILES[$name]["name"][$i];
|
||||
$filePath = $_FILES[$name]["tmp_name"][$i];
|
||||
$files[$fileName] = $filePath;
|
||||
|
||||
if (!empty($extensions) && !in_array(pathinfo($fileName, PATHINFO_EXTENSION), $extensions)) {
|
||||
return $this->createError("File '$fileName' has forbidden extension, allowed: " . implode(",", $extensions));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$fileName = $_FILES[$name]["name"];
|
||||
$filePath = $_FILES[$name]["tmp_name"];
|
||||
$files[$fileName] = $filePath;
|
||||
if (!empty($extensions) && !in_array(pathinfo($fileName, PATHINFO_EXTENSION), $extensions)) {
|
||||
return $this->createError("File '$fileName' has forbidden extension, allowed: " . implode(",", $extensions));
|
||||
}
|
||||
}
|
||||
|
||||
if ($allowMultiple) {
|
||||
return $files;
|
||||
} else {
|
||||
$fileName = key($files);
|
||||
return [$fileName, $files[$fileName]];
|
||||
}
|
||||
}
|
||||
}
|
@ -460,6 +460,7 @@ class MySQL extends SQL {
|
||||
return $query;
|
||||
}
|
||||
|
||||
// FIXME: access mysql database instead of configured one
|
||||
public function tableExists(string $tableName): bool {
|
||||
$tableSchema = $this->connectionData->getProperty("database");
|
||||
$res = $this->select(new Count())
|
||||
|
@ -16,20 +16,35 @@ use Twig\Loader\FilesystemLoader;
|
||||
|
||||
class TemplateDocument extends Document {
|
||||
|
||||
const TEMPLATE_PATH = WEBROOT . '/Core/Templates';
|
||||
|
||||
private string $templateName;
|
||||
protected array $parameters;
|
||||
private Environment $twigEnvironment;
|
||||
private FilesystemLoader $twigLoader;
|
||||
protected string $title;
|
||||
|
||||
private static function getTemplatePaths(): array {
|
||||
return [
|
||||
implode(DIRECTORY_SEPARATOR, [WEBROOT, 'Site', 'Templates']),
|
||||
implode(DIRECTORY_SEPARATOR, [WEBROOT, 'Core', 'Templates']),
|
||||
];
|
||||
}
|
||||
|
||||
private static function getTemplatePath(string $templateName): ?string {
|
||||
foreach (self::getTemplatePaths() as $path) {
|
||||
$filePath = implode(DIRECTORY_SEPARATOR, [$path, $templateName]);
|
||||
if (is_file($filePath)) {
|
||||
return $filePath;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function __construct(Router $router, string $templateName, array $params = []) {
|
||||
parent::__construct($router);
|
||||
$this->title = "Untitled Document";
|
||||
$this->templateName = $templateName;
|
||||
$this->parameters = $params;
|
||||
$this->twigLoader = new FilesystemLoader(self::TEMPLATE_PATH);
|
||||
$this->twigLoader = new FilesystemLoader(self::getTemplatePaths());
|
||||
$this->twigEnvironment = new Environment($this->twigLoader, [
|
||||
'cache' => WEBROOT . '/Site/Cache/Templates/',
|
||||
'auto_reload' => true
|
||||
@ -84,7 +99,7 @@ class TemplateDocument extends Document {
|
||||
"query" => $urlParts["query"] ?? "",
|
||||
"fragment" => $urlParts["fragment"] ?? ""
|
||||
],
|
||||
"lastModified" => date(L('Y-m-d H:i:s'), @filemtime(implode(DIRECTORY_SEPARATOR, [self::TEMPLATE_PATH, $name]))),
|
||||
"lastModified" => date(L('Y-m-d H:i:s'), @filemtime(self::getTemplatePath($name))),
|
||||
"registrationEnabled" => $settings->isRegistrationAllowed(),
|
||||
"title" => $this->title,
|
||||
"recaptcha" => [
|
||||
|
@ -4,15 +4,60 @@ return [
|
||||
"something_went_wrong" => "Etwas ist schief gelaufen",
|
||||
"error_occurred" => "Ein Fehler ist aufgetreten",
|
||||
"retry" => "Erneut versuchen",
|
||||
"Go back" => "Zurück",
|
||||
"go_back" => "Zurück",
|
||||
"save" => "Speichern",
|
||||
"saving" => "Speichere",
|
||||
"unsaved_changes" => "Du hast nicht gespeicherte Änderungen",
|
||||
"new" => "Neu",
|
||||
"edit" => "Bearbeiten",
|
||||
"submitting" => "Übermittle",
|
||||
"submit" => "Absenden",
|
||||
"request" => "Anfordern",
|
||||
"cancel" => "Abbrechen",
|
||||
"confirm" => "Bestätigen",
|
||||
"language" => "Sprache",
|
||||
"loading" => "Laden",
|
||||
"logout" => "Ausloggen",
|
||||
"noscript" => "Sie müssen Javascript aktivieren um diese Anwendung zu benutzen",
|
||||
"name" => "Name",
|
||||
"type" => "Typ",
|
||||
"size" => "Größe",
|
||||
"last_modified" => "Zuletzt geändert",
|
||||
|
||||
# dialog / actions
|
||||
"action" => "Aktion",
|
||||
"title" => "Titel",
|
||||
"message" => "Nachricht",
|
||||
"rename" => "Umbenennen",
|
||||
"move" => "Verschieben",
|
||||
"delete" => "Löschen",
|
||||
"info" => "Info",
|
||||
"download" => "Herunterladen",
|
||||
"download_all" => "Alles Herunterladen",
|
||||
"upload" => "Hochladen",
|
||||
"uploading" => "Lade hoch",
|
||||
"overwrite" => "Überschreiben",
|
||||
"reload" => "Aktualisieren",
|
||||
|
||||
# data table
|
||||
"showing_x_of_y_entries" => "Zeige %d von %d Einträgen",
|
||||
"controls" => "Steuerung",
|
||||
|
||||
# date / time
|
||||
"date" => "Datum",
|
||||
"start_date" => "Startdatum",
|
||||
"end_date" => "Enddatum",
|
||||
"date_format" => "d.m.Y",
|
||||
"date_time_format" => "d.m.Y H:i",
|
||||
"date_time_format_precise" => "d.m.Y H:i:s",
|
||||
"time_format" => "H:i",
|
||||
"time_format_precise" => "H:i:s",
|
||||
"datefns_date_format" => "dd.MM.yyyy",
|
||||
"datefns_time_format" => "HH:mm",
|
||||
"datefns_time_format_precise" => "HH:mm:ss",
|
||||
"datefns_datetime_format" => "dd.MM.yyyy HH:mm",
|
||||
"datefns_datetime_format_precise" => "dd.MM.yyyy HH:mm:ss",
|
||||
|
||||
# API
|
||||
"no_api_key_registered" => "Kein gültiger API-Schlüssel registriert",
|
||||
];
|
@ -3,16 +3,61 @@
|
||||
return [
|
||||
"something_went_wrong" => "Something went wrong",
|
||||
"error_occurred" => "An error occurred",
|
||||
"retry" => "Retry",
|
||||
"go_back" => "Go Back",
|
||||
"submitting" => "Submitting",
|
||||
"submit" => "Submit",
|
||||
"request" => "Request",
|
||||
"unsaved_changes" => "You have unsaved changed",
|
||||
"new" => "New",
|
||||
"language" => "Language",
|
||||
"loading" => "Loading",
|
||||
"logout" => "Logout",
|
||||
"noscript" => "You need Javascript enabled to run this app",
|
||||
"name" => "Name",
|
||||
"type" => "Type",
|
||||
"size" => "Size",
|
||||
"last_modified" => "Last Modified",
|
||||
|
||||
# dialog / actions
|
||||
"action" => "Action",
|
||||
"title" => "Title",
|
||||
"message" => "Message",
|
||||
"edit" => "Edit",
|
||||
"submitting" => "Submitting",
|
||||
"submit" => "Submit",
|
||||
"request" => "Request",
|
||||
"cancel" => "Cancel",
|
||||
"confirm" => "Confirm",
|
||||
"retry" => "Retry",
|
||||
"go_back" => "Go Back",
|
||||
"save" => "Save",
|
||||
"saving" => "Saving",
|
||||
"delete" => "Delete",
|
||||
"info" => "Info",
|
||||
"download" => "Download",
|
||||
"download_all" => "Download All",
|
||||
"upload" => "Upload",
|
||||
"uploading" => "Uploading",
|
||||
"rename" => "Rename",
|
||||
"move" => "Move",
|
||||
"overwrite" => "Overwrite",
|
||||
"reload" => "Reload",
|
||||
|
||||
# data table
|
||||
"showing_x_of_y_entries" => "Showing %d of %d entries",
|
||||
"controls" => "Controls",
|
||||
|
||||
# date / time
|
||||
"date" => "Date",
|
||||
"start_date" => "Start Date",
|
||||
"end_date" => "End Date",
|
||||
"date_format" => "m/d/Y",
|
||||
"date_time_format" => "m/d/Y G:i A",
|
||||
"date_time_format_precise" => "m/d/Y G:i:s A",
|
||||
"time_format" => "G:i A",
|
||||
"time_format_precise" => "G:i:s A",
|
||||
"datefns_date_format" => "MM/dd/yyyy",
|
||||
"datefns_time_format" => "p",
|
||||
"datefns_time_format_precise" => "pp",
|
||||
"datefns_datetime_format" => "MM/dd/yyyy p",
|
||||
"datefns_datetime_format_precise" => "MM/dd/yyyy pp",
|
||||
|
||||
# API
|
||||
"no_api_key_registered" => "No valid API-Key registered",
|
||||
];
|
@ -55,18 +55,20 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable {
|
||||
}
|
||||
|
||||
public function jsonSerialize(?array $propertyNames = null): array {
|
||||
$properties = (new \ReflectionClass(get_called_class()))->getProperties();
|
||||
$reflectionClass = (new \ReflectionClass(get_called_class()));
|
||||
$properties = $reflectionClass->getProperties();
|
||||
$ignoredProperties = ["entityLogConfig", "customData"];
|
||||
|
||||
$jsonArray = [];
|
||||
foreach ($properties as $property) {
|
||||
$property->setAccessible(true);
|
||||
$propertyName = $property->getName();
|
||||
|
||||
if (in_array($propertyName, $ignoredProperties)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($property->getAttributes(Transient::class))) {
|
||||
if (DatabaseEntityHandler::getAttribute($property, Transient::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -93,6 +95,10 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable {
|
||||
$value = $value->getTimestamp();
|
||||
} else if ($value instanceof DatabaseEntity) {
|
||||
$subPropertyNames = $propertyNames[$propertyName] ?? null;
|
||||
if ($subPropertyNames === null && $value instanceof $this) {
|
||||
$subPropertyNames = $propertyNames;
|
||||
}
|
||||
|
||||
$value = $value->jsonSerialize($subPropertyNames);
|
||||
} else if (is_array($value)) {
|
||||
$subPropertyNames = $propertyNames[$propertyName] ?? null;
|
||||
@ -104,7 +110,7 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable {
|
||||
}, $value);
|
||||
}
|
||||
|
||||
$jsonArray[$property->getName()] = $value;
|
||||
$jsonArray[$propertyName] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -315,6 +315,10 @@ class DatabaseEntityHandler implements Persistable {
|
||||
return $this->properties[$property];
|
||||
}
|
||||
|
||||
public function hasProperty(string $propertyName): bool {
|
||||
return isset($this->properties[$propertyName]);
|
||||
}
|
||||
|
||||
public static function getPrefixedRow(array $row, string $prefix): array {
|
||||
$rel_row = [];
|
||||
foreach ($row as $relKey => $relValue) {
|
||||
@ -325,7 +329,7 @@ class DatabaseEntityHandler implements Persistable {
|
||||
return $rel_row;
|
||||
}
|
||||
|
||||
private function getValueFromRow(array $row, string $propertyName, mixed &$value, bool $initEntities = false): bool {
|
||||
private function getValueFromRow(array $row, string $propertyName, mixed &$value, int $fetchEntities = DatabaseEntityQuery::FETCH_NONE): bool {
|
||||
$column = $this->columns[$propertyName] ?? null;
|
||||
if (!$column) {
|
||||
return false;
|
||||
@ -340,15 +344,22 @@ class DatabaseEntityHandler implements Persistable {
|
||||
if ($column instanceof DateTimeColumn) {
|
||||
$value = new \DateTime($value);
|
||||
} else if ($column instanceof JsonColumn) {
|
||||
$value = json_decode($value);
|
||||
$value = json_decode($value, true);
|
||||
} else if (isset($this->relations[$propertyName])) {
|
||||
$relationHandler = $this->relations[$propertyName];
|
||||
$relColumnPrefix = self::buildColumnName($propertyName) . "_";
|
||||
if (array_key_exists($relColumnPrefix . "id", $row)) {
|
||||
$relId = $row[$relColumnPrefix . "id"];
|
||||
if ($relId !== null) {
|
||||
if ($initEntities) {
|
||||
$relationHandler = $this->relations[$propertyName];
|
||||
if ($fetchEntities !== DatabaseEntityQuery::FETCH_NONE) {
|
||||
if ($this === $relationHandler) {
|
||||
$value = DatabaseEntityQuery::fetchOne($this)
|
||||
->fetchEntities($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE)
|
||||
->whereEq($this->getColumnName("id"), $relId)
|
||||
->execute();
|
||||
} else {
|
||||
$value = $relationHandler->entityFromRow(self::getPrefixedRow($row, $relColumnPrefix), [], true);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@ -362,10 +373,17 @@ class DatabaseEntityHandler implements Persistable {
|
||||
}
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
$defaultValue = self::getAttribute($this->properties[$propertyName], DefaultValue::class);
|
||||
if ($defaultValue) {
|
||||
$value = $defaultValue->getValue();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function entityFromRow(array $row, array $additionalColumns = [], bool $initEntities = false): ?DatabaseEntity {
|
||||
public function entityFromRow(array $row, array $additionalColumns = [], int $fetchEntities = DatabaseEntityQuery::FETCH_NONE): ?DatabaseEntity {
|
||||
try {
|
||||
|
||||
$constructorClass = $this->entityClass;
|
||||
@ -384,7 +402,8 @@ class DatabaseEntityHandler implements Persistable {
|
||||
}
|
||||
|
||||
foreach ($this->properties as $property) {
|
||||
if ($this->getValueFromRow($row, $property->getName(), $value, $initEntities)) {
|
||||
$propertyName = $property->getName();
|
||||
if ($this->getValueFromRow($row, $propertyName, $value, $fetchEntities)) {
|
||||
$property->setAccessible(true);
|
||||
$property->setValue($entity, $value);
|
||||
}
|
||||
@ -405,6 +424,7 @@ class DatabaseEntityHandler implements Persistable {
|
||||
|
||||
$this->properties["id"]->setAccessible(true);
|
||||
$this->properties["id"]->setValue($entity, $row["id"]);
|
||||
|
||||
$entity->postFetch($this->sql, $row);
|
||||
return $entity;
|
||||
} catch (\Exception $exception) {
|
||||
@ -512,9 +532,13 @@ class DatabaseEntityHandler implements Persistable {
|
||||
return $success;
|
||||
}
|
||||
|
||||
public function fetchNMRelations(array $entities, bool $recursive = false) {
|
||||
public function fetchNMRelations(array $entities, int $fetchEntities = DatabaseEntityQuery::FETCH_DIRECT) {
|
||||
|
||||
if ($recursive) {
|
||||
if ($fetchEntities === DatabaseEntityQuery::FETCH_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE) {
|
||||
foreach ($entities as $entity) {
|
||||
foreach ($this->relations as $propertyName => $relHandler) {
|
||||
$property = $this->properties[$propertyName];
|
||||
@ -549,10 +573,7 @@ class DatabaseEntityHandler implements Persistable {
|
||||
->addSelectValue(new Column($thisIdColumn))
|
||||
->where(new CondIn(new Column($thisIdColumn), $entityIds));
|
||||
|
||||
if ($recursive) {
|
||||
$relEntityQuery->fetchEntities(true);
|
||||
}
|
||||
|
||||
$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());
|
||||
@ -563,7 +584,7 @@ class DatabaseEntityHandler implements Persistable {
|
||||
foreach ($rows as $row) {
|
||||
$relId = $row["id"];
|
||||
if (!isset($relEntities[$relId])) {
|
||||
$relEntity = $otherHandler->entityFromRow($row, [], $recursive);
|
||||
$relEntity = $otherHandler->entityFromRow($row, [], $fetchEntities);
|
||||
$relEntities[$relId] = $relEntity;
|
||||
}
|
||||
|
||||
@ -575,6 +596,7 @@ class DatabaseEntityHandler implements Persistable {
|
||||
$property->setValue($thisEntity, $targetArray);
|
||||
}
|
||||
} else if ($nmRelation instanceof NMRelationReference) {
|
||||
|
||||
$otherHandler = $nmRelation->getRelHandler();
|
||||
$thisIdColumn = $otherHandler->getColumnName($nmRelation->getThisProperty(), false);
|
||||
$relIdColumn = $otherHandler->getColumnName($nmRelation->getRefProperty(), false);
|
||||
@ -582,10 +604,7 @@ class DatabaseEntityHandler implements Persistable {
|
||||
$relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler)
|
||||
->where(new CondIn(new Column($thisIdColumn), $entityIds));
|
||||
|
||||
if ($recursive) {
|
||||
$relEntityQuery->fetchEntities(true);
|
||||
}
|
||||
|
||||
$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());
|
||||
@ -596,7 +615,7 @@ class DatabaseEntityHandler implements Persistable {
|
||||
$thisIdProperty->setAccessible(true);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$relEntity = $otherHandler->entityFromRow($row, [], $recursive);
|
||||
$relEntity = $otherHandler->entityFromRow($row, [], $fetchEntities);
|
||||
$thisEntity = $entities[$row[$thisIdColumn]];
|
||||
$thisIdProperty->setValue($relEntity, $thisEntity);
|
||||
$targetArray = $property->getValue($thisEntity);
|
||||
@ -609,10 +628,11 @@ class DatabaseEntityHandler implements Persistable {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($recursive) {
|
||||
// TODO whats that here lol
|
||||
if ($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE) {
|
||||
foreach ($entities as $entity) {
|
||||
$relEntities = $property->getValue($entity);
|
||||
$otherHandler->fetchNMRelations($relEntities);
|
||||
// $otherHandler->fetchNMRelations($relEntities, $fetchEntities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,8 +94,10 @@ class DatabaseEntityQuery extends Select {
|
||||
|
||||
$relIndex = 1;
|
||||
foreach ($this->handler->getRelations() as $propertyName => $relationHandler) {
|
||||
if ($this->handler !== $relationHandler) {
|
||||
$this->fetchRelation($propertyName, $this->handler->getTableName(), $this->handler, $relationHandler, $relIndex, $recursive);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -117,7 +119,6 @@ class DatabaseEntityQuery extends Select {
|
||||
$alias = "t$relIndex"; // t1, t2, t3, ...
|
||||
$relIndex++;
|
||||
|
||||
|
||||
if ($isNullable) {
|
||||
$this->leftJoin($referencedTable, "$tableName.$foreignColumnName", "$alias.id", $alias);
|
||||
} else {
|
||||
@ -153,21 +154,21 @@ class DatabaseEntityQuery extends Select {
|
||||
if ($this->resultType === SQL::FETCH_ALL) {
|
||||
$entities = [];
|
||||
foreach ($res as $row) {
|
||||
$entity = $this->handler->entityFromRow($row, $this->additionalColumns, $this->fetchSubEntities !== self::FETCH_NONE);
|
||||
$entity = $this->handler->entityFromRow($row, $this->additionalColumns, $this->fetchSubEntities);
|
||||
if ($entity) {
|
||||
$entities[$entity->getId()] = $entity;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->fetchSubEntities !== self::FETCH_NONE) {
|
||||
$this->handler->fetchNMRelations($entities, $this->fetchSubEntities === self::FETCH_RECURSIVE);
|
||||
$this->handler->fetchNMRelations($entities, $this->fetchSubEntities);
|
||||
}
|
||||
|
||||
return $entities;
|
||||
} else if ($this->resultType === SQL::FETCH_ONE) {
|
||||
$entity = $this->handler->entityFromRow($res, $this->additionalColumns, $this->fetchSubEntities !== self::FETCH_NONE);
|
||||
$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 === self::FETCH_RECURSIVE);
|
||||
$this->handler->fetchNMRelations([$entity->getId() => $entity], $this->fetchSubEntities);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
|
@ -26,7 +26,7 @@ class DocumentRoute extends Route {
|
||||
|
||||
protected function readExtra() {
|
||||
parent::readExtra();
|
||||
$this->args = json_decode($this->extra);
|
||||
$this->args = json_decode($this->extra) ?? [];
|
||||
}
|
||||
|
||||
public function preInsert(array &$row) {
|
||||
|
0
Site/Localization/.gitkeep
Normal file
0
Site/Localization/.gitkeep
Normal file
@ -1,4 +1,5 @@
|
||||
import {USER_GROUP_ADMIN} from "./constants";
|
||||
import {isInt} from "./util";
|
||||
|
||||
export default class API {
|
||||
constructor() {
|
||||
@ -14,13 +15,22 @@ export default class API {
|
||||
|
||||
async apiCall(method, params) {
|
||||
params = params || { };
|
||||
params.csrfToken = this.csrfToken();
|
||||
let response = await fetch("/api/" + method, {
|
||||
method: 'post',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(params)
|
||||
});
|
||||
const csrfToken = this.csrfToken();
|
||||
const config = {method: 'post'};
|
||||
if (params instanceof FormData) {
|
||||
if (csrfToken) {
|
||||
params.append("csrfToken", csrfToken);
|
||||
}
|
||||
config.body = params;
|
||||
} else {
|
||||
if (csrfToken) {
|
||||
params.csrfToken = csrfToken;
|
||||
}
|
||||
config.headers = {'Content-Type': 'application/json'};
|
||||
config.body = JSON.stringify(params);
|
||||
}
|
||||
|
||||
let response = await fetch("/api/" + method, config);
|
||||
let res = await response.json();
|
||||
if (!res.success && res.msg === "You are not logged in.") {
|
||||
this.loggedIn = false;
|
||||
@ -35,7 +45,7 @@ export default class API {
|
||||
}
|
||||
|
||||
for (const permission of this.permissions) {
|
||||
if (method.endsWith("*") && permission.toLowerCase().startsWith(method.toLowerCase().substr(0, method.length - 1))) {
|
||||
if (method.endsWith("*") && permission.toLowerCase().startsWith(method.toLowerCase().substring(0, method.length - 1))) {
|
||||
return true;
|
||||
} else if (method.toLowerCase() === permission.toLowerCase()) {
|
||||
return true;
|
||||
@ -48,7 +58,7 @@ export default class API {
|
||||
|
||||
hasGroup(groupIdOrName) {
|
||||
if (this.loggedIn && this.user?.groups) {
|
||||
if (!isNaN(groupIdOrName) && (typeof groupIdOrName === 'string' && groupIdOrName.match(/^\d+$/))) {
|
||||
if (isInt(groupIdOrName)) {
|
||||
return this.user.groups.hasOwnProperty(groupIdOrName);
|
||||
} else {
|
||||
let userGroups = Object.values(this.user.groups);
|
||||
|
10
react/shared/elements/dialog.css
Normal file
10
react/shared/elements/dialog.css
Normal file
@ -0,0 +1,10 @@
|
||||
.modal-dialog {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 400px;
|
||||
border: 2px solid #000;
|
||||
padding: 8px;
|
||||
background-color: white;
|
||||
}
|
@ -1,14 +1,16 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import {Box, Modal} from "@mui/material";
|
||||
import {Button, Typography} from "@material-ui/core";
|
||||
import "./dialog.css";
|
||||
|
||||
export default function Dialog(props) {
|
||||
|
||||
const show = props.show;
|
||||
const classes = ["modal", "fade"];
|
||||
const style = { paddingRight: "12px", display: (show ? "block" : "none") };
|
||||
const onClose = props.onClose || function() { };
|
||||
const onOption = props.onOption || function() { };
|
||||
const options = props.options || ["Close"];
|
||||
const type = props.type || "default";
|
||||
|
||||
let buttons = [];
|
||||
for (let name of options) {
|
||||
@ -17,31 +19,27 @@ export default function Dialog(props) {
|
||||
else if(name === "No") type = "danger";
|
||||
|
||||
buttons.push(
|
||||
<button type="button" key={"button-" + name} className={"btn btn-" + type}
|
||||
<Button variant={"outlined"} size={"small"} type="button" key={"button-" + name}
|
||||
data-dismiss={"modal"} onClick={() => { onClose(); onOption(name); }}>
|
||||
{name}
|
||||
</button>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx(classes, show && "show")} style={style} aria-modal={"true"} onClick={() => onClose()}>
|
||||
<div className="modal-dialog" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">{props.title}</h4>
|
||||
<button type="button" className="close" data-dismiss="modal" aria-label="Close" onClick={() => onClose()}>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
<p>{props.message}</p>
|
||||
</div>
|
||||
<div className="modal-footer">
|
||||
return <Modal
|
||||
open={show}
|
||||
onClose={onClose}
|
||||
aria-labelledby="modal-title"
|
||||
aria-describedby="modal-description"
|
||||
>
|
||||
<Box className={clsx("modal-dialog", props.className)}>
|
||||
<Typography id="modal-title" variant="h6" component="h2">
|
||||
{props.title}
|
||||
</Typography>
|
||||
<Typography id="modal-description" sx={{ mt: 2 }}>
|
||||
{props.message}
|
||||
</Typography>
|
||||
{ buttons }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</Box>
|
||||
</Modal>
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import React, {useReducer} from 'react';
|
||||
import {createContext, useCallback, useState} from "react";
|
||||
import { enUS as dateFnsEN, de as dateFnsDE } from 'date-fns/locale';
|
||||
|
||||
const LocaleContext = createContext(null);
|
||||
|
||||
@ -29,11 +30,11 @@ function reducer(entries, action) {
|
||||
|
||||
function LocaleProvider(props) {
|
||||
|
||||
// const [entries, setEntries] = useState(window.languageEntries || {});
|
||||
const [entries, dispatch] = useReducer(reducer, window.languageEntries || {});
|
||||
const [currentLocale, setCurrentLocale] = useState(window.languageCode || "en_US");
|
||||
|
||||
const translate = useCallback((key, defaultTranslation = null) => {
|
||||
|
||||
if (currentLocale) {
|
||||
if (entries.hasOwnProperty(currentLocale)) {
|
||||
let [module, variable] = key.split(".");
|
||||
@ -46,7 +47,7 @@ function LocaleProvider(props) {
|
||||
}
|
||||
}
|
||||
|
||||
return defaultTranslation || "[" + key + "]";
|
||||
return key ? defaultTranslation || "[" + key + "]" : "";
|
||||
}, [currentLocale, entries]);
|
||||
|
||||
const hasModule = useCallback((code, module) => {
|
||||
@ -61,6 +62,16 @@ function LocaleProvider(props) {
|
||||
}
|
||||
}, [entries]);
|
||||
|
||||
const toDateFns = () => {
|
||||
switch (currentLocale) {
|
||||
case 'de_DE':
|
||||
return dateFnsDE;
|
||||
case 'en_US':
|
||||
default:
|
||||
return dateFnsEN;
|
||||
}
|
||||
}
|
||||
|
||||
/** API HOOKS **/
|
||||
const setLanguage = useCallback(async (api, params) => {
|
||||
let res = await api.setLanguage(params);
|
||||
|
@ -50,25 +50,25 @@ const getBaseUrl = () => {
|
||||
const formatDate = (L, apiDate) => {
|
||||
if (!(apiDate instanceof Date)) {
|
||||
if (!isNaN(apiDate)) {
|
||||
apiDate = new Date(apiDate);
|
||||
apiDate = new Date(apiDate * 1000);
|
||||
} else {
|
||||
apiDate = parse(apiDate, API_DATE_FORMAT, new Date());
|
||||
}
|
||||
}
|
||||
|
||||
return format(apiDate, L("general.date_format", "YYY/MM/dd"));
|
||||
return format(apiDate, L("general.datefns_date_format", "YYY/MM/dd"));
|
||||
}
|
||||
|
||||
const formatDateTime = (L, apiDate) => {
|
||||
if (!(apiDate instanceof Date)) {
|
||||
if (!isNaN(apiDate)) {
|
||||
apiDate = new Date(apiDate);
|
||||
apiDate = new Date(apiDate * 1000);
|
||||
} else {
|
||||
apiDate = parse(apiDate, API_DATETIME_FORMAT, new Date());
|
||||
}
|
||||
}
|
||||
|
||||
return format(apiDate, L("general.date_time_format", "YYY/MM/dd HH:mm:ss"));
|
||||
return format(apiDate, L("general.datefns_date_time_format", "YYY/MM/dd HH:mm:ss"));
|
||||
}
|
||||
|
||||
const upperFirstChars = (str) => {
|
||||
@ -77,5 +77,11 @@ const upperFirstChars = (str) => {
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
const isInt = (value) => {
|
||||
return !isNaN(value) &&
|
||||
parseInt(Number(value)) === value &&
|
||||
!isNaN(parseInt(value, 10));
|
||||
}
|
||||
|
||||
export { humanReadableSize, removeParameter, getParameter, encodeText, decodeText, getBaseUrl,
|
||||
formatDate, formatDateTime, upperFirstChars };
|
||||
formatDate, formatDateTime, upperFirstChars, isInt };
|
@ -17,7 +17,7 @@ class DatabaseEntityTest extends \PHPUnit\Framework\TestCase {
|
||||
|
||||
public static function setUpBeforeClass(): void {
|
||||
parent::setUpBeforeClass();
|
||||
self::$CONTEXT = new Context();
|
||||
self::$CONTEXT = Context::instance();
|
||||
if (!self::$CONTEXT->initSQL()) {
|
||||
throw new Exception("Could not establish database connection");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user