frontend, localization, bugfix
This commit is contained in:
parent
0418118841
commit
1d6ff17994
@ -160,6 +160,9 @@ class Parameter {
|
|||||||
if ($value instanceof DateTime) {
|
if ($value instanceof DateTime) {
|
||||||
$this->value = $value;
|
$this->value = $value;
|
||||||
$valid = true;
|
$valid = true;
|
||||||
|
} else if (is_int($value) || (is_string($value) && preg_match("/^\d+$/", $value))) {
|
||||||
|
$this->value = (new \DateTime())->setTimestamp(intval($value));
|
||||||
|
$valid = true;
|
||||||
} else {
|
} else {
|
||||||
$format = $this->getFormat();
|
$format = $this->getFormat();
|
||||||
$d = DateTime::createFromFormat($format, $value);
|
$d = DateTime::createFromFormat($format, $value);
|
||||||
|
@ -13,11 +13,15 @@ class StringType extends Parameter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function parseParam($value): bool {
|
public function parseParam($value): bool {
|
||||||
if(!is_string($value)) {
|
if (!parent::parseParam($value)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($this->maxLength > 0 && strlen($value) > $this->maxLength) {
|
if (!is_string($value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->maxLength > 0 && strlen($value) > $this->maxLength) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +144,7 @@ abstract class Request {
|
|||||||
|
|
||||||
if ($this->externalCall) {
|
if ($this->externalCall) {
|
||||||
$values = $_REQUEST;
|
$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);
|
$jsonData = json_decode(file_get_contents('php://input'), true);
|
||||||
if ($jsonData !== null) {
|
if ($jsonData !== null) {
|
||||||
$values = array_merge($values, $jsonData);
|
$values = array_merge($values, $jsonData);
|
||||||
@ -432,4 +432,42 @@ abstract class Request {
|
|||||||
return $this->createError("Error processing image: " . $ex->getMessage());
|
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;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: access mysql database instead of configured one
|
||||||
public function tableExists(string $tableName): bool {
|
public function tableExists(string $tableName): bool {
|
||||||
$tableSchema = $this->connectionData->getProperty("database");
|
$tableSchema = $this->connectionData->getProperty("database");
|
||||||
$res = $this->select(new Count())
|
$res = $this->select(new Count())
|
||||||
|
@ -16,20 +16,35 @@ use Twig\Loader\FilesystemLoader;
|
|||||||
|
|
||||||
class TemplateDocument extends Document {
|
class TemplateDocument extends Document {
|
||||||
|
|
||||||
const TEMPLATE_PATH = WEBROOT . '/Core/Templates';
|
|
||||||
|
|
||||||
private string $templateName;
|
private string $templateName;
|
||||||
protected array $parameters;
|
protected array $parameters;
|
||||||
private Environment $twigEnvironment;
|
private Environment $twigEnvironment;
|
||||||
private FilesystemLoader $twigLoader;
|
private FilesystemLoader $twigLoader;
|
||||||
protected string $title;
|
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 = []) {
|
public function __construct(Router $router, string $templateName, array $params = []) {
|
||||||
parent::__construct($router);
|
parent::__construct($router);
|
||||||
$this->title = "Untitled Document";
|
$this->title = "Untitled Document";
|
||||||
$this->templateName = $templateName;
|
$this->templateName = $templateName;
|
||||||
$this->parameters = $params;
|
$this->parameters = $params;
|
||||||
$this->twigLoader = new FilesystemLoader(self::TEMPLATE_PATH);
|
$this->twigLoader = new FilesystemLoader(self::getTemplatePaths());
|
||||||
$this->twigEnvironment = new Environment($this->twigLoader, [
|
$this->twigEnvironment = new Environment($this->twigLoader, [
|
||||||
'cache' => WEBROOT . '/Site/Cache/Templates/',
|
'cache' => WEBROOT . '/Site/Cache/Templates/',
|
||||||
'auto_reload' => true
|
'auto_reload' => true
|
||||||
@ -84,7 +99,7 @@ class TemplateDocument extends Document {
|
|||||||
"query" => $urlParts["query"] ?? "",
|
"query" => $urlParts["query"] ?? "",
|
||||||
"fragment" => $urlParts["fragment"] ?? ""
|
"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(),
|
"registrationEnabled" => $settings->isRegistrationAllowed(),
|
||||||
"title" => $this->title,
|
"title" => $this->title,
|
||||||
"recaptcha" => [
|
"recaptcha" => [
|
||||||
|
@ -4,15 +4,60 @@ return [
|
|||||||
"something_went_wrong" => "Etwas ist schief gelaufen",
|
"something_went_wrong" => "Etwas ist schief gelaufen",
|
||||||
"error_occurred" => "Ein Fehler ist aufgetreten",
|
"error_occurred" => "Ein Fehler ist aufgetreten",
|
||||||
"retry" => "Erneut versuchen",
|
"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",
|
"submitting" => "Übermittle",
|
||||||
"submit" => "Absenden",
|
"submit" => "Absenden",
|
||||||
"request" => "Anfordern",
|
"request" => "Anfordern",
|
||||||
|
"cancel" => "Abbrechen",
|
||||||
|
"confirm" => "Bestätigen",
|
||||||
"language" => "Sprache",
|
"language" => "Sprache",
|
||||||
"loading" => "Laden",
|
"loading" => "Laden",
|
||||||
"logout" => "Ausloggen",
|
"logout" => "Ausloggen",
|
||||||
"noscript" => "Sie müssen Javascript aktivieren um diese Anwendung zu benutzen",
|
"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
|
# data table
|
||||||
"showing_x_of_y_entries" => "Zeige %d von %d Einträgen",
|
"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 [
|
return [
|
||||||
"something_went_wrong" => "Something went wrong",
|
"something_went_wrong" => "Something went wrong",
|
||||||
"error_occurred" => "An error occurred",
|
"error_occurred" => "An error occurred",
|
||||||
"retry" => "Retry",
|
"unsaved_changes" => "You have unsaved changed",
|
||||||
"go_back" => "Go Back",
|
"new" => "New",
|
||||||
"submitting" => "Submitting",
|
|
||||||
"submit" => "Submit",
|
|
||||||
"request" => "Request",
|
|
||||||
"language" => "Language",
|
"language" => "Language",
|
||||||
"loading" => "Loading",
|
"loading" => "Loading",
|
||||||
"logout" => "Logout",
|
"logout" => "Logout",
|
||||||
"noscript" => "You need Javascript enabled to run this app",
|
"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
|
# data table
|
||||||
"showing_x_of_y_entries" => "Showing %d of %d entries",
|
"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 {
|
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"];
|
$ignoredProperties = ["entityLogConfig", "customData"];
|
||||||
|
|
||||||
$jsonArray = [];
|
$jsonArray = [];
|
||||||
foreach ($properties as $property) {
|
foreach ($properties as $property) {
|
||||||
$property->setAccessible(true);
|
$property->setAccessible(true);
|
||||||
$propertyName = $property->getName();
|
$propertyName = $property->getName();
|
||||||
|
|
||||||
if (in_array($propertyName, $ignoredProperties)) {
|
if (in_array($propertyName, $ignoredProperties)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($property->getAttributes(Transient::class))) {
|
if (DatabaseEntityHandler::getAttribute($property, Transient::class)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +95,10 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable {
|
|||||||
$value = $value->getTimestamp();
|
$value = $value->getTimestamp();
|
||||||
} else if ($value instanceof DatabaseEntity) {
|
} else if ($value instanceof DatabaseEntity) {
|
||||||
$subPropertyNames = $propertyNames[$propertyName] ?? null;
|
$subPropertyNames = $propertyNames[$propertyName] ?? null;
|
||||||
|
if ($subPropertyNames === null && $value instanceof $this) {
|
||||||
|
$subPropertyNames = $propertyNames;
|
||||||
|
}
|
||||||
|
|
||||||
$value = $value->jsonSerialize($subPropertyNames);
|
$value = $value->jsonSerialize($subPropertyNames);
|
||||||
} else if (is_array($value)) {
|
} else if (is_array($value)) {
|
||||||
$subPropertyNames = $propertyNames[$propertyName] ?? null;
|
$subPropertyNames = $propertyNames[$propertyName] ?? null;
|
||||||
@ -104,7 +110,7 @@ abstract class DatabaseEntity implements ArrayAccess, JsonSerializable {
|
|||||||
}, $value);
|
}, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
$jsonArray[$property->getName()] = $value;
|
$jsonArray[$propertyName] = $value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -315,6 +315,10 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
return $this->properties[$property];
|
return $this->properties[$property];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function hasProperty(string $propertyName): bool {
|
||||||
|
return isset($this->properties[$propertyName]);
|
||||||
|
}
|
||||||
|
|
||||||
public static function getPrefixedRow(array $row, string $prefix): array {
|
public static function getPrefixedRow(array $row, string $prefix): array {
|
||||||
$rel_row = [];
|
$rel_row = [];
|
||||||
foreach ($row as $relKey => $relValue) {
|
foreach ($row as $relKey => $relValue) {
|
||||||
@ -325,7 +329,7 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
return $rel_row;
|
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;
|
$column = $this->columns[$propertyName] ?? null;
|
||||||
if (!$column) {
|
if (!$column) {
|
||||||
return false;
|
return false;
|
||||||
@ -340,15 +344,22 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
if ($column instanceof DateTimeColumn) {
|
if ($column instanceof DateTimeColumn) {
|
||||||
$value = new \DateTime($value);
|
$value = new \DateTime($value);
|
||||||
} else if ($column instanceof JsonColumn) {
|
} else if ($column instanceof JsonColumn) {
|
||||||
$value = json_decode($value);
|
$value = json_decode($value, true);
|
||||||
} else if (isset($this->relations[$propertyName])) {
|
} else if (isset($this->relations[$propertyName])) {
|
||||||
|
$relationHandler = $this->relations[$propertyName];
|
||||||
$relColumnPrefix = self::buildColumnName($propertyName) . "_";
|
$relColumnPrefix = self::buildColumnName($propertyName) . "_";
|
||||||
if (array_key_exists($relColumnPrefix . "id", $row)) {
|
if (array_key_exists($relColumnPrefix . "id", $row)) {
|
||||||
$relId = $row[$relColumnPrefix . "id"];
|
$relId = $row[$relColumnPrefix . "id"];
|
||||||
if ($relId !== null) {
|
if ($relId !== null) {
|
||||||
if ($initEntities) {
|
if ($fetchEntities !== DatabaseEntityQuery::FETCH_NONE) {
|
||||||
$relationHandler = $this->relations[$propertyName];
|
if ($this === $relationHandler) {
|
||||||
$value = $relationHandler->entityFromRow(self::getPrefixedRow($row, $relColumnPrefix), [], true);
|
$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 {
|
} else {
|
||||||
return false;
|
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;
|
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 {
|
try {
|
||||||
|
|
||||||
$constructorClass = $this->entityClass;
|
$constructorClass = $this->entityClass;
|
||||||
@ -384,7 +402,8 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach ($this->properties as $property) {
|
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->setAccessible(true);
|
||||||
$property->setValue($entity, $value);
|
$property->setValue($entity, $value);
|
||||||
}
|
}
|
||||||
@ -405,6 +424,7 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
|
|
||||||
$this->properties["id"]->setAccessible(true);
|
$this->properties["id"]->setAccessible(true);
|
||||||
$this->properties["id"]->setValue($entity, $row["id"]);
|
$this->properties["id"]->setValue($entity, $row["id"]);
|
||||||
|
|
||||||
$entity->postFetch($this->sql, $row);
|
$entity->postFetch($this->sql, $row);
|
||||||
return $entity;
|
return $entity;
|
||||||
} catch (\Exception $exception) {
|
} catch (\Exception $exception) {
|
||||||
@ -512,9 +532,13 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
return $success;
|
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 ($entities as $entity) {
|
||||||
foreach ($this->relations as $propertyName => $relHandler) {
|
foreach ($this->relations as $propertyName => $relHandler) {
|
||||||
$property = $this->properties[$propertyName];
|
$property = $this->properties[$propertyName];
|
||||||
@ -549,10 +573,7 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
->addSelectValue(new Column($thisIdColumn))
|
->addSelectValue(new Column($thisIdColumn))
|
||||||
->where(new CondIn(new Column($thisIdColumn), $entityIds));
|
->where(new CondIn(new Column($thisIdColumn), $entityIds));
|
||||||
|
|
||||||
if ($recursive) {
|
$relEntityQuery->fetchEntities($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE);
|
||||||
$relEntityQuery->fetchEntities(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$rows = $relEntityQuery->executeSQL();
|
$rows = $relEntityQuery->executeSQL();
|
||||||
if (!is_array($rows)) {
|
if (!is_array($rows)) {
|
||||||
$this->logger->error("Error fetching n:m relations from table: '$nmTable': " . $this->sql->getLastError());
|
$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) {
|
foreach ($rows as $row) {
|
||||||
$relId = $row["id"];
|
$relId = $row["id"];
|
||||||
if (!isset($relEntities[$relId])) {
|
if (!isset($relEntities[$relId])) {
|
||||||
$relEntity = $otherHandler->entityFromRow($row, [], $recursive);
|
$relEntity = $otherHandler->entityFromRow($row, [], $fetchEntities);
|
||||||
$relEntities[$relId] = $relEntity;
|
$relEntities[$relId] = $relEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -575,6 +596,7 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
$property->setValue($thisEntity, $targetArray);
|
$property->setValue($thisEntity, $targetArray);
|
||||||
}
|
}
|
||||||
} else if ($nmRelation instanceof NMRelationReference) {
|
} else if ($nmRelation instanceof NMRelationReference) {
|
||||||
|
|
||||||
$otherHandler = $nmRelation->getRelHandler();
|
$otherHandler = $nmRelation->getRelHandler();
|
||||||
$thisIdColumn = $otherHandler->getColumnName($nmRelation->getThisProperty(), false);
|
$thisIdColumn = $otherHandler->getColumnName($nmRelation->getThisProperty(), false);
|
||||||
$relIdColumn = $otherHandler->getColumnName($nmRelation->getRefProperty(), false);
|
$relIdColumn = $otherHandler->getColumnName($nmRelation->getRefProperty(), false);
|
||||||
@ -582,10 +604,7 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
$relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler)
|
$relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler)
|
||||||
->where(new CondIn(new Column($thisIdColumn), $entityIds));
|
->where(new CondIn(new Column($thisIdColumn), $entityIds));
|
||||||
|
|
||||||
if ($recursive) {
|
$relEntityQuery->fetchEntities($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE);
|
||||||
$relEntityQuery->fetchEntities(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
$rows = $relEntityQuery->executeSQL();
|
$rows = $relEntityQuery->executeSQL();
|
||||||
if (!is_array($rows)) {
|
if (!is_array($rows)) {
|
||||||
$this->logger->error("Error fetching n:m relations from table: '$nmTable': " . $this->sql->getLastError());
|
$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);
|
$thisIdProperty->setAccessible(true);
|
||||||
|
|
||||||
foreach ($rows as $row) {
|
foreach ($rows as $row) {
|
||||||
$relEntity = $otherHandler->entityFromRow($row, [], $recursive);
|
$relEntity = $otherHandler->entityFromRow($row, [], $fetchEntities);
|
||||||
$thisEntity = $entities[$row[$thisIdColumn]];
|
$thisEntity = $entities[$row[$thisIdColumn]];
|
||||||
$thisIdProperty->setValue($relEntity, $thisEntity);
|
$thisIdProperty->setValue($relEntity, $thisEntity);
|
||||||
$targetArray = $property->getValue($thisEntity);
|
$targetArray = $property->getValue($thisEntity);
|
||||||
@ -609,10 +628,11 @@ class DatabaseEntityHandler implements Persistable {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($recursive) {
|
// TODO whats that here lol
|
||||||
|
if ($fetchEntities === DatabaseEntityQuery::FETCH_RECURSIVE) {
|
||||||
foreach ($entities as $entity) {
|
foreach ($entities as $entity) {
|
||||||
$relEntities = $property->getValue($entity);
|
$relEntities = $property->getValue($entity);
|
||||||
$otherHandler->fetchNMRelations($relEntities);
|
// $otherHandler->fetchNMRelations($relEntities, $fetchEntities);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,9 @@ class DatabaseEntityQuery extends Select {
|
|||||||
|
|
||||||
$relIndex = 1;
|
$relIndex = 1;
|
||||||
foreach ($this->handler->getRelations() as $propertyName => $relationHandler) {
|
foreach ($this->handler->getRelations() as $propertyName => $relationHandler) {
|
||||||
$this->fetchRelation($propertyName, $this->handler->getTableName(), $this->handler, $relationHandler, $relIndex, $recursive);
|
if ($this->handler !== $relationHandler) {
|
||||||
|
$this->fetchRelation($propertyName, $this->handler->getTableName(), $this->handler, $relationHandler, $relIndex, $recursive);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@ -117,7 +119,6 @@ class DatabaseEntityQuery extends Select {
|
|||||||
$alias = "t$relIndex"; // t1, t2, t3, ...
|
$alias = "t$relIndex"; // t1, t2, t3, ...
|
||||||
$relIndex++;
|
$relIndex++;
|
||||||
|
|
||||||
|
|
||||||
if ($isNullable) {
|
if ($isNullable) {
|
||||||
$this->leftJoin($referencedTable, "$tableName.$foreignColumnName", "$alias.id", $alias);
|
$this->leftJoin($referencedTable, "$tableName.$foreignColumnName", "$alias.id", $alias);
|
||||||
} else {
|
} else {
|
||||||
@ -153,21 +154,21 @@ class DatabaseEntityQuery extends Select {
|
|||||||
if ($this->resultType === SQL::FETCH_ALL) {
|
if ($this->resultType === SQL::FETCH_ALL) {
|
||||||
$entities = [];
|
$entities = [];
|
||||||
foreach ($res as $row) {
|
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) {
|
if ($entity) {
|
||||||
$entities[$entity->getId()] = $entity;
|
$entities[$entity->getId()] = $entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->fetchSubEntities !== self::FETCH_NONE) {
|
if ($this->fetchSubEntities !== self::FETCH_NONE) {
|
||||||
$this->handler->fetchNMRelations($entities, $this->fetchSubEntities === self::FETCH_RECURSIVE);
|
$this->handler->fetchNMRelations($entities, $this->fetchSubEntities);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $entities;
|
return $entities;
|
||||||
} else if ($this->resultType === SQL::FETCH_ONE) {
|
} 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) {
|
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;
|
return $entity;
|
||||||
|
@ -26,7 +26,7 @@ class DocumentRoute extends Route {
|
|||||||
|
|
||||||
protected function readExtra() {
|
protected function readExtra() {
|
||||||
parent::readExtra();
|
parent::readExtra();
|
||||||
$this->args = json_decode($this->extra);
|
$this->args = json_decode($this->extra) ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function preInsert(array &$row) {
|
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 {USER_GROUP_ADMIN} from "./constants";
|
||||||
|
import {isInt} from "./util";
|
||||||
|
|
||||||
export default class API {
|
export default class API {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -14,13 +15,22 @@ export default class API {
|
|||||||
|
|
||||||
async apiCall(method, params) {
|
async apiCall(method, params) {
|
||||||
params = params || { };
|
params = params || { };
|
||||||
params.csrfToken = this.csrfToken();
|
const csrfToken = this.csrfToken();
|
||||||
let response = await fetch("/api/" + method, {
|
const config = {method: 'post'};
|
||||||
method: 'post',
|
if (params instanceof FormData) {
|
||||||
headers: {'Content-Type': 'application/json'},
|
if (csrfToken) {
|
||||||
body: JSON.stringify(params)
|
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();
|
let res = await response.json();
|
||||||
if (!res.success && res.msg === "You are not logged in.") {
|
if (!res.success && res.msg === "You are not logged in.") {
|
||||||
this.loggedIn = false;
|
this.loggedIn = false;
|
||||||
@ -35,7 +45,7 @@ export default class API {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const permission of this.permissions) {
|
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;
|
return true;
|
||||||
} else if (method.toLowerCase() === permission.toLowerCase()) {
|
} else if (method.toLowerCase() === permission.toLowerCase()) {
|
||||||
return true;
|
return true;
|
||||||
@ -48,7 +58,7 @@ export default class API {
|
|||||||
|
|
||||||
hasGroup(groupIdOrName) {
|
hasGroup(groupIdOrName) {
|
||||||
if (this.loggedIn && this.user?.groups) {
|
if (this.loggedIn && this.user?.groups) {
|
||||||
if (!isNaN(groupIdOrName) && (typeof groupIdOrName === 'string' && groupIdOrName.match(/^\d+$/))) {
|
if (isInt(groupIdOrName)) {
|
||||||
return this.user.groups.hasOwnProperty(groupIdOrName);
|
return this.user.groups.hasOwnProperty(groupIdOrName);
|
||||||
} else {
|
} else {
|
||||||
let userGroups = Object.values(this.user.groups);
|
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 React from "react";
|
||||||
import clsx from "clsx";
|
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) {
|
export default function Dialog(props) {
|
||||||
|
|
||||||
const show = props.show;
|
const show = props.show;
|
||||||
const classes = ["modal", "fade"];
|
|
||||||
const style = { paddingRight: "12px", display: (show ? "block" : "none") };
|
|
||||||
const onClose = props.onClose || function() { };
|
const onClose = props.onClose || function() { };
|
||||||
const onOption = props.onOption || function() { };
|
const onOption = props.onOption || function() { };
|
||||||
const options = props.options || ["Close"];
|
const options = props.options || ["Close"];
|
||||||
|
const type = props.type || "default";
|
||||||
|
|
||||||
let buttons = [];
|
let buttons = [];
|
||||||
for (let name of options) {
|
for (let name of options) {
|
||||||
@ -17,31 +19,27 @@ export default function Dialog(props) {
|
|||||||
else if(name === "No") type = "danger";
|
else if(name === "No") type = "danger";
|
||||||
|
|
||||||
buttons.push(
|
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); }}>
|
data-dismiss={"modal"} onClick={() => { onClose(); onOption(name); }}>
|
||||||
{name}
|
{name}
|
||||||
</button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <Modal
|
||||||
<div className={clsx(classes, show && "show")} style={style} aria-modal={"true"} onClick={() => onClose()}>
|
open={show}
|
||||||
<div className="modal-dialog" onClick={(e) => e.stopPropagation()}>
|
onClose={onClose}
|
||||||
<div className="modal-content">
|
aria-labelledby="modal-title"
|
||||||
<div className="modal-header">
|
aria-describedby="modal-description"
|
||||||
<h4 className="modal-title">{props.title}</h4>
|
>
|
||||||
<button type="button" className="close" data-dismiss="modal" aria-label="Close" onClick={() => onClose()}>
|
<Box className={clsx("modal-dialog", props.className)}>
|
||||||
<span aria-hidden="true">×</span>
|
<Typography id="modal-title" variant="h6" component="h2">
|
||||||
</button>
|
{props.title}
|
||||||
</div>
|
</Typography>
|
||||||
<div className="modal-body">
|
<Typography id="modal-description" sx={{ mt: 2 }}>
|
||||||
<p>{props.message}</p>
|
{props.message}
|
||||||
</div>
|
</Typography>
|
||||||
<div className="modal-footer">
|
{ buttons }
|
||||||
{ buttons }
|
</Box>
|
||||||
</div>
|
</Modal>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import React, {useReducer} from 'react';
|
import React, {useReducer} from 'react';
|
||||||
import {createContext, useCallback, useState} from "react";
|
import {createContext, useCallback, useState} from "react";
|
||||||
|
import { enUS as dateFnsEN, de as dateFnsDE } from 'date-fns/locale';
|
||||||
|
|
||||||
const LocaleContext = createContext(null);
|
const LocaleContext = createContext(null);
|
||||||
|
|
||||||
@ -29,11 +30,11 @@ function reducer(entries, action) {
|
|||||||
|
|
||||||
function LocaleProvider(props) {
|
function LocaleProvider(props) {
|
||||||
|
|
||||||
// const [entries, setEntries] = useState(window.languageEntries || {});
|
|
||||||
const [entries, dispatch] = useReducer(reducer, window.languageEntries || {});
|
const [entries, dispatch] = useReducer(reducer, window.languageEntries || {});
|
||||||
const [currentLocale, setCurrentLocale] = useState(window.languageCode || "en_US");
|
const [currentLocale, setCurrentLocale] = useState(window.languageCode || "en_US");
|
||||||
|
|
||||||
const translate = useCallback((key, defaultTranslation = null) => {
|
const translate = useCallback((key, defaultTranslation = null) => {
|
||||||
|
|
||||||
if (currentLocale) {
|
if (currentLocale) {
|
||||||
if (entries.hasOwnProperty(currentLocale)) {
|
if (entries.hasOwnProperty(currentLocale)) {
|
||||||
let [module, variable] = key.split(".");
|
let [module, variable] = key.split(".");
|
||||||
@ -46,7 +47,7 @@ function LocaleProvider(props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return defaultTranslation || "[" + key + "]";
|
return key ? defaultTranslation || "[" + key + "]" : "";
|
||||||
}, [currentLocale, entries]);
|
}, [currentLocale, entries]);
|
||||||
|
|
||||||
const hasModule = useCallback((code, module) => {
|
const hasModule = useCallback((code, module) => {
|
||||||
@ -61,6 +62,16 @@ function LocaleProvider(props) {
|
|||||||
}
|
}
|
||||||
}, [entries]);
|
}, [entries]);
|
||||||
|
|
||||||
|
const toDateFns = () => {
|
||||||
|
switch (currentLocale) {
|
||||||
|
case 'de_DE':
|
||||||
|
return dateFnsDE;
|
||||||
|
case 'en_US':
|
||||||
|
default:
|
||||||
|
return dateFnsEN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** API HOOKS **/
|
/** API HOOKS **/
|
||||||
const setLanguage = useCallback(async (api, params) => {
|
const setLanguage = useCallback(async (api, params) => {
|
||||||
let res = await api.setLanguage(params);
|
let res = await api.setLanguage(params);
|
||||||
|
@ -50,25 +50,25 @@ const getBaseUrl = () => {
|
|||||||
const formatDate = (L, apiDate) => {
|
const formatDate = (L, apiDate) => {
|
||||||
if (!(apiDate instanceof Date)) {
|
if (!(apiDate instanceof Date)) {
|
||||||
if (!isNaN(apiDate)) {
|
if (!isNaN(apiDate)) {
|
||||||
apiDate = new Date(apiDate);
|
apiDate = new Date(apiDate * 1000);
|
||||||
} else {
|
} else {
|
||||||
apiDate = parse(apiDate, API_DATE_FORMAT, new Date());
|
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) => {
|
const formatDateTime = (L, apiDate) => {
|
||||||
if (!(apiDate instanceof Date)) {
|
if (!(apiDate instanceof Date)) {
|
||||||
if (!isNaN(apiDate)) {
|
if (!isNaN(apiDate)) {
|
||||||
apiDate = new Date(apiDate);
|
apiDate = new Date(apiDate * 1000);
|
||||||
} else {
|
} else {
|
||||||
apiDate = parse(apiDate, API_DATETIME_FORMAT, new Date());
|
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) => {
|
const upperFirstChars = (str) => {
|
||||||
@ -77,5 +77,11 @@ const upperFirstChars = (str) => {
|
|||||||
.join(" ");
|
.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isInt = (value) => {
|
||||||
|
return !isNaN(value) &&
|
||||||
|
parseInt(Number(value)) === value &&
|
||||||
|
!isNaN(parseInt(value, 10));
|
||||||
|
}
|
||||||
|
|
||||||
export { humanReadableSize, removeParameter, getParameter, encodeText, decodeText, getBaseUrl,
|
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 {
|
public static function setUpBeforeClass(): void {
|
||||||
parent::setUpBeforeClass();
|
parent::setUpBeforeClass();
|
||||||
self::$CONTEXT = new Context();
|
self::$CONTEXT = Context::instance();
|
||||||
if (!self::$CONTEXT->initSQL()) {
|
if (!self::$CONTEXT->initSQL()) {
|
||||||
throw new Exception("Could not establish database connection");
|
throw new Exception("Could not establish database connection");
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user