Core v2.3, N:M Relations
This commit is contained in:
parent
b5b8f9b856
commit
303a5b69b5
@ -1,291 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Core\API {
|
|
||||||
|
|
||||||
use Core\Objects\Context;
|
|
||||||
|
|
||||||
abstract class ContactAPI extends Request {
|
|
||||||
|
|
||||||
protected ?string $messageId;
|
|
||||||
|
|
||||||
public function __construct(Context $context, bool $externalCall, array $params) {
|
|
||||||
parent::__construct($context, $externalCall, $params);
|
|
||||||
$this->messageId = null;
|
|
||||||
$this->csrfTokenRequired = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function sendMail(string $name, ?string $fromEmail, string $subject, string $message, ?string $to = null): bool {
|
|
||||||
$request = new \Core\API\Mail\Send($this->context);
|
|
||||||
$this->success = $request->execute(array(
|
|
||||||
"subject" => $subject,
|
|
||||||
"body" => $message,
|
|
||||||
"replyTo" => $fromEmail,
|
|
||||||
"replyName" => $name,
|
|
||||||
"to" => $to
|
|
||||||
));
|
|
||||||
|
|
||||||
$this->lastError = $request->getLastError();
|
|
||||||
if ($this->success) {
|
|
||||||
$this->messageId = $request->getResult()["messageId"];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Core\API\Contact {
|
|
||||||
|
|
||||||
use Core\API\ContactAPI;
|
|
||||||
use Core\API\Parameter\Parameter;
|
|
||||||
use Core\API\Parameter\StringType;
|
|
||||||
use Core\API\VerifyCaptcha;
|
|
||||||
use Core\Driver\SQL\Condition\Compare;
|
|
||||||
use Core\Driver\SQL\Condition\CondNot;
|
|
||||||
use Core\Driver\SQL\Expression\CaseWhen;
|
|
||||||
use Core\Driver\SQL\Expression\Sum;
|
|
||||||
use Core\Objects\Context;
|
|
||||||
|
|
||||||
class Request extends ContactAPI {
|
|
||||||
|
|
||||||
public function __construct(Context $context, bool $externalCall = false) {
|
|
||||||
$parameters = array(
|
|
||||||
'fromName' => new StringType('fromName', 32),
|
|
||||||
'fromEmail' => new Parameter('fromEmail', Parameter::TYPE_EMAIL),
|
|
||||||
'message' => new StringType('message', 512),
|
|
||||||
);
|
|
||||||
|
|
||||||
$settings = $context->getSettings();
|
|
||||||
if ($settings->isRecaptchaEnabled()) {
|
|
||||||
$parameters["captcha"] = new StringType("captcha");
|
|
||||||
}
|
|
||||||
|
|
||||||
parent::__construct($context, $externalCall, $parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function _execute(): bool {
|
|
||||||
$settings = $this->context->getSettings();
|
|
||||||
if ($settings->isRecaptchaEnabled()) {
|
|
||||||
$captcha = $this->getParam("captcha");
|
|
||||||
$req = new VerifyCaptcha($this->context);
|
|
||||||
if (!$req->execute(array("captcha" => $captcha, "action" => "contact"))) {
|
|
||||||
return $this->createError($req->getLastError());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parameter
|
|
||||||
$message = $this->getParam("message");
|
|
||||||
$name = $this->getParam("fromName");
|
|
||||||
$email = $this->getParam("fromEmail");
|
|
||||||
|
|
||||||
$sendMail = $this->sendMail($name, $email, "Contact Request", $message);
|
|
||||||
$insertDB = $this->insertContactRequest();
|
|
||||||
if (!$sendMail && !$insertDB) {
|
|
||||||
return $this->createError("The contact request could not be sent. The Administrator is already informed. Please try again later.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function insertContactRequest(): bool {
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$name = $this->getParam("fromName");
|
|
||||||
$email = $this->getParam("fromEmail");
|
|
||||||
$message = $this->getParam("message");
|
|
||||||
$messageId = $this->messageId ?? null;
|
|
||||||
|
|
||||||
$res = $sql->insert("ContactRequest", array("from_name", "from_email", "message", "messageId"))
|
|
||||||
->addRow($name, $email, $message, $messageId)
|
|
||||||
->returning("id")
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->success = ($res !== FALSE);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Respond extends ContactAPI {
|
|
||||||
|
|
||||||
public function __construct(Context $context, bool $externalCall = false) {
|
|
||||||
parent::__construct($context, $externalCall, array(
|
|
||||||
"requestId" => new Parameter("requestId", Parameter::TYPE_INT),
|
|
||||||
'message' => new StringType('message', 512),
|
|
||||||
));
|
|
||||||
$this->loginRequired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getSenderMail(): ?string {
|
|
||||||
$requestId = $this->getParam("requestId");
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$res = $sql->select("from_email")
|
|
||||||
->from("ContactRequest")
|
|
||||||
->where(new Compare("id", $requestId))
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->success = ($res !== false);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
|
|
||||||
if ($this->success) {
|
|
||||||
if (empty($res)) {
|
|
||||||
return $this->createError("Request does not exist");
|
|
||||||
} else {
|
|
||||||
return $res[0]["from_email"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function insertResponseMessage(): bool {
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$message = $this->getParam("message");
|
|
||||||
$requestId = $this->getParam("requestId");
|
|
||||||
|
|
||||||
$this->success = $sql->insert("ContactMessage", ["request_id", "user_id", "message", "messageId", "read"])
|
|
||||||
->addRow($requestId, $this->context->getUser()->getId(), $message, $this->messageId, true)
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function updateEntity() {
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$requestId = $this->getParam("requestId");
|
|
||||||
|
|
||||||
$sql->update("EntityLog")
|
|
||||||
->set("modified", $sql->now())
|
|
||||||
->where(new Compare("entityId", $requestId))
|
|
||||||
->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function _execute(): bool {
|
|
||||||
$message = $this->getParam("message");
|
|
||||||
$senderMail = $this->getSenderMail();
|
|
||||||
if (!$this->success) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = $this->context->getUser();
|
|
||||||
$fromName = $user->getUsername();
|
|
||||||
$fromEmail = $user->getEmail();
|
|
||||||
|
|
||||||
if (!$this->sendMail($fromName, $fromEmail, "Re: Contact Request", $message, $senderMail)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$this->insertResponseMessage()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->updateEntity();
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Fetch extends ContactAPI {
|
|
||||||
|
|
||||||
public function __construct(Context $context, bool $externalCall = false) {
|
|
||||||
parent::__construct($context, $externalCall, array());
|
|
||||||
$this->loginRequired = true;
|
|
||||||
$this->csrfTokenRequired = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function _execute(): bool {
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$res = $sql->select("ContactRequest.id", "from_name", "from_email", "from_name",
|
|
||||||
new Sum(new CaseWhen(new CondNot("ContactMessage.read"), 1, 0), "unread"))
|
|
||||||
->from("ContactRequest")
|
|
||||||
->groupBy("ContactRequest.id")
|
|
||||||
->leftJoin("ContactMessage", "ContactRequest.id", "ContactMessage.request_id")
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->success = ($res !== false);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
|
|
||||||
if ($this->success) {
|
|
||||||
$this->result["contactRequests"] = [];
|
|
||||||
foreach ($res as $row) {
|
|
||||||
$this->result["contactRequests"][] = array(
|
|
||||||
"id" => intval($row["id"]),
|
|
||||||
"from_name" => $row["from_name"],
|
|
||||||
"from_email" => $row["from_email"],
|
|
||||||
"unread" => intval($row["unread"]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Get extends ContactAPI {
|
|
||||||
|
|
||||||
public function __construct(Context $context, bool $externalCall = false) {
|
|
||||||
parent::__construct($context, $externalCall, array(
|
|
||||||
"requestId" => new Parameter("requestId", Parameter::TYPE_INT),
|
|
||||||
));
|
|
||||||
$this->loginRequired = true;
|
|
||||||
$this->csrfTokenRequired = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function updateRead() {
|
|
||||||
$requestId = $this->getParam("requestId");
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$sql->update("ContactMessage")
|
|
||||||
->set("read", 1)
|
|
||||||
->where(new Compare("request_id", $requestId))
|
|
||||||
->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function _execute(): bool {
|
|
||||||
$requestId = $this->getParam("requestId");
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
|
|
||||||
$res = $sql->select("from_name", "from_email", "message", "created_at")
|
|
||||||
->from("ContactRequest")
|
|
||||||
->where(new Compare("id", $requestId))
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->success = ($res !== false);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
|
|
||||||
if ($this->success) {
|
|
||||||
if (empty($res)) {
|
|
||||||
return $this->createError("Request does not exist");
|
|
||||||
} else {
|
|
||||||
$row = $res[0];
|
|
||||||
$this->result["request"] = array(
|
|
||||||
"from_name" => $row["from_name"],
|
|
||||||
"from_email" => $row["from_email"],
|
|
||||||
"messages" => array(
|
|
||||||
["sender_id" => null, "message" => $row["message"], "timestamp" => $row["created_at"]]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
$res = $sql->select("user_id", "message", "created_at")
|
|
||||||
->from("ContactMessage")
|
|
||||||
->where(new Compare("request_id", $requestId))
|
|
||||||
->orderBy("created_at")
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->success = ($res !== false);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
|
|
||||||
if ($this->success) {
|
|
||||||
foreach ($res as $row) {
|
|
||||||
$this->result["request"]["messages"][] = array(
|
|
||||||
"sender_id" => $row["user_id"], "message" => $row["message"], "timestamp" => $row["created_at"]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->updateRead();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -31,6 +31,7 @@ namespace Core\API\Groups {
|
|||||||
use Core\API\Parameter\Parameter;
|
use Core\API\Parameter\Parameter;
|
||||||
use Core\API\Parameter\StringType;
|
use Core\API\Parameter\StringType;
|
||||||
use Core\Objects\Context;
|
use Core\Objects\Context;
|
||||||
|
use Core\Objects\DatabaseEntity\Controller\NMRelation;
|
||||||
use Core\Objects\DatabaseEntity\Group;
|
use Core\Objects\DatabaseEntity\Group;
|
||||||
|
|
||||||
class Fetch extends GroupsAPI {
|
class Fetch extends GroupsAPI {
|
||||||
@ -46,20 +47,6 @@ namespace Core\API\Groups {
|
|||||||
$this->groupCount = 0;
|
$this->groupCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function fetchGroupCount(): bool {
|
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$res = $sql->select($sql->count())->from("Group")->execute();
|
|
||||||
$this->success = ($res !== FALSE);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
|
|
||||||
if ($this->success) {
|
|
||||||
$this->groupCount = $res[0]["count"];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function _execute(): bool {
|
public function _execute(): bool {
|
||||||
$page = $this->getParam("page");
|
$page = $this->getParam("page");
|
||||||
if($page < 1) {
|
if($page < 1) {
|
||||||
@ -71,39 +58,42 @@ namespace Core\API\Groups {
|
|||||||
return $this->createError("Invalid fetch count");
|
return $this->createError("Invalid fetch count");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->fetchGroupCount()) {
|
$sql = $this->context->getSQL();
|
||||||
return false;
|
$groupCount = Group::count($sql);
|
||||||
|
if ($groupCount === false) {
|
||||||
|
return $this->createError("Error fetching group count: " . $sql->getLastError());
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
$groups = Group::findBy(Group::createBuilder($sql, false)
|
||||||
$res = $sql->select("Group.id as groupId", "Group.name as groupName", "Group.color as groupColor", $sql->count("UserGroup.user_id"))
|
->orderBy("id")
|
||||||
->from("Group")
|
|
||||||
->leftJoin("UserGroup", "UserGroup.group_id", "Group.id")
|
|
||||||
->groupBy("Group.id")
|
|
||||||
->orderBy("Group.id")
|
|
||||||
->ascending()
|
->ascending()
|
||||||
->limit($count)
|
->limit($count)
|
||||||
->offset(($page - 1) * $count)
|
->offset(($page - 1) * $count));
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->success = ($res !== FALSE);
|
if ($groups !== false) {
|
||||||
$this->lastError = $sql->getLastError();
|
$this->result["groups"] = [];
|
||||||
|
|
||||||
if($this->success) {
|
|
||||||
$this->result["groups"] = array();
|
|
||||||
foreach($res as $row) {
|
|
||||||
$groupId = intval($row["groupId"]);
|
|
||||||
$groupName = $row["groupName"];
|
|
||||||
$groupColor = $row["groupColor"];
|
|
||||||
$memberCount = $row["usergroup_user_id_count"];
|
|
||||||
$this->result["groups"][$groupId] = array(
|
|
||||||
"name" => $groupName,
|
|
||||||
"memberCount" => $memberCount,
|
|
||||||
"color" => $groupColor,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$this->result["pageCount"] = intval(ceil($this->groupCount / $count));
|
$this->result["pageCount"] = intval(ceil($this->groupCount / $count));
|
||||||
$this->result["totalCount"] = $this->groupCount;
|
$this->result["totalCount"] = $this->groupCount;
|
||||||
|
|
||||||
|
foreach ($groups as $groupId => $group) {
|
||||||
|
$this->result["groups"][$groupId] = $group->jsonSerialize();
|
||||||
|
$this->result["groups"][$groupId]["memberCount"] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nmTable = NMRelation::buildTableName("User", "Group");
|
||||||
|
$res = $sql->select("group_id", $sql->count("user_id"))
|
||||||
|
->from($nmTable)
|
||||||
|
->groupBy("group_id")
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
if (is_array($res)) {
|
||||||
|
foreach ($res as $row) {
|
||||||
|
list ($groupId, $memberCount) = [$row["group_id"], $row["user_id_count"]];
|
||||||
|
if (isset($this->result["groups"][$groupId])) {
|
||||||
|
$this->result["groups"][$groupId]["memberCount"] = $memberCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success;
|
return $this->success;
|
||||||
@ -162,7 +152,7 @@ namespace Core\API\Groups {
|
|||||||
|
|
||||||
public function _execute(): bool {
|
public function _execute(): bool {
|
||||||
$id = $this->getParam("id");
|
$id = $this->getParam("id");
|
||||||
if (in_array($id, DEFAULT_GROUPS)) {
|
if (in_array($id, array_keys(Group::GROUPS))) {
|
||||||
return $this->createError("You cannot delete a default group.");
|
return $this->createError("You cannot delete a default group.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,8 @@ namespace Core\API\Logs {
|
|||||||
$shownLogLevels = array_slice(Logger::LOG_LEVELS, $logLevel);
|
$shownLogLevels = array_slice(Logger::LOG_LEVELS, $logLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = SystemLog::findAllBuilder($sql)
|
|
||||||
|
$query = SystemLog::createBuilder($sql, false)
|
||||||
->orderBy("timestamp")
|
->orderBy("timestamp")
|
||||||
->descending();
|
->descending();
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ namespace Core\API\Logs {
|
|||||||
$query->where(new CondIn(new Column("severity"), $shownLogLevels));
|
$query->where(new CondIn(new Column("severity"), $shownLogLevels));
|
||||||
}
|
}
|
||||||
|
|
||||||
$logEntries = $query->execute();
|
$logEntries = SystemLog::findBy($query);
|
||||||
$this->success = $logEntries !== false;
|
$this->success = $logEntries !== false;
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ namespace Core\API\Mail {
|
|||||||
use Core\API\MailAPI;
|
use Core\API\MailAPI;
|
||||||
use Core\API\Parameter\Parameter;
|
use Core\API\Parameter\Parameter;
|
||||||
use Core\API\Parameter\StringType;
|
use Core\API\Parameter\StringType;
|
||||||
|
use Core\Objects\DatabaseEntity\MailQueueItem;
|
||||||
use DateTimeInterface;
|
use DateTimeInterface;
|
||||||
use Core\Driver\SQL\Column\Column;
|
use Core\Driver\SQL\Column\Column;
|
||||||
use Core\Driver\SQL\Condition\Compare;
|
use Core\Driver\SQL\Condition\Compare;
|
||||||
@ -84,7 +85,7 @@ namespace Core\API\Mail {
|
|||||||
class Send extends MailAPI {
|
class Send extends MailAPI {
|
||||||
public function __construct(Context $context, $externalCall = false) {
|
public function __construct(Context $context, $externalCall = false) {
|
||||||
parent::__construct($context, $externalCall, array(
|
parent::__construct($context, $externalCall, array(
|
||||||
'to' => new Parameter('to', Parameter::TYPE_EMAIL, true, null),
|
'to' => new Parameter('to', Parameter::TYPE_EMAIL),
|
||||||
'subject' => new StringType('subject', -1),
|
'subject' => new StringType('subject', -1),
|
||||||
'body' => new StringType('body', -1),
|
'body' => new StringType('body', -1),
|
||||||
'replyTo' => new Parameter('replyTo', Parameter::TYPE_EMAIL, true, null),
|
'replyTo' => new Parameter('replyTo', Parameter::TYPE_EMAIL, true, null),
|
||||||
@ -104,7 +105,7 @@ namespace Core\API\Mail {
|
|||||||
|
|
||||||
$fromMail = $mailConfig->getProperty('from');
|
$fromMail = $mailConfig->getProperty('from');
|
||||||
$mailFooter = $mailConfig->getProperty('mail_footer');
|
$mailFooter = $mailConfig->getProperty('mail_footer');
|
||||||
$toMail = $this->getParam('to') ?? $fromMail;
|
$toMail = $this->getParam('to');
|
||||||
$subject = $this->getParam('subject');
|
$subject = $this->getParam('subject');
|
||||||
$replyTo = $this->getParam('replyTo');
|
$replyTo = $this->getParam('replyTo');
|
||||||
$replyName = $this->getParam('replyName');
|
$replyName = $this->getParam('replyName');
|
||||||
@ -119,10 +120,8 @@ namespace Core\API\Mail {
|
|||||||
|
|
||||||
if ($mailAsync) {
|
if ($mailAsync) {
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$this->success = $sql->insert("MailQueue", ["from", "to", "subject", "body",
|
$mailQueueItem = new MailQueueItem($fromMail, $toMail, $subject, $body, $replyTo, $replyName, $gpgFingerprint);
|
||||||
"replyTo", "replyName", "gpgFingerprint"])
|
$this->success = $mailQueueItem->save($sql);
|
||||||
->addRow($fromMail, $toMail, $subject, $body, $replyTo, $replyName, $gpgFingerprint)
|
|
||||||
->execute() !== false;
|
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
return $this->success;
|
return $this->success;
|
||||||
}
|
}
|
||||||
@ -223,77 +222,37 @@ namespace Core\API\Mail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$res = $sql->select("id", "from", "to", "subject", "body",
|
$mailQueueItems = MailQueueItem::findBy(MailQueueItem::createBuilder($sql, false)
|
||||||
"replyTo", "replyName", "gpgFingerprint", "retryCount")
|
|
||||||
->from("MailQueue")
|
|
||||||
->where(new Compare("retryCount", 0, ">"))
|
->where(new Compare("retryCount", 0, ">"))
|
||||||
->where(new Compare("status", "waiting"))
|
->where(new Compare("status", "waiting"))
|
||||||
->where(new Compare("nextTry", $sql->now(), "<="))
|
->where(new Compare("nextTry", $sql->now(), "<=")));
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->success = ($res !== false);
|
$this->success = ($mailQueueItems !== false);
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
|
|
||||||
if ($this->success && is_array($res)) {
|
if ($this->success && is_array($mailQueueItems)) {
|
||||||
if ($debug) {
|
if ($debug) {
|
||||||
echo "Found " . count($res) . " mails to send" . PHP_EOL;
|
echo "Found " . count($mailQueueItems) . " mails to send" . PHP_EOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
$successfulMails = [];
|
$successfulMails = 0;
|
||||||
foreach ($res as $row) {
|
foreach ($mailQueueItems as $mailQueueItem) {
|
||||||
|
|
||||||
if (time() - $startTime >= 45) {
|
if (time() - $startTime >= 45) {
|
||||||
$this->lastError = "Not able to process whole mail queue within 45 seconds, will continue on next time";
|
$this->lastError = "Not able to process whole mail queue within 45 seconds, will continue on next time";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$to = $row["to"];
|
|
||||||
$subject = $row["subject"];
|
|
||||||
|
|
||||||
if ($debug) {
|
if ($debug) {
|
||||||
echo "Sending subject=$subject to=$to" . PHP_EOL;
|
echo "Sending subject=$mailQueueItem->subject to=$mailQueueItem->to" . PHP_EOL;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mailId = intval($row["id"]);
|
if ($mailQueueItem->send($this->context)) {
|
||||||
$retryCount = intval($row["retryCount"]);
|
$successfulMails++;
|
||||||
$req = new Send($this->context);
|
|
||||||
$args = [
|
|
||||||
"to" => $to,
|
|
||||||
"subject" => $subject,
|
|
||||||
"body" => $row["body"],
|
|
||||||
"replyTo" => $row["replyTo"],
|
|
||||||
"replyName" => $row["replyName"],
|
|
||||||
"gpgFingerprint" => $row["gpgFingerprint"],
|
|
||||||
"async" => false
|
|
||||||
];
|
|
||||||
$success = $req->execute($args);
|
|
||||||
$error = $req->getLastError();
|
|
||||||
|
|
||||||
if (!$success) {
|
|
||||||
$delay = [0, 720, 360, 60, 30, 1];
|
|
||||||
$minutes = $delay[max(0, min(count($delay) - 1, $retryCount))];
|
|
||||||
$nextTry = (new \DateTime())->modify("+$minutes minute");
|
|
||||||
$sql->update("MailQueue")
|
|
||||||
->set("retryCount", $retryCount - 1)
|
|
||||||
->set("status", "error")
|
|
||||||
->set("errorMessage", $error)
|
|
||||||
->set("nextTry", $nextTry)
|
|
||||||
->where(new Compare("id", $mailId))
|
|
||||||
->execute();
|
|
||||||
} else {
|
|
||||||
$successfulMails[] = $mailId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->success = count($successfulMails) === count($res);
|
$this->success = $successfulMails === count($mailQueueItems);
|
||||||
if (!empty($successfulMails)) {
|
|
||||||
$res = $sql->update("MailQueue")
|
|
||||||
->set("status", "success")
|
|
||||||
->where(new CondIn(new Column("id"), $successfulMails))
|
|
||||||
->execute();
|
|
||||||
$this->success = $res !== false;
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success;
|
return $this->success;
|
||||||
|
@ -19,6 +19,7 @@ namespace Core\API\News {
|
|||||||
use Core\API\Parameter\StringType;
|
use Core\API\Parameter\StringType;
|
||||||
use Core\Driver\SQL\Condition\Compare;
|
use Core\Driver\SQL\Condition\Compare;
|
||||||
use Core\Objects\Context;
|
use Core\Objects\Context;
|
||||||
|
use Core\Objects\DatabaseEntity\Group;
|
||||||
use Core\Objects\DatabaseEntity\News;
|
use Core\Objects\DatabaseEntity\News;
|
||||||
|
|
||||||
class Get extends NewsAPI {
|
class Get extends NewsAPI {
|
||||||
@ -40,7 +41,7 @@ namespace Core\API\News {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$newsQuery = News::findAllBuilder($sql)
|
$newsQuery = News::createBuilder($sql, false)
|
||||||
->limit($limit)
|
->limit($limit)
|
||||||
->orderBy("published_at")
|
->orderBy("published_at")
|
||||||
->descending()
|
->descending()
|
||||||
@ -50,7 +51,7 @@ namespace Core\API\News {
|
|||||||
$newsQuery->where(new Compare("published_at", $since, ">="));
|
$newsQuery->where(new Compare("published_at", $since, ">="));
|
||||||
}
|
}
|
||||||
|
|
||||||
$newsArray = $newsQuery->execute();
|
$newsArray = News::findBy($newsQuery);
|
||||||
$this->success = $newsArray !== null;
|
$this->success = $newsArray !== null;
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
|
|
||||||
@ -113,7 +114,7 @@ namespace Core\API\News {
|
|||||||
return false;
|
return false;
|
||||||
} else if ($news === null) {
|
} else if ($news === null) {
|
||||||
return $this->createError("News Post not found");
|
return $this->createError("News Post not found");
|
||||||
} else if ($news->publishedBy->getId() !== $currentUser->getId() && !$currentUser->hasGroup(USER_GROUP_ADMIN)) {
|
} else if ($news->publishedBy->getId() !== $currentUser->getId() && !$currentUser->hasGroup(Group::ADMIN)) {
|
||||||
return $this->createError("You do not have permissions to delete news post of other users.");
|
return $this->createError("You do not have permissions to delete news post of other users.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +145,7 @@ namespace Core\API\News {
|
|||||||
return false;
|
return false;
|
||||||
} else if ($news === null) {
|
} else if ($news === null) {
|
||||||
return $this->createError("News Post not found");
|
return $this->createError("News Post not found");
|
||||||
} else if ($news->publishedBy->getId() !== $currentUser->getId() && !$currentUser->hasGroup(USER_GROUP_ADMIN)) {
|
} else if ($news->publishedBy->getId() !== $currentUser->getId() && !$currentUser->hasGroup(Group::ADMIN)) {
|
||||||
return $this->createError("You do not have permissions to edit news post of other users.");
|
return $this->createError("You do not have permissions to edit news post of other users.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,231 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Core\API {
|
|
||||||
|
|
||||||
use Core\Objects\Context;
|
|
||||||
|
|
||||||
abstract class NotificationsAPI extends Request {
|
|
||||||
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
|
|
||||||
parent::__construct($context, $externalCall, $params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Core\API\Notifications {
|
|
||||||
|
|
||||||
use Core\API\NotificationsAPI;
|
|
||||||
use Core\API\Parameter\Parameter;
|
|
||||||
use Core\API\Parameter\StringType;
|
|
||||||
use Core\Driver\SQL\Column\Column;
|
|
||||||
use Core\Driver\SQL\Condition\Compare;
|
|
||||||
use Core\Driver\SQL\Condition\CondIn;
|
|
||||||
use Core\Driver\SQL\Query\Select;
|
|
||||||
use Core\Objects\Context;
|
|
||||||
use Core\Objects\DatabaseEntity\Group;
|
|
||||||
use Core\Objects\DatabaseEntity\Notification;
|
|
||||||
use Core\Objects\DatabaseEntity\User;
|
|
||||||
|
|
||||||
class Create extends NotificationsAPI {
|
|
||||||
|
|
||||||
public function __construct(Context $context, $externalCall = false) {
|
|
||||||
parent::__construct($context, $externalCall, array(
|
|
||||||
'groupId' => new Parameter('groupId', Parameter::TYPE_INT, true),
|
|
||||||
'userId' => new Parameter('userId', Parameter::TYPE_INT, true),
|
|
||||||
'title' => new StringType('title', 32),
|
|
||||||
'message' => new StringType('message', 256),
|
|
||||||
));
|
|
||||||
$this->isPublic = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function insertUserNotification($userId, $notificationId): bool {
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$res = $sql->insert("UserNotification", array("user_id", "notification_id"))
|
|
||||||
->addRow($userId, $notificationId)
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->success = ($res !== FALSE);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function insertGroupNotification($groupId, $notificationId): bool {
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$res = $sql->insert("GroupNotification", array("group_id", "notification_id"))
|
|
||||||
->addRow($groupId, $notificationId)
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->success = ($res !== FALSE);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function createNotification($title, $message): bool|int {
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$notification = new Notification();
|
|
||||||
$notification->title = $title;
|
|
||||||
$notification->message = $message;
|
|
||||||
|
|
||||||
$this->success = ($notification->save($sql) !== FALSE);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
|
|
||||||
if ($this->success) {
|
|
||||||
return $notification->getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function _execute(): bool {
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$userId = $this->getParam("userId");
|
|
||||||
$groupId = $this->getParam("groupId");
|
|
||||||
$title = $this->getParam("title");
|
|
||||||
$message = $this->getParam("message");
|
|
||||||
|
|
||||||
if (is_null($userId) && is_null($groupId)) {
|
|
||||||
return $this->createError("Either userId or groupId must be specified.");
|
|
||||||
} else if(!is_null($userId) && !is_null($groupId)) {
|
|
||||||
return $this->createError("Only one of userId and groupId must be specified.");
|
|
||||||
} else if(!is_null($userId)) {
|
|
||||||
if (User::exists($sql, $userId)) {
|
|
||||||
$id = $this->createNotification($title, $message);
|
|
||||||
if ($this->success) {
|
|
||||||
return $this->insertUserNotification($userId, $id);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return $this->createError("User not found: $userId");
|
|
||||||
}
|
|
||||||
} else if(!is_null($groupId)) {
|
|
||||||
if (Group::exists($sql, $groupId)) {
|
|
||||||
$id = $this->createNotification($title, $message);
|
|
||||||
if ($this->success) {
|
|
||||||
return $this->insertGroupNotification($groupId, $id);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return $this->createError("Group not found: $groupId");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Fetch extends NotificationsAPI {
|
|
||||||
|
|
||||||
private array $notifications;
|
|
||||||
private array $notificationIds;
|
|
||||||
|
|
||||||
public function __construct(Context $context, $externalCall = false) {
|
|
||||||
parent::__construct($context, $externalCall, array(
|
|
||||||
'new' => new Parameter('new', Parameter::TYPE_BOOLEAN, true, true)
|
|
||||||
));
|
|
||||||
$this->loginRequired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fetchUserNotifications(): bool {
|
|
||||||
$onlyNew = $this->getParam('new');
|
|
||||||
$userId = $this->context->getUser()->getId();
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$query = $sql->select($sql->distinct("Notification.id"), "created_at", "title", "message", "type")
|
|
||||||
->from("Notification")
|
|
||||||
->innerJoin("UserNotification", "UserNotification.notification_id", "Notification.id")
|
|
||||||
->where(new Compare("UserNotification.user_id", $userId))
|
|
||||||
->orderBy("created_at")->descending();
|
|
||||||
|
|
||||||
if ($onlyNew) {
|
|
||||||
$query->where(new Compare("UserNotification.seen", false));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->fetchNotifications($query);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fetchGroupNotifications(): bool {
|
|
||||||
$onlyNew = $this->getParam('new');
|
|
||||||
$userId = $this->context->getUser()->getId();
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$query = $sql->select($sql->distinct("Notification.id"), "created_at", "title", "message", "type")
|
|
||||||
->from("Notification")
|
|
||||||
->innerJoin("GroupNotification", "GroupNotification.notification_id", "Notification.id")
|
|
||||||
->innerJoin("UserGroup", "GroupNotification.group_id", "UserGroup.group_id")
|
|
||||||
->where(new Compare("UserGroup.user_id", $userId))
|
|
||||||
->orderBy("created_at")->descending();
|
|
||||||
|
|
||||||
if ($onlyNew) {
|
|
||||||
$query->where(new Compare("GroupNotification.seen", false));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->fetchNotifications($query);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fetchNotifications(Select $query): bool {
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$res = $query->execute();
|
|
||||||
$this->success = ($res !== FALSE);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
|
|
||||||
if ($this->success) {
|
|
||||||
foreach($res as $row) {
|
|
||||||
$id = $row["id"];
|
|
||||||
if (!in_array($id, $this->notificationIds)) {
|
|
||||||
$this->notificationIds[] = $id;
|
|
||||||
$this->notifications[] = array(
|
|
||||||
"id" => $id,
|
|
||||||
"title" => $row["title"],
|
|
||||||
"message" => $row["message"],
|
|
||||||
"created_at" => $row["created_at"],
|
|
||||||
"type" => $row["type"]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function _execute(): bool {
|
|
||||||
$this->notifications = array();
|
|
||||||
$this->notificationIds = array();
|
|
||||||
if ($this->fetchUserNotifications() && $this->fetchGroupNotifications()) {
|
|
||||||
$this->result["notifications"] = $this->notifications;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Seen extends NotificationsAPI {
|
|
||||||
|
|
||||||
public function __construct(Context $context, bool $externalCall = false) {
|
|
||||||
parent::__construct($context, $externalCall, array());
|
|
||||||
$this->loginRequired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function _execute(): bool {
|
|
||||||
|
|
||||||
$currentUser = $this->context->getUser();
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$res = $sql->update("UserNotification")
|
|
||||||
->set("seen", true)
|
|
||||||
->where(new Compare("user_id", $currentUser->getId()))
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->success = ($res !== FALSE);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
|
|
||||||
if ($this->success) {
|
|
||||||
$res = $sql->update("GroupNotification")
|
|
||||||
->set("seen", true)
|
|
||||||
->where(new CondIn(new Column("group_id"),
|
|
||||||
$sql->select("group_id")
|
|
||||||
->from("UserGroup")
|
|
||||||
->where(new Compare("user_id", $currentUser->getId()))))
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->success = ($res !== FALSE);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,6 +3,7 @@
|
|||||||
namespace Core\API {
|
namespace Core\API {
|
||||||
|
|
||||||
use Core\Objects\Context;
|
use Core\Objects\Context;
|
||||||
|
use Core\Objects\DatabaseEntity\Group;
|
||||||
|
|
||||||
abstract class PermissionAPI extends Request {
|
abstract class PermissionAPI extends Request {
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ namespace Core\API {
|
|||||||
|
|
||||||
protected function checkStaticPermission(): bool {
|
protected function checkStaticPermission(): bool {
|
||||||
$user = $this->context->getUser();
|
$user = $this->context->getUser();
|
||||||
if (!$user || !$user->hasGroup(USER_GROUP_ADMIN)) {
|
if (!$user || !$user->hasGroup(Group::ADMIN)) {
|
||||||
return $this->createError("Permission denied.");
|
return $this->createError("Permission denied.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +35,6 @@ namespace Core\API\Permission {
|
|||||||
use Core\Driver\SQL\Strategy\UpdateStrategy;
|
use Core\Driver\SQL\Strategy\UpdateStrategy;
|
||||||
use Core\Objects\Context;
|
use Core\Objects\Context;
|
||||||
use Core\Objects\DatabaseEntity\Group;
|
use Core\Objects\DatabaseEntity\Group;
|
||||||
use Core\Objects\DatabaseEntity\User;
|
|
||||||
|
|
||||||
class Check extends PermissionAPI {
|
class Check extends PermissionAPI {
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ namespace Core\API;
|
|||||||
|
|
||||||
use Core\API\Parameter\StringType;
|
use Core\API\Parameter\StringType;
|
||||||
use Core\Objects\Context;
|
use Core\Objects\Context;
|
||||||
|
use Core\Objects\DatabaseEntity\Group;
|
||||||
use Core\Objects\DatabaseEntity\User;
|
use Core\Objects\DatabaseEntity\User;
|
||||||
|
|
||||||
class Swagger extends Request {
|
class Swagger extends Request {
|
||||||
@ -90,7 +91,7 @@ class Swagger extends Request {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// special case: hardcoded permission
|
// special case: hardcoded permission
|
||||||
if ($request instanceof Permission\Save && (!$currentUser || !$currentUser->hasGroup(USER_GROUP_ADMIN))) {
|
if ($request instanceof Permission\Save && (!$currentUser || !$currentUser->hasGroup(Group::ADMIN))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ namespace Core\API {
|
|||||||
$this->checkPasswordRequirements($password, $confirmPassword);
|
$this->checkPasswordRequirements($password, $confirmPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function insertUser($username, $email, $password, $confirmed, $fullName = ""): bool|User {
|
protected function insertUser(string $username, ?string $email, string $password, bool $confirmed, string $fullName = "", array $groups = []): bool|User {
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
|
|
||||||
$user = new User();
|
$user = new User();
|
||||||
@ -86,6 +86,7 @@ namespace Core\API {
|
|||||||
$user->email = $email;
|
$user->email = $email;
|
||||||
$user->confirmed = $confirmed;
|
$user->confirmed = $confirmed;
|
||||||
$user->fullName = $fullName ?? "";
|
$user->fullName = $fullName ?? "";
|
||||||
|
$user->groups = $groups;
|
||||||
|
|
||||||
$this->success = ($user->save($sql) !== FALSE);
|
$this->success = ($user->save($sql) !== FALSE);
|
||||||
$this->lastError = $sql->getLastError();
|
$this->lastError = $sql->getLastError();
|
||||||
@ -107,12 +108,11 @@ namespace Core\API {
|
|||||||
|
|
||||||
protected function checkToken(string $token) : UserToken|bool {
|
protected function checkToken(string $token) : UserToken|bool {
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$userToken = UserToken::findBuilder($sql)
|
$userToken = UserToken::findBy(UserToken::createBuilder($sql, true)
|
||||||
->where(new Compare("UserToken.token", $token))
|
->where(new Compare("UserToken.token", $token))
|
||||||
->where(new Compare("UserToken.valid_until", $sql->now(), ">"))
|
->where(new Compare("UserToken.valid_until", $sql->now(), ">"))
|
||||||
->where(new Compare("UserToken.used", 0))
|
->where(new Compare("UserToken.used", 0))
|
||||||
->fetchEntities()
|
->fetchEntities());
|
||||||
->execute();
|
|
||||||
|
|
||||||
if ($userToken === false) {
|
if ($userToken === false) {
|
||||||
return $this->createError("Error verifying token: " . $sql->getLastError());
|
return $this->createError("Error verifying token: " . $sql->getLastError());
|
||||||
@ -128,11 +128,16 @@ namespace Core\API {
|
|||||||
|
|
||||||
namespace Core\API\User {
|
namespace Core\API\User {
|
||||||
|
|
||||||
|
use Core\API\Parameter\ArrayType;
|
||||||
use Core\API\Parameter\Parameter;
|
use Core\API\Parameter\Parameter;
|
||||||
use Core\API\Parameter\StringType;
|
use Core\API\Parameter\StringType;
|
||||||
use Core\API\Template\Render;
|
use Core\API\Template\Render;
|
||||||
use Core\API\UserAPI;
|
use Core\API\UserAPI;
|
||||||
use Core\API\VerifyCaptcha;
|
use Core\API\VerifyCaptcha;
|
||||||
|
use Core\Driver\SQL\Condition\CondBool;
|
||||||
|
use Core\Driver\SQL\Condition\CondNot;
|
||||||
|
use Core\Driver\SQL\Condition\CondOr;
|
||||||
|
use Core\Objects\DatabaseEntity\Group;
|
||||||
use Core\Objects\DatabaseEntity\UserToken;
|
use Core\Objects\DatabaseEntity\UserToken;
|
||||||
use Core\Driver\SQL\Column\Column;
|
use Core\Driver\SQL\Column\Column;
|
||||||
use Core\Driver\SQL\Condition\Compare;
|
use Core\Driver\SQL\Condition\Compare;
|
||||||
@ -146,12 +151,15 @@ namespace Core\API\User {
|
|||||||
|
|
||||||
class Create extends UserAPI {
|
class Create extends UserAPI {
|
||||||
|
|
||||||
|
private User $user;
|
||||||
|
|
||||||
public function __construct(Context $context, $externalCall = false) {
|
public function __construct(Context $context, $externalCall = false) {
|
||||||
parent::__construct($context, $externalCall, array(
|
parent::__construct($context, $externalCall, array(
|
||||||
'username' => new StringType('username', 32),
|
'username' => new StringType('username', 32),
|
||||||
'email' => new Parameter('email', Parameter::TYPE_EMAIL, true, NULL),
|
'email' => new Parameter('email', Parameter::TYPE_EMAIL, true, NULL),
|
||||||
'password' => new StringType('password'),
|
'password' => new StringType('password'),
|
||||||
'confirmPassword' => new StringType('confirmPassword'),
|
'confirmPassword' => new StringType('confirmPassword'),
|
||||||
|
'groups' => new ArrayType("groups", Parameter::TYPE_INT, true, true, [])
|
||||||
));
|
));
|
||||||
|
|
||||||
$this->loginRequired = true;
|
$this->loginRequired = true;
|
||||||
@ -171,21 +179,36 @@ namespace Core\API\User {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$groups = [];
|
||||||
|
$sql = $this->context->getSQL();
|
||||||
|
$requestedGroups = array_unique($this->getParam("groups"));
|
||||||
|
if (!empty($requestedGroups)) {
|
||||||
|
$groups = Group::findAll($sql, new CondIn(new Column("id"), $requestedGroups));
|
||||||
|
foreach ($requestedGroups as $groupId) {
|
||||||
|
if (!isset($groups[$groupId])) {
|
||||||
|
return $this->createError("Group with id=$groupId does not exist.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// prevent duplicate keys
|
// prevent duplicate keys
|
||||||
$email = (!is_null($email) && empty($email)) ? null : $email;
|
$email = (!is_null($email) && empty($email)) ? null : $email;
|
||||||
$user = $this->insertUser($username, $email, $password, true);
|
$user = $this->insertUser($username, $email, $password, true, "", $groups);
|
||||||
if ($user !== false) {
|
if ($user !== false) {
|
||||||
|
$this->user = $user;
|
||||||
$this->result["userId"] = $user->getId();
|
$this->result["userId"] = $user->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success;
|
return $this->success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getUser(): User {
|
||||||
|
return $this->user;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Fetch extends UserAPI {
|
class Fetch extends UserAPI {
|
||||||
|
|
||||||
private int $userCount;
|
|
||||||
|
|
||||||
public function __construct(Context $context, $externalCall = false) {
|
public function __construct(Context $context, $externalCall = false) {
|
||||||
parent::__construct($context, $externalCall, array(
|
parent::__construct($context, $externalCall, array(
|
||||||
'page' => new Parameter('page', Parameter::TYPE_INT, true, 1),
|
'page' => new Parameter('page', Parameter::TYPE_INT, true, 1),
|
||||||
@ -193,21 +216,7 @@ namespace Core\API\User {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getUserCount(): bool {
|
private function selectIds($page, $count): array|bool {
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$res = $sql->select($sql->count())->from("User")->execute();
|
|
||||||
$this->success = ($res !== FALSE);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
|
|
||||||
if ($this->success) {
|
|
||||||
$this->userCount = $res[0]["count"];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function selectIds($page, $count) {
|
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$res = $sql->select("User.id")
|
$res = $sql->select("User.id")
|
||||||
->from("User")
|
->from("User")
|
||||||
@ -241,72 +250,57 @@ namespace Core\API\User {
|
|||||||
return $this->createError("Invalid fetch count");
|
return $this->createError("Invalid fetch count");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->getUserCount()) {
|
$condition = null;
|
||||||
return false;
|
$currentUser = $this->context->getUser();
|
||||||
}
|
$fullInfo = ($currentUser->hasGroup(Group::ADMIN) ||
|
||||||
|
$currentUser->hasGroup(Group::SUPPORT));
|
||||||
$userIds = $this->selectIds($page, $count);
|
if (!$fullInfo) {
|
||||||
if ($userIds === false) {
|
$condition = new CondOr(
|
||||||
return false;
|
new Compare("User.id", $currentUser->getId()),
|
||||||
|
new CondBool("User.confirmed")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$res = $sql->select("User.id as userId", "User.name", "User.email", "User.registered_at", "User.confirmed",
|
$userCount = User::count($sql, $condition);
|
||||||
"User.profile_picture", "User.full_name", "Group.id as groupId", "User.last_online",
|
if ($userCount === false) {
|
||||||
"Group.name as groupName", "Group.color as groupColor")
|
return $this->createError("Error fetching user count: " . $sql->getLastError());
|
||||||
->from("User")
|
}
|
||||||
->leftJoin("UserGroup", "User.id", "UserGroup.user_id")
|
|
||||||
->leftJoin("Group", "Group.id", "UserGroup.group_id")
|
|
||||||
->where(new CondIn(new Column("User.id"), $userIds))
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->success = ($res !== FALSE);
|
$userQuery = User::createBuilder($sql, false)
|
||||||
$this->lastError = $sql->getLastError();
|
->orderBy("id")
|
||||||
$currentUser = $this->context->getUser();
|
->ascending()
|
||||||
|
->limit($count)
|
||||||
|
->offset(($page - 1) * $count)
|
||||||
|
->fetchEntities();
|
||||||
|
|
||||||
if ($this->success) {
|
if ($condition) {
|
||||||
$this->result["users"] = array();
|
$userQuery->where($condition);
|
||||||
foreach ($res as $row) {
|
}
|
||||||
$userId = intval($row["userId"]);
|
|
||||||
$groupId = $row["groupId"];
|
|
||||||
$groupName = $row["groupName"];
|
|
||||||
$groupColor = $row["groupColor"];
|
|
||||||
|
|
||||||
$fullInfo = ($userId === $currentUser->getId() ||
|
$users = User::findBy($userQuery);
|
||||||
$currentUser->hasGroup(USER_GROUP_ADMIN) ||
|
if ($users !== false) {
|
||||||
$currentUser->hasGroup(USER_GROUP_SUPPORT));
|
$this->result["users"] = [];
|
||||||
|
|
||||||
if (!isset($this->result["users"][$userId])) {
|
foreach ($users as $userId => $user) {
|
||||||
$user = array(
|
$serialized = $user->jsonSerialize();
|
||||||
"id" => $userId,
|
|
||||||
"name" => $row["name"],
|
|
||||||
"fullName" => $row["full_name"],
|
|
||||||
"profilePicture" => $row["profile_picture"],
|
|
||||||
"email" => $row["email"],
|
|
||||||
"confirmed" => $sql->parseBool($row["confirmed"]),
|
|
||||||
"groups" => array(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($fullInfo) {
|
if (!$fullInfo && $userId !== $currentUser->getId()) {
|
||||||
$user["registered_at"] = $row["registered_at"];
|
$publicAttributes = ["id", "name", "fullName", "profilePicture", "email", "groups"];
|
||||||
$user["last_online"] = $row["last_online"];
|
foreach (array_keys($serialized) as $attr) {
|
||||||
} else if (!$sql->parseBool($row["confirmed"])) {
|
if (!in_array($attr, $publicAttributes)) {
|
||||||
continue;
|
unset($serialized[$attr]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->result["users"][$userId] = $user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!is_null($groupId)) {
|
$this->result["users"][$userId] = $serialized;
|
||||||
$this->result["users"][$userId]["groups"][intval($groupId)] = array(
|
|
||||||
"id" => intval($groupId),
|
|
||||||
"name" => $groupName,
|
|
||||||
"color" => $groupColor
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->result["pageCount"] = intval(ceil($this->userCount / $count));
|
$this->result["pageCount"] = intval(ceil($this->userCount / $count));
|
||||||
$this->result["totalCount"] = $this->userCount;
|
$this->result["totalCount"] = $this->userCount;
|
||||||
|
} else {
|
||||||
|
return $this->createError("Error fetching users: " . $sql->getLastError());
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->success;
|
return $this->success;
|
||||||
@ -338,13 +332,13 @@ namespace Core\API\User {
|
|||||||
// either we are querying own info or we are support / admin
|
// either we are querying own info or we are support / admin
|
||||||
$currentUser = $this->context->getUser();
|
$currentUser = $this->context->getUser();
|
||||||
$canView = ($userId === $currentUser->getId() ||
|
$canView = ($userId === $currentUser->getId() ||
|
||||||
$currentUser->hasGroup(USER_GROUP_ADMIN) ||
|
$currentUser->hasGroup(Group::ADMIN) ||
|
||||||
$currentUser->hasGroup(USER_GROUP_SUPPORT));
|
$currentUser->hasGroup(Group::SUPPORT));
|
||||||
|
|
||||||
// full info only when we have administrative privileges, or we are querying ourselves
|
// full info only when we have administrative privileges, or we are querying ourselves
|
||||||
$fullInfo = ($userId === $currentUser->getId() ||
|
$fullInfo = ($userId === $currentUser->getId() ||
|
||||||
$currentUser->hasGroup(USER_GROUP_ADMIN) ||
|
$currentUser->hasGroup(Group::ADMIN) ||
|
||||||
$currentUser->hasGroup(USER_GROUP_SUPPORT));
|
$currentUser->hasGroup(Group::SUPPORT));
|
||||||
|
|
||||||
if (!$canView) {
|
if (!$canView) {
|
||||||
|
|
||||||
@ -617,10 +611,9 @@ namespace Core\API\User {
|
|||||||
$stayLoggedIn = $this->getParam('stayLoggedIn');
|
$stayLoggedIn = $this->getParam('stayLoggedIn');
|
||||||
|
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$user = User::findBuilder($sql)
|
$user = User::findBy(User::createBuilder($sql, true)
|
||||||
->where(new Compare("User.name", $username), new Compare("User.email", $username))
|
->where(new Compare("User.name", $username), new Compare("User.email", $username))
|
||||||
->fetchEntities()
|
->fetchEntities());
|
||||||
->execute();
|
|
||||||
|
|
||||||
if ($user !== false) {
|
if ($user !== false) {
|
||||||
if ($user === null) {
|
if ($user === null) {
|
||||||
@ -842,7 +835,7 @@ namespace Core\API\User {
|
|||||||
$groupIds[] = $param->value;
|
$groupIds[] = $param->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($id === $currentUser->getId() && !in_array(USER_GROUP_ADMIN, $groupIds)) {
|
if ($id === $currentUser->getId() && !in_array(Group::ADMIN, $groupIds)) {
|
||||||
return $this->createError("Cannot remove Administrator group from own user.");
|
return $this->createError("Cannot remove Administrator group from own user.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -958,10 +951,9 @@ namespace Core\API\User {
|
|||||||
|
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$email = $this->getParam("email");
|
$email = $this->getParam("email");
|
||||||
$user = User::findBuilder($sql)
|
$user = User::findBy(User::createBuilder($sql, true)
|
||||||
->where(new Compare("email", $email))
|
->where(new Compare("email", $email))
|
||||||
->fetchEntities()
|
->fetchEntities());
|
||||||
->execute();
|
|
||||||
if ($user === false) {
|
if ($user === false) {
|
||||||
return $this->createError("Could not fetch user details: " . $sql->getLastError());
|
return $this->createError("Could not fetch user details: " . $sql->getLastError());
|
||||||
} else if ($user !== null) {
|
} else if ($user !== null) {
|
||||||
@ -1040,10 +1032,9 @@ namespace Core\API\User {
|
|||||||
|
|
||||||
$email = $this->getParam("email");
|
$email = $this->getParam("email");
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
$user = User::findBuilder($sql)
|
$user = User::findBy(User::createBuilder($sql, true)
|
||||||
->where(new Compare("User.email", $email))
|
->where(new Compare("User.email", $email))
|
||||||
->where(new Compare("User.confirmed", false))
|
->where(new Compare("User.confirmed", false)));
|
||||||
->execute();
|
|
||||||
|
|
||||||
if ($user === false) {
|
if ($user === false) {
|
||||||
return $this->createError("Error retrieving user details: " . $sql->getLastError());
|
return $this->createError("Error retrieving user details: " . $sql->getLastError());
|
||||||
@ -1052,11 +1043,10 @@ namespace Core\API\User {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$userToken = UserToken::findBuilder($sql)
|
$userToken = UserToken::findBy(UserToken::createBuilder($sql, true)
|
||||||
->where(new Compare("used", false))
|
->where(new Compare("used", false))
|
||||||
->where(new Compare("tokenType", UserToken::TYPE_EMAIL_CONFIRM))
|
->where(new Compare("tokenType", UserToken::TYPE_EMAIL_CONFIRM))
|
||||||
->where(new Compare("user_id", $user->getId()))
|
->where(new Compare("user_id", $user->getId())));
|
||||||
->execute();
|
|
||||||
|
|
||||||
$validHours = 48;
|
$validHours = 48;
|
||||||
if ($userToken === false) {
|
if ($userToken === false) {
|
||||||
@ -1366,12 +1356,11 @@ namespace Core\API\User {
|
|||||||
$token = $this->getParam("token");
|
$token = $this->getParam("token");
|
||||||
$sql = $this->context->getSQL();
|
$sql = $this->context->getSQL();
|
||||||
|
|
||||||
$userToken = UserToken::findBuilder($sql)
|
$userToken = UserToken::findBy(UserToken::createBuilder($sql, true)
|
||||||
->where(new Compare("token", $token))
|
->where(new Compare("token", $token))
|
||||||
->where(new Compare("valid_until", $sql->now(), ">="))
|
->where(new Compare("valid_until", $sql->now(), ">="))
|
||||||
->where(new Compare("user_id", $currentUser->getId()))
|
->where(new Compare("user_id", $currentUser->getId()))
|
||||||
->where(new Compare("token_type", UserToken::TYPE_GPG_CONFIRM))
|
->where(new Compare("token_type", UserToken::TYPE_GPG_CONFIRM)));
|
||||||
->execute();
|
|
||||||
|
|
||||||
if ($userToken !== false) {
|
if ($userToken !== false) {
|
||||||
if ($userToken === null) {
|
if ($userToken === null) {
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
namespace Core\Configuration;
|
namespace Core\Configuration;
|
||||||
|
|
||||||
use Core\Driver\SQL\SQL;
|
use Core\Driver\SQL\SQL;
|
||||||
use Core\Driver\SQL\Strategy\SetNullStrategy;
|
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||||
use Core\Driver\SQL\Strategy\CascadeStrategy;
|
use Core\Objects\DatabaseEntity\Group;
|
||||||
use Core\Objects\DatabaseEntity\DatabaseEntity;
|
use Core\Objects\DatabaseEntity\Language;
|
||||||
use PHPUnit\Util\Exception;
|
use PHPUnit\Util\Exception;
|
||||||
|
|
||||||
class CreateDatabase extends DatabaseScript {
|
class CreateDatabase extends DatabaseScript {
|
||||||
@ -15,37 +15,16 @@ class CreateDatabase extends DatabaseScript {
|
|||||||
|
|
||||||
self::loadEntities($queries, $sql);
|
self::loadEntities($queries, $sql);
|
||||||
|
|
||||||
$queries[] = $sql->insert("Language", array("code", "name"))
|
$queries[] = Language::getHandler($sql)->getInsertQuery([
|
||||||
->addRow("en_US", 'American English')
|
new Language(Language::AMERICAN_ENGLISH, "en_US", 'American English'),
|
||||||
->addRow("de_DE", 'Deutsch Standard');
|
new Language(Language::AMERICAN_ENGLISH, "de_DE", 'Deutsch Standard'),
|
||||||
|
]);
|
||||||
|
|
||||||
$queries[] = $sql->insert("Group", array("name", "color"))
|
$queries[] = Group::getHandler($sql)->getInsertQuery([
|
||||||
->addRow(USER_GROUP_MODERATOR_NAME, "#007bff")
|
new Group(Group::ADMIN, Group::GROUPS[Group::ADMIN], "#007bff"),
|
||||||
->addRow(USER_GROUP_SUPPORT_NAME, "#28a745")
|
new Group(Group::MODERATOR, Group::GROUPS[Group::MODERATOR], "#28a745"),
|
||||||
->addRow(USER_GROUP_ADMIN_NAME, "#dc3545");
|
new Group(Group::SUPPORT, Group::GROUPS[Group::SUPPORT], "#dc3545"),
|
||||||
|
]);
|
||||||
$queries[] = $sql->createTable("UserGroup")
|
|
||||||
->addInt("user_id")
|
|
||||||
->addInt("group_id")
|
|
||||||
->unique("user_id", "group_id")
|
|
||||||
->foreignKey("user_id", "User", "id", new CascadeStrategy())
|
|
||||||
->foreignKey("group_id", "Group", "id", new CascadeStrategy());
|
|
||||||
|
|
||||||
$queries[] = $sql->createTable("UserNotification")
|
|
||||||
->addInt("user_id")
|
|
||||||
->addInt("notification_id")
|
|
||||||
->addBool("seen", false)
|
|
||||||
->foreignKey("user_id", "User", "id")
|
|
||||||
->foreignKey("notification_id", "Notification", "id")
|
|
||||||
->unique("user_id", "notification_id");
|
|
||||||
|
|
||||||
$queries[] = $sql->createTable("GroupNotification")
|
|
||||||
->addInt("group_id")
|
|
||||||
->addInt("notification_id")
|
|
||||||
->addBool("seen", false)
|
|
||||||
->foreignKey("group_id", "Group", "id")
|
|
||||||
->foreignKey("notification_id", "Notification", "id")
|
|
||||||
->unique("group_id", "notification_id");
|
|
||||||
|
|
||||||
$queries[] = $sql->createTable("Visitor")
|
$queries[] = $sql->createTable("Visitor")
|
||||||
->addInt("day")
|
->addInt("day")
|
||||||
@ -81,92 +60,43 @@ class CreateDatabase extends DatabaseScript {
|
|||||||
->addBool("private", false) // these values are not returned from '/api/settings/get', but can be changed
|
->addBool("private", false) // these values are not returned from '/api/settings/get', but can be changed
|
||||||
->addBool("readonly", false) // these values are neither returned, nor can be changed from outside
|
->addBool("readonly", false) // these values are neither returned, nor can be changed from outside
|
||||||
->primaryKey("name");
|
->primaryKey("name");
|
||||||
|
$settingsQuery = $sql->insert("Settings", array("name", "value", "private", "readonly"));
|
||||||
$settingsQuery = $sql->insert("Settings", array("name", "value", "private", "readonly"))
|
|
||||||
// ->addRow("mail_enabled", "0") # this key will be set during installation
|
|
||||||
->addRow("mail_host", "", false, false)
|
|
||||||
->addRow("mail_port", "", false, false)
|
|
||||||
->addRow("mail_username", "", false, false)
|
|
||||||
->addRow("mail_password", "", true, false)
|
|
||||||
->addRow("mail_from", "", false, false)
|
|
||||||
->addRow("mail_last_sync", "", false, false)
|
|
||||||
->addRow("mail_footer", "", false, false);
|
|
||||||
|
|
||||||
(Settings::loadDefaults())->addRows($settingsQuery);
|
(Settings::loadDefaults())->addRows($settingsQuery);
|
||||||
$queries[] = $settingsQuery;
|
$queries[] = $settingsQuery;
|
||||||
|
|
||||||
$queries[] = $sql->createTable("ContactRequest")
|
|
||||||
->addSerial("id")
|
|
||||||
->addString("from_name", 32)
|
|
||||||
->addString("from_email", 64)
|
|
||||||
->addString("message", 512)
|
|
||||||
->addString("messageId", 78, true) # null = don't sync with mails (usually if mail could not be sent)
|
|
||||||
->addDateTime("created_at", false, $sql->currentTimestamp())
|
|
||||||
->unique("messageId")
|
|
||||||
->primaryKey("id");
|
|
||||||
|
|
||||||
$queries[] = $sql->createTable("ContactMessage")
|
|
||||||
->addSerial("id")
|
|
||||||
->addInt("request_id")
|
|
||||||
->addInt("user_id", true) # null = customer has sent this message
|
|
||||||
->addString("message", 512)
|
|
||||||
->addString("messageId", 78)
|
|
||||||
->addDateTime("created_at", false, $sql->currentTimestamp())
|
|
||||||
->addBool("read", false)
|
|
||||||
->unique("messageId")
|
|
||||||
->primaryKey("id")
|
|
||||||
->foreignKey("request_id", "ContactRequest", "id", new CascadeStrategy())
|
|
||||||
->foreignKey("user_id", "User", "id", new SetNullStrategy());
|
|
||||||
|
|
||||||
$queries[] = $sql->createTable("ApiPermission")
|
$queries[] = $sql->createTable("ApiPermission")
|
||||||
->addString("method", 32)
|
->addString("method", 32)
|
||||||
->addJson("groups", true, '[]')
|
->addJson("groups", true, '[]')
|
||||||
->addString("description", 128, false, "")
|
->addString("description", 128, false, "")
|
||||||
->primaryKey("method");
|
->primaryKey("method");
|
||||||
|
|
||||||
$queries[] = $sql->createTable("MailQueue")
|
|
||||||
->addSerial("id")
|
|
||||||
->addString("from", 64)
|
|
||||||
->addString("to", 64)
|
|
||||||
->addString("subject")
|
|
||||||
->addString("body")
|
|
||||||
->addString("replyTo", 64, true)
|
|
||||||
->addString("replyName", 32, true)
|
|
||||||
->addString("gpgFingerprint", 64, true)
|
|
||||||
->addEnum("status", ["waiting", "success", "error"], false, 'waiting')
|
|
||||||
->addInt("retryCount", false, 5)
|
|
||||||
->addDateTime("nextTry", false, $sql->now())
|
|
||||||
->addString("errorMessage", NULL, true)
|
|
||||||
->primaryKey("id");
|
|
||||||
|
|
||||||
$queries = array_merge($queries, \Core\Configuration\Patch\EntityLog_2021_04_08::createTableLog($sql, "MailQueue", 30));
|
|
||||||
|
|
||||||
$queries[] = $sql->insert("ApiPermission", array("method", "groups", "description"))
|
$queries[] = $sql->insert("ApiPermission", array("method", "groups", "description"))
|
||||||
->addRow("ApiKey/create", array(), "Allows users to create API-Keys for themselves")
|
->addRow("ApiKey/create", array(), "Allows users to create API-Keys for themselves")
|
||||||
->addRow("ApiKey/fetch", array(), "Allows users to list their API-Keys")
|
->addRow("ApiKey/fetch", array(), "Allows users to list their API-Keys")
|
||||||
->addRow("ApiKey/refresh", array(), "Allows users to refresh their API-Keys")
|
->addRow("ApiKey/refresh", array(), "Allows users to refresh their API-Keys")
|
||||||
->addRow("ApiKey/revoke", array(), "Allows users to revoke their API-Keys")
|
->addRow("ApiKey/revoke", array(), "Allows users to revoke their API-Keys")
|
||||||
->addRow("Groups/fetch", array(USER_GROUP_SUPPORT, USER_GROUP_ADMIN), "Allows users to list all available groups")
|
->addRow("Groups/fetch", array(Group::SUPPORT, Group::ADMIN), "Allows users to list all available groups")
|
||||||
->addRow("Groups/create", array(USER_GROUP_ADMIN), "Allows users to create a new groups")
|
->addRow("Groups/create", array(Group::ADMIN), "Allows users to create a new groups")
|
||||||
->addRow("Groups/delete", array(USER_GROUP_ADMIN), "Allows users to delete a group")
|
->addRow("Groups/delete", array(Group::ADMIN), "Allows users to delete a group")
|
||||||
->addRow("Routes/fetch", array(USER_GROUP_ADMIN), "Allows users to list all configured routes")
|
->addRow("Routes/fetch", array(Group::ADMIN), "Allows users to list all configured routes")
|
||||||
->addRow("Routes/save", array(USER_GROUP_ADMIN), "Allows users to create, delete and modify routes")
|
->addRow("Routes/save", array(Group::ADMIN), "Allows users to create, delete and modify routes")
|
||||||
->addRow("Mail/test", array(USER_GROUP_SUPPORT, USER_GROUP_ADMIN), "Allows users to send a test email to a given address")
|
->addRow("Mail/test", array(Group::SUPPORT, Group::ADMIN), "Allows users to send a test email to a given address")
|
||||||
->addRow("Mail/Sync", array(USER_GROUP_SUPPORT, USER_GROUP_ADMIN), "Allows users to synchronize mails with the database")
|
->addRow("Mail/Sync", array(Group::SUPPORT, Group::ADMIN), "Allows users to synchronize mails with the database")
|
||||||
->addRow("Settings/get", array(USER_GROUP_ADMIN), "Allows users to fetch server settings")
|
->addRow("Settings/get", array(Group::ADMIN), "Allows users to fetch server settings")
|
||||||
->addRow("Settings/set", array(USER_GROUP_ADMIN), "Allows users create, delete or modify server settings")
|
->addRow("Settings/set", array(Group::ADMIN), "Allows users create, delete or modify server settings")
|
||||||
->addRow("Stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to fetch server stats")
|
->addRow("Stats", array(Group::ADMIN, Group::SUPPORT), "Allows users to fetch server stats")
|
||||||
->addRow("User/create", array(USER_GROUP_ADMIN), "Allows users to create a new user, email address does not need to be confirmed")
|
->addRow("User/create", array(Group::ADMIN), "Allows users to create a new user, email address does not need to be confirmed")
|
||||||
->addRow("User/fetch", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to list all registered users")
|
->addRow("User/fetch", array(Group::ADMIN, Group::SUPPORT), "Allows users to list all registered users")
|
||||||
->addRow("User/get", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to get information about a single user")
|
->addRow("User/get", array(Group::ADMIN, Group::SUPPORT), "Allows users to get information about a single user")
|
||||||
->addRow("User/invite", array(USER_GROUP_ADMIN), "Allows users to create a new user and send them an invitation link")
|
->addRow("User/invite", array(Group::ADMIN), "Allows users to create a new user and send them an invitation link")
|
||||||
->addRow("User/edit", array(USER_GROUP_ADMIN), "Allows users to edit details and group memberships of any user")
|
->addRow("User/edit", array(Group::ADMIN), "Allows users to edit details and group memberships of any user")
|
||||||
->addRow("User/delete", array(USER_GROUP_ADMIN), "Allows users to delete any other user")
|
->addRow("User/delete", array(Group::ADMIN), "Allows users to delete any other user")
|
||||||
->addRow("Permission/fetch", array(USER_GROUP_ADMIN), "Allows users to list all API permissions")
|
->addRow("Permission/fetch", array(Group::ADMIN), "Allows users to list all API permissions")
|
||||||
->addRow("Visitors/stats", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see visitor statistics")
|
->addRow("Visitors/stats", array(Group::ADMIN, Group::SUPPORT), "Allows users to see visitor statistics")
|
||||||
->addRow("Contact/respond", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to respond to contact requests")
|
->addRow("Contact/respond", array(Group::ADMIN, Group::SUPPORT), "Allows users to respond to contact requests")
|
||||||
->addRow("Contact/fetch", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to fetch all contact requests")
|
->addRow("Contact/fetch", array(Group::ADMIN, Group::SUPPORT), "Allows users to fetch all contact requests")
|
||||||
->addRow("Contact/get", array(USER_GROUP_ADMIN, USER_GROUP_SUPPORT), "Allows users to see messages within a contact request");
|
->addRow("Contact/get", array(Group::ADMIN, Group::SUPPORT), "Allows users to see messages within a contact request")
|
||||||
|
->addRow("Logs/get", [Group::ADMIN], "Allows users to fetch system logs");
|
||||||
|
|
||||||
self::loadPatches($queries, $sql);
|
self::loadPatches($queries, $sql);
|
||||||
|
|
||||||
@ -195,9 +125,10 @@ class CreateDatabase extends DatabaseScript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static function loadEntities(&$queries, $sql) {
|
public static function loadEntities(&$queries, $sql) {
|
||||||
$handlers = [];
|
$persistables = [];
|
||||||
$baseDirs = ["Core", "Site"];
|
$baseDirs = ["Core", "Site"];
|
||||||
foreach ($baseDirs as $baseDir) {
|
foreach ($baseDirs as $baseDir) {
|
||||||
|
|
||||||
$entityDirectory = "./$baseDir/Objects/DatabaseEntity/";
|
$entityDirectory = "./$baseDir/Objects/DatabaseEntity/";
|
||||||
if (file_exists($entityDirectory) && is_dir($entityDirectory)) {
|
if (file_exists($entityDirectory) && is_dir($entityDirectory)) {
|
||||||
$scan_arr = scandir($entityDirectory);
|
$scan_arr = scandir($entityDirectory);
|
||||||
@ -206,38 +137,40 @@ class CreateDatabase extends DatabaseScript {
|
|||||||
$suffix = ".class.php";
|
$suffix = ".class.php";
|
||||||
if (endsWith($file, $suffix)) {
|
if (endsWith($file, $suffix)) {
|
||||||
$className = substr($file, 0, strlen($file) - strlen($suffix));
|
$className = substr($file, 0, strlen($file) - strlen($suffix));
|
||||||
if (!in_array($className, ["DatabaseEntity", "DatabaseEntityQuery", "DatabaseEntityHandler"])) {
|
$className = "\\$baseDir\\Objects\\DatabaseEntity\\$className";
|
||||||
$className = "\\$baseDir\\Objects\\DatabaseEntity\\$className";
|
$reflectionClass = new \ReflectionClass($className);
|
||||||
$reflectionClass = new \ReflectionClass($className);
|
if ($reflectionClass->isSubclassOf(DatabaseEntity::class)) {
|
||||||
if ($reflectionClass->isSubclassOf(DatabaseEntity::class)) {
|
$method = "$className::getHandler";
|
||||||
$method = "$className::getHandler";
|
$handler = call_user_func($method, $sql);
|
||||||
$handler = call_user_func($method, $sql);
|
$persistables[$handler->getTableName()] = $handler;
|
||||||
$handlers[$handler->getTableName()] = $handler;
|
|
||||||
|
foreach ($handler->getNMRelations() as $nmTableName => $nmRelation) {
|
||||||
|
$persistables[$nmTableName] = $nmRelation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$tableCount = count($handlers);
|
$tableCount = count($persistables);
|
||||||
$createdTables = [];
|
$createdTables = [];
|
||||||
while (!empty($handlers)) {
|
while (!empty($persistables)) {
|
||||||
$prevCount = $tableCount;
|
$prevCount = $tableCount;
|
||||||
$unmetDependenciesTotal = [];
|
$unmetDependenciesTotal = [];
|
||||||
|
|
||||||
foreach ($handlers as $tableName => $handler) {
|
foreach ($persistables as $tableName => $persistable) {
|
||||||
$dependsOn = $handler->dependsOn();
|
$dependsOn = $persistable->dependsOn();
|
||||||
$unmetDependencies = array_diff($dependsOn, $createdTables);
|
$unmetDependencies = array_diff($dependsOn, $createdTables);
|
||||||
if (empty($unmetDependencies)) {
|
if (empty($unmetDependencies)) {
|
||||||
$queries[] = $handler->getTableQuery();
|
$queries = array_merge($queries, $persistable->getCreateQueries($sql));
|
||||||
$createdTables[] = $tableName;
|
$createdTables[] = $tableName;
|
||||||
unset($handlers[$tableName]);
|
unset($persistables[$tableName]);
|
||||||
} else {
|
} else {
|
||||||
$unmetDependenciesTotal = array_merge($unmetDependenciesTotal, $unmetDependencies);
|
$unmetDependenciesTotal = array_merge($unmetDependenciesTotal, $unmetDependencies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$tableCount = count($handlers);
|
$tableCount = count($persistables);
|
||||||
if ($tableCount === $prevCount) {
|
if ($tableCount === $prevCount) {
|
||||||
throw new Exception("Circular or unmet table dependency detected. Unmet dependencies: "
|
throw new Exception("Circular or unmet table dependency detected. Unmet dependencies: "
|
||||||
. implode(", ", $unmetDependenciesTotal));
|
. implode(", ", $unmetDependenciesTotal));
|
||||||
|
@ -5,7 +5,6 @@ namespace Core\Configuration\Patch;
|
|||||||
use Core\Configuration\DatabaseScript;
|
use Core\Configuration\DatabaseScript;
|
||||||
use Core\Driver\SQL\Column\IntColumn;
|
use Core\Driver\SQL\Column\IntColumn;
|
||||||
use Core\Driver\SQL\Condition\Compare;
|
use Core\Driver\SQL\Condition\Compare;
|
||||||
use Core\Driver\SQL\Query\CreateProcedure;
|
|
||||||
use Core\Driver\SQL\SQL;
|
use Core\Driver\SQL\SQL;
|
||||||
use Core\Driver\SQL\Type\CurrentColumn;
|
use Core\Driver\SQL\Type\CurrentColumn;
|
||||||
use Core\Driver\SQL\Type\CurrentTable;
|
use Core\Driver\SQL\Type\CurrentTable;
|
||||||
@ -13,32 +12,6 @@ use Core\Driver\SQL\Type\Trigger;
|
|||||||
|
|
||||||
class EntityLog_2021_04_08 extends DatabaseScript {
|
class EntityLog_2021_04_08 extends DatabaseScript {
|
||||||
|
|
||||||
public static function createTableLog(SQL $sql, string $table, int $lifetime = 90): array {
|
|
||||||
return [
|
|
||||||
$sql->createTrigger("${table}_trg_insert")
|
|
||||||
->after()->insert($table)
|
|
||||||
->exec(new CreateProcedure($sql, "InsertEntityLog"), [
|
|
||||||
"tableName" => new CurrentTable(),
|
|
||||||
"entityId" => new CurrentColumn("id"),
|
|
||||||
"lifetime" => $lifetime,
|
|
||||||
]),
|
|
||||||
|
|
||||||
$sql->createTrigger("${table}_trg_update")
|
|
||||||
->after()->update($table)
|
|
||||||
->exec(new CreateProcedure($sql, "UpdateEntityLog"), [
|
|
||||||
"tableName" => new CurrentTable(),
|
|
||||||
"entityId" => new CurrentColumn("id"),
|
|
||||||
]),
|
|
||||||
|
|
||||||
$sql->createTrigger("${table}_trg_delete")
|
|
||||||
->after()->delete($table)
|
|
||||||
->exec(new CreateProcedure($sql, "DeleteEntityLog"), [
|
|
||||||
"tableName" => new CurrentTable(),
|
|
||||||
"entityId" => new CurrentColumn("id"),
|
|
||||||
])
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function createQueries(SQL $sql): array {
|
public static function createQueries(SQL $sql): array {
|
||||||
|
|
||||||
$queries = array();
|
$queries = array();
|
||||||
@ -84,11 +57,6 @@ class EntityLog_2021_04_08 extends DatabaseScript {
|
|||||||
$queries[] = $updateProcedure;
|
$queries[] = $updateProcedure;
|
||||||
$queries[] = $deleteProcedure;
|
$queries[] = $deleteProcedure;
|
||||||
|
|
||||||
$tables = ["ContactRequest"];
|
|
||||||
foreach ($tables as $table) {
|
|
||||||
$queries = array_merge($queries, self::createTableLog($sql, $table));
|
|
||||||
}
|
|
||||||
|
|
||||||
return $queries;
|
return $queries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Core\Configuration\Patch;
|
|
||||||
|
|
||||||
use Core\Configuration\DatabaseScript;
|
|
||||||
use Core\Driver\SQL\SQL;
|
|
||||||
|
|
||||||
class SystemLog_2022_03_30 extends DatabaseScript {
|
|
||||||
|
|
||||||
public static function createQueries(SQL $sql): array {
|
|
||||||
return [
|
|
||||||
$sql->insert("ApiPermission", ["method", "groups", "description"])
|
|
||||||
->addRow("Logs/get", [USER_GROUP_ADMIN], "Allows users to fetch system logs")
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -63,7 +63,7 @@ class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static function loadDefaults(): Settings {
|
public static function loadDefaults(): Settings {
|
||||||
$hostname = $_SERVER["SERVER_NAME"];
|
$hostname = $_SERVER["SERVER_NAME"] ?? null;
|
||||||
if (empty($hostname)) {
|
if (empty($hostname)) {
|
||||||
$hostname = "localhost";
|
$hostname = "localhost";
|
||||||
}
|
}
|
||||||
@ -190,7 +190,14 @@ class Settings {
|
|||||||
->addRow("recaptcha_enabled", $this->recaptchaEnabled ? "1" : "0", false, false)
|
->addRow("recaptcha_enabled", $this->recaptchaEnabled ? "1" : "0", false, false)
|
||||||
->addRow("recaptcha_public_key", $this->recaptchaPublicKey, false, false)
|
->addRow("recaptcha_public_key", $this->recaptchaPublicKey, false, false)
|
||||||
->addRow("recaptcha_private_key", $this->recaptchaPrivateKey, true, false)
|
->addRow("recaptcha_private_key", $this->recaptchaPrivateKey, true, false)
|
||||||
->addRow("allowed_extensions", implode(",", $this->allowedExtensions), true, false);
|
->addRow("allowed_extensions", implode(",", $this->allowedExtensions), true, false)
|
||||||
|
->addRow("mail_host", "", false, false)
|
||||||
|
->addRow("mail_port", "", false, false)
|
||||||
|
->addRow("mail_username", "", false, false)
|
||||||
|
->addRow("mail_password", "", true, false)
|
||||||
|
->addRow("mail_from", "", false, false)
|
||||||
|
->addRow("mail_last_sync", "", false, false)
|
||||||
|
->addRow("mail_footer", "", false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSiteName(): string {
|
public function getSiteName(): string {
|
||||||
|
@ -17,7 +17,7 @@ class Info extends HtmlDocument {
|
|||||||
class InfoBody extends SimpleBody {
|
class InfoBody extends SimpleBody {
|
||||||
protected function getContent(): string {
|
protected function getContent(): string {
|
||||||
$user = $this->getDocument()->getUser();
|
$user = $this->getDocument()->getUser();
|
||||||
if ($user && $user->hasGroup(USER_GROUP_ADMIN)) {
|
if ($user && $user->hasGroup(Group::ADMIN)) {
|
||||||
phpinfo();
|
phpinfo();
|
||||||
return "";
|
return "";
|
||||||
} else {
|
} else {
|
||||||
|
@ -30,6 +30,7 @@ namespace Documents\Install {
|
|||||||
use Core\External\PHPMailer\Exception;
|
use Core\External\PHPMailer\Exception;
|
||||||
use Core\External\PHPMailer\PHPMailer;
|
use Core\External\PHPMailer\PHPMailer;
|
||||||
use Core\Objects\ConnectionData;
|
use Core\Objects\ConnectionData;
|
||||||
|
use Core\Objects\DatabaseEntity\Group;
|
||||||
|
|
||||||
class InstallHead extends Head {
|
class InstallHead extends Head {
|
||||||
|
|
||||||
@ -202,7 +203,7 @@ namespace Documents\Install {
|
|||||||
$req->execute(array(
|
$req->execute(array(
|
||||||
"title" => "Welcome",
|
"title" => "Welcome",
|
||||||
"message" => "Your Web-base was successfully installed. Check out the admin dashboard. Have fun!",
|
"message" => "Your Web-base was successfully installed. Check out the admin dashboard. Have fun!",
|
||||||
"groupId" => USER_GROUP_ADMIN
|
"groupId" => Group::ADMIN
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
$this->errorString = $req->getLastError();
|
$this->errorString = $req->getLastError();
|
||||||
@ -436,16 +437,10 @@ namespace Documents\Install {
|
|||||||
'email' => $email,
|
'email' => $email,
|
||||||
'password' => $password,
|
'password' => $password,
|
||||||
'confirmPassword' => $confirmPassword,
|
'confirmPassword' => $confirmPassword,
|
||||||
|
'groups' => [Group::ADMIN]
|
||||||
));
|
));
|
||||||
|
|
||||||
$msg = $req->getLastError();
|
$msg = $req->getLastError();
|
||||||
if ($success) {
|
|
||||||
$sql = $context->getSQL();
|
|
||||||
$success = $sql->insert("UserGroup", array("group_id", "user_id"))
|
|
||||||
->addRow(USER_GROUP_ADMIN, $req->getResult()["userId"])
|
|
||||||
->execute();
|
|
||||||
$msg = $sql->getLastError();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return array("msg" => $msg, "success" => $success);
|
return array("msg" => $msg, "success" => $success);
|
||||||
|
@ -38,6 +38,11 @@ class Select extends Query {
|
|||||||
$this->fetchType = SQL::FETCH_ALL;
|
$this->fetchType = SQL::FETCH_ALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function addColumn(string $columnName): Select {
|
||||||
|
$this->selectValues[] = $columnName;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function from(...$tables): Select {
|
public function from(...$tables): Select {
|
||||||
$this->tables = array_merge($this->tables, $tables);
|
$this->tables = array_merge($this->tables, $tables);
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -90,7 +90,7 @@ class Context {
|
|||||||
session_write_close();
|
session_write_close();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function loadSession(int $userId, int $sessionId) {
|
private function loadSession(int $userId, int $sessionId): void {
|
||||||
$this->session = Session::init($this, $userId, $sessionId);
|
$this->session = Session::init($this, $userId, $sessionId);
|
||||||
$this->user = $this->session?->getUser();
|
$this->user = $this->session?->getUser();
|
||||||
if ($this->user) {
|
if ($this->user) {
|
||||||
@ -128,12 +128,13 @@ class Context {
|
|||||||
|
|
||||||
public function updateLanguage(string $lang): bool {
|
public function updateLanguage(string $lang): bool {
|
||||||
if ($this->sql) {
|
if ($this->sql) {
|
||||||
$language = Language::findBuilder($this->sql)
|
$language = Language::findBy(Language::createBuilder($this->sql, true)
|
||||||
->where(new CondOr(
|
->where(new CondOr(
|
||||||
new CondLike("name", "%$lang%"), // english
|
new CondLike("name", "%$lang%"), // english
|
||||||
new Compare("code", $lang), // de_DE
|
new Compare("code", $lang), // de_DE
|
||||||
new CondLike("code", $lang . "_%"))) // de -> de_%
|
new CondLike("code", "${lang}_%") // de -> de_%
|
||||||
->execute();
|
))
|
||||||
|
);
|
||||||
if ($language) {
|
if ($language) {
|
||||||
$this->setLanguage($language);
|
$this->setLanguage($language);
|
||||||
return true;
|
return true;
|
||||||
@ -176,14 +177,13 @@ class Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function loadApiKey(string $apiKey): bool {
|
public function loadApiKey(string $apiKey): bool {
|
||||||
$this->user = User::findBuilder($this->sql)
|
$this->user = User::findBy(User::createBuilder($this->sql, true)
|
||||||
->addJoin(new Join("INNER","ApiKey", "ApiKey.user_id", "User.id"))
|
->addJoin(new Join("INNER","ApiKey", "ApiKey.user_id", "User.id"))
|
||||||
->where(new Compare("ApiKey.api_key", $apiKey))
|
->where(new Compare("ApiKey.api_key", $apiKey))
|
||||||
->where(new Compare("valid_until", $this->sql->currentTimestamp(), ">"))
|
->where(new Compare("valid_until", $this->sql->currentTimestamp(), ">"))
|
||||||
->where(new Compare("ApiKey.active", true))
|
->where(new Compare("ApiKey.active", true))
|
||||||
->where(new Compare("User.confirmed", true))
|
->where(new Compare("User.confirmed", true))
|
||||||
->fetchEntities()
|
->fetchEntities());
|
||||||
->execute();
|
|
||||||
|
|
||||||
return $this->user !== null;
|
return $this->user !== null;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace Core\Objects\DatabaseEntity;
|
namespace Core\Objects\DatabaseEntity;
|
||||||
|
|
||||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||||
|
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||||
|
|
||||||
class ApiKey extends DatabaseEntity {
|
class ApiKey extends DatabaseEntity {
|
||||||
|
|
||||||
|
18
Core/Objects/DatabaseEntity/Attribute/Multiple.php
Normal file
18
Core/Objects/DatabaseEntity/Attribute/Multiple.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Objects\DatabaseEntity\Attribute;
|
||||||
|
|
||||||
|
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||||
|
class Multiple {
|
||||||
|
|
||||||
|
private string $className;
|
||||||
|
|
||||||
|
public function __construct(string $className) {
|
||||||
|
$this->className = $className;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClassName(): string {
|
||||||
|
return $this->className;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Core\Objects\DatabaseEntity;
|
namespace Core\Objects\DatabaseEntity\Controller;
|
||||||
|
|
||||||
use Core\Driver\SQL\Condition\Compare;
|
use Core\Driver\SQL\Condition\Compare;
|
||||||
use Core\Driver\SQL\Condition\Condition;
|
use Core\Driver\SQL\Condition\Condition;
|
||||||
@ -8,6 +8,13 @@ use Core\Driver\SQL\SQL;
|
|||||||
|
|
||||||
abstract class DatabaseEntity {
|
abstract class DatabaseEntity {
|
||||||
|
|
||||||
|
protected static array $entityLogConfig = [
|
||||||
|
"insert" => false,
|
||||||
|
"update" => false,
|
||||||
|
"delete" => false,
|
||||||
|
"lifetime" => null,
|
||||||
|
];
|
||||||
|
|
||||||
private static array $handlers = [];
|
private static array $handlers = [];
|
||||||
protected ?int $id;
|
protected ?int $id;
|
||||||
|
|
||||||
@ -51,8 +58,8 @@ abstract class DatabaseEntity {
|
|||||||
return $res !== false && $res[0]["count"] !== 0;
|
return $res !== false && $res[0]["count"] !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function findBuilder(SQL $sql): DatabaseEntityQuery {
|
public static function findBy(DatabaseEntityQuery $dbQuery): static|array|bool|null {
|
||||||
return DatabaseEntityQuery::fetchOne(self::getHandler($sql));
|
return $dbQuery->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function findAll(SQL $sql, ?Condition $condition = null): ?array {
|
public static function findAll(SQL $sql, ?Condition $condition = null): ?array {
|
||||||
@ -60,17 +67,22 @@ abstract class DatabaseEntity {
|
|||||||
return $handler->fetchMultiple($condition);
|
return $handler->fetchMultiple($condition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function findAllBuilder(SQL $sql): DatabaseEntityQuery {
|
public static function createBuilder(SQL $sql, bool $one): DatabaseEntityQuery {
|
||||||
return DatabaseEntityQuery::fetchAll(self::getHandler($sql));
|
if ($one) {
|
||||||
|
return DatabaseEntityQuery::fetchOne(self::getHandler($sql));
|
||||||
|
} else {
|
||||||
|
return DatabaseEntityQuery::fetchAll(self::getHandler($sql));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(SQL $sql, ?array $columns = null): bool {
|
public function save(SQL $sql, ?array $columns = null, bool $saveNM = false): bool {
|
||||||
$handler = self::getHandler($sql);
|
$handler = self::getHandler($sql);
|
||||||
$res = $handler->insertOrUpdate($this, $columns);
|
$res = $handler->insertOrUpdate($this, $columns, $saveNM);
|
||||||
if ($res === false) {
|
if ($res === false) {
|
||||||
return false;
|
return false;
|
||||||
} else if ($this->id === null) {
|
} else if ($this->id === null) {
|
||||||
$this->id = $res;
|
$this->id = $res;
|
||||||
|
$handler->insertNM($this);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -127,4 +139,22 @@ abstract class DatabaseEntity {
|
|||||||
public function getId(): ?int {
|
public function getId(): ?int {
|
||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function count(SQL $sql, ?Condition $condition = null): int|bool {
|
||||||
|
$handler = self::getHandler($sql);
|
||||||
|
$query = $sql->select($sql->count())
|
||||||
|
->from($handler->getTableName());
|
||||||
|
|
||||||
|
if ($condition) {
|
||||||
|
$query->where($condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
$res = $query->execute();
|
||||||
|
|
||||||
|
if (!empty($res)) {
|
||||||
|
return $res[0]["count"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,33 +1,46 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Core\Objects\DatabaseEntity;
|
namespace Core\Objects\DatabaseEntity\Controller;
|
||||||
|
|
||||||
use Core\Driver\Logger\Logger;
|
use Core\Driver\Logger\Logger;
|
||||||
use Core\Driver\SQL\Column\BoolColumn;
|
use Core\Driver\SQL\Column\BoolColumn;
|
||||||
|
use Core\Driver\SQL\Column\Column;
|
||||||
use Core\Driver\SQL\Column\DateTimeColumn;
|
use Core\Driver\SQL\Column\DateTimeColumn;
|
||||||
use Core\Driver\SQL\Column\EnumColumn;
|
use Core\Driver\SQL\Column\EnumColumn;
|
||||||
use Core\Driver\SQL\Column\IntColumn;
|
use Core\Driver\SQL\Column\IntColumn;
|
||||||
use Core\Driver\SQL\Column\JsonColumn;
|
use Core\Driver\SQL\Column\JsonColumn;
|
||||||
use Core\Driver\SQL\Column\StringColumn;
|
use Core\Driver\SQL\Column\StringColumn;
|
||||||
use Core\Driver\SQL\Condition\Compare;
|
use Core\Driver\SQL\Condition\Compare;
|
||||||
|
use Core\Driver\SQL\Condition\CondAnd;
|
||||||
|
use Core\Driver\SQL\Condition\CondBool;
|
||||||
|
use Core\Driver\SQL\Condition\CondIn;
|
||||||
use Core\Driver\SQL\Condition\Condition;
|
use Core\Driver\SQL\Condition\Condition;
|
||||||
use Core\Driver\SQL\Column\DoubleColumn;
|
use Core\Driver\SQL\Column\DoubleColumn;
|
||||||
use Core\Driver\SQL\Column\FloatColumn;
|
use Core\Driver\SQL\Column\FloatColumn;
|
||||||
|
use Core\Driver\SQL\Condition\CondNot;
|
||||||
|
use Core\Driver\SQL\Condition\CondOr;
|
||||||
use Core\Driver\SQL\Constraint\ForeignKey;
|
use Core\Driver\SQL\Constraint\ForeignKey;
|
||||||
|
use Core\Driver\SQL\Join;
|
||||||
|
use Core\Driver\SQL\Query\CreateProcedure;
|
||||||
use Core\Driver\SQL\Query\CreateTable;
|
use Core\Driver\SQL\Query\CreateTable;
|
||||||
|
use Core\Driver\SQL\Query\Insert;
|
||||||
use Core\Driver\SQL\Query\Select;
|
use Core\Driver\SQL\Query\Select;
|
||||||
use Core\Driver\SQL\SQL;
|
use Core\Driver\SQL\SQL;
|
||||||
use Core\Driver\SQL\Strategy\CascadeStrategy;
|
use Core\Driver\SQL\Strategy\CascadeStrategy;
|
||||||
use Core\Driver\SQL\Strategy\SetNullStrategy;
|
use Core\Driver\SQL\Strategy\SetNullStrategy;
|
||||||
|
use Core\Driver\SQL\Strategy\UpdateStrategy;
|
||||||
|
use Core\Driver\SQL\Type\CurrentColumn;
|
||||||
|
use Core\Driver\SQL\Type\CurrentTable;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\Enum;
|
use Core\Objects\DatabaseEntity\Attribute\Enum;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\Json;
|
use Core\Objects\DatabaseEntity\Attribute\Json;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||||
|
use Core\Objects\DatabaseEntity\Attribute\Multiple;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\Transient;
|
use Core\Objects\DatabaseEntity\Attribute\Transient;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\Unique;
|
use Core\Objects\DatabaseEntity\Attribute\Unique;
|
||||||
use PHPUnit\Util\Exception;
|
use PHPUnit\Util\Exception;
|
||||||
|
|
||||||
class DatabaseEntityHandler {
|
class DatabaseEntityHandler implements Persistable {
|
||||||
|
|
||||||
private \ReflectionClass $entityClass;
|
private \ReflectionClass $entityClass;
|
||||||
private string $tableName;
|
private string $tableName;
|
||||||
@ -35,6 +48,7 @@ class DatabaseEntityHandler {
|
|||||||
private array $properties;
|
private array $properties;
|
||||||
private array $relations;
|
private array $relations;
|
||||||
private array $constraints;
|
private array $constraints;
|
||||||
|
private array $nmRelations;
|
||||||
private SQL $sql;
|
private SQL $sql;
|
||||||
private Logger $logger;
|
private Logger $logger;
|
||||||
|
|
||||||
@ -50,8 +64,9 @@ class DatabaseEntityHandler {
|
|||||||
$this->tableName = $this->entityClass->getShortName();
|
$this->tableName = $this->entityClass->getShortName();
|
||||||
$this->columns = []; // property name => database column name
|
$this->columns = []; // property name => database column name
|
||||||
$this->properties = []; // property name => \ReflectionProperty
|
$this->properties = []; // property name => \ReflectionProperty
|
||||||
$this->relations = []; // property name => referenced table name
|
$this->relations = []; // property name => DatabaseEntityHandler
|
||||||
$this->constraints = []; // \Driver\SQL\Constraint\Constraint
|
$this->constraints = []; // \Driver\SQL\Constraint\Constraint
|
||||||
|
$this->nmRelations = []; // table name => NMRelation
|
||||||
|
|
||||||
foreach ($this->entityClass->getProperties() as $property) {
|
foreach ($this->entityClass->getProperties() as $property) {
|
||||||
$propertyName = $property->getName();
|
$propertyName = $property->getName();
|
||||||
@ -60,6 +75,10 @@ class DatabaseEntityHandler {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($property->isStatic()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$propertyType = $property->getType();
|
$propertyType = $property->getType();
|
||||||
$columnName = self::getColumnName($propertyName);
|
$columnName = self::getColumnName($propertyName);
|
||||||
if (!($propertyType instanceof \ReflectionNamedType)) {
|
if (!($propertyType instanceof \ReflectionNamedType)) {
|
||||||
@ -93,18 +112,46 @@ class DatabaseEntityHandler {
|
|||||||
$this->columns[$propertyName] = new BoolColumn($columnName, $defaultValue ?? false);
|
$this->columns[$propertyName] = new BoolColumn($columnName, $defaultValue ?? false);
|
||||||
} else if ($propertyTypeName === 'DateTime') {
|
} else if ($propertyTypeName === 'DateTime') {
|
||||||
$this->columns[$propertyName] = new DateTimeColumn($columnName, $nullable, $defaultValue);
|
$this->columns[$propertyName] = new DateTimeColumn($columnName, $nullable, $defaultValue);
|
||||||
/*} else if ($propertyName === 'array') {
|
/*} else if ($propertyName === 'array') {
|
||||||
$many = self::getAttribute($property, Many::class);
|
$many = self::getAttribute($property, Many::class);
|
||||||
if ($many) {
|
if ($many) {
|
||||||
$requestedType = $many->getValue();
|
$requestedType = $many->getValue();
|
||||||
if (isClass($requestedType)) {
|
if (isClass($requestedType)) {
|
||||||
$requestedClass = new \ReflectionClass($requestedType);
|
$requestedClass = new \ReflectionClass($requestedType);
|
||||||
|
} else {
|
||||||
|
$this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $requestedType");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $requestedType");
|
$this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $propertyTypeName");
|
||||||
|
}*/
|
||||||
|
} else if ($propertyTypeName === "array") {
|
||||||
|
$multiple = self::getAttribute($property, Multiple::class);
|
||||||
|
if (!$multiple) {
|
||||||
|
$this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $propertyTypeName. " .
|
||||||
|
"Is the 'Multiple' attribute missing?");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$refClass = $multiple->getClassName();
|
||||||
|
$requestedClass = new \ReflectionClass($refClass);
|
||||||
|
if ($requestedClass->isSubclassOf(DatabaseEntity::class)) {
|
||||||
|
$nmTableName = NMRelation::buildTableName($this->getTableName(), $requestedClass->getShortName());
|
||||||
|
$nmRelation = $this->nmRelations[$nmTableName] ?? null;
|
||||||
|
if (!$nmRelation) {
|
||||||
|
$otherHandler = DatabaseEntity::getHandler($this->sql, $requestedClass);
|
||||||
|
$otherNM = $otherHandler->getNMRelations();
|
||||||
|
$nmRelation = $otherNM[$nmTableName] ?? (new NMRelation($this, $otherHandler));
|
||||||
|
$this->nmRelations[$nmTableName] = $nmRelation;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->nmRelations[$nmTableName]->addProperty($this, $property);
|
||||||
|
} else {
|
||||||
|
$this->raiseError("Cannot persist class '$className': Property '$propertyName' of type multiple can " .
|
||||||
|
"only reference DatabaseEntity types, but got: $refClass");
|
||||||
}
|
}
|
||||||
} else {
|
} catch (\Exception $ex) {
|
||||||
$this->raiseError("Cannot persist class '$className': Property '$propertyName' has non persist-able type: $propertyTypeName");
|
$this->raiseError("Cannot persist class '$className' property '$propertyTypeName': " . $ex->getMessage());
|
||||||
}*/
|
}
|
||||||
} else if ($propertyTypeName !== "mixed") {
|
} else if ($propertyTypeName !== "mixed") {
|
||||||
try {
|
try {
|
||||||
$requestedClass = new \ReflectionClass($propertyTypeName);
|
$requestedClass = new \ReflectionClass($propertyTypeName);
|
||||||
@ -167,6 +214,10 @@ class DatabaseEntityHandler {
|
|||||||
return $this->relations;
|
return $this->relations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getNMRelations(): array {
|
||||||
|
return $this->nmRelations;
|
||||||
|
}
|
||||||
|
|
||||||
public function getColumnNames(): array {
|
public function getColumnNames(): array {
|
||||||
$columns = ["$this->tableName.id"];
|
$columns = ["$this->tableName.id"];
|
||||||
foreach ($this->columns as $column) {
|
foreach ($this->columns as $column) {
|
||||||
@ -238,6 +289,14 @@ class DatabaseEntityHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// init n:m / 1:n properties with empty arrays
|
||||||
|
foreach ($this->nmRelations as $nmRelation) {
|
||||||
|
foreach ($nmRelation->getProperties($this) as $property) {
|
||||||
|
$property->setAccessible(true);
|
||||||
|
$property->setValue($entity, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$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);
|
||||||
@ -248,6 +307,190 @@ class DatabaseEntityHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updateNM(DatabaseEntity $entity): bool {
|
||||||
|
if (empty($this->nmRelations)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->nmRelations as $nmTable => $nmRelation) {
|
||||||
|
|
||||||
|
$thisIdColumn = $nmRelation->getIdColumn($this);
|
||||||
|
$thisTableName = $this->getTableName();
|
||||||
|
$dataColumns = $nmRelation->getDataColumns();
|
||||||
|
$otherHandler = $nmRelation->getOtherHandler($this);
|
||||||
|
$refIdColumn = $nmRelation->getIdColumn($otherHandler);
|
||||||
|
|
||||||
|
|
||||||
|
// delete from n:m table if no longer exists
|
||||||
|
$deleteStatement = $this->sql->delete($nmTable)
|
||||||
|
->where(new Compare($thisIdColumn, $entity->getId()));
|
||||||
|
|
||||||
|
if (!empty($dataColumns)) {
|
||||||
|
$conditions = [];
|
||||||
|
foreach ($dataColumns[$thisTableName] as $propertyName => $columnName) {
|
||||||
|
$property = $this->properties[$propertyName];
|
||||||
|
$entityIds = array_keys($property->getValue($entity));
|
||||||
|
if (!empty($entityIds)) {
|
||||||
|
$conditions[] = new CondAnd(
|
||||||
|
new CondBool($columnName),
|
||||||
|
new CondNot(new CondIn(new Column($refIdColumn), $entityIds)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($conditions)) {
|
||||||
|
$deleteStatement->where(new CondOr(...$conditions));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$property = next($nmRelation->getProperties($this));
|
||||||
|
$entityIds = array_keys($property->getValue($entity));
|
||||||
|
if (!empty($entityIds)) {
|
||||||
|
$deleteStatement->where(
|
||||||
|
new CondNot(new CondIn(new Column($refIdColumn), $entityIds))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$deleteStatement->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->insertNM($entity, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function insertNM(DatabaseEntity $entity, bool $ignoreExisting = true): bool {
|
||||||
|
|
||||||
|
if (empty($this->nmRelations)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$success = true;
|
||||||
|
foreach ($this->nmRelations as $nmTable => $nmRelation) {
|
||||||
|
$otherHandler = $nmRelation->getOtherHandler($this);
|
||||||
|
$thisIdColumn = $nmRelation->getIdColumn($this);
|
||||||
|
$thisTableName = $this->getTableName();
|
||||||
|
$refIdColumn = $nmRelation->getIdColumn($otherHandler);
|
||||||
|
$dataColumns = $nmRelation->getDataColumns();
|
||||||
|
|
||||||
|
$columns = [
|
||||||
|
$thisIdColumn,
|
||||||
|
$refIdColumn,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!empty($dataColumns)) {
|
||||||
|
$columns = array_merge($columns, array_values($dataColumns[$thisTableName]));
|
||||||
|
}
|
||||||
|
|
||||||
|
$statement = $this->sql->insert($nmTable, $columns);
|
||||||
|
if ($ignoreExisting) {
|
||||||
|
$statement->onDuplicateKeyStrategy(new UpdateStrategy($nmRelation->getAllColumns(), [
|
||||||
|
$thisIdColumn => $entity->getId()
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($nmRelation->getProperties($this) as $property) {
|
||||||
|
$property->setAccessible(true);
|
||||||
|
$relEntities = $property->getValue($entity);
|
||||||
|
foreach ($relEntities as $relEntity) {
|
||||||
|
$nmRow = [$entity->getId(), $relEntity->getId()];
|
||||||
|
if (!empty($dataColumns)) {
|
||||||
|
foreach (array_keys($dataColumns[$thisTableName]) as $propertyName) {
|
||||||
|
$nmRow[] = $property->getName() === $propertyName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$statement->addRow(...$nmRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$success = $statement->execute() && $success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function fetchNMRelations(array $entities, bool $recursive = false) {
|
||||||
|
|
||||||
|
if ($recursive) {
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
foreach ($this->relations as $propertyName => $relHandler) {
|
||||||
|
$relEntity = $this->properties[$propertyName]->getValue($entity);
|
||||||
|
if ($relEntity) {
|
||||||
|
$relHandler->fetchNMRelations([$relEntity->getId() => $relEntity], true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->nmRelations)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$entityIds = array_keys($entities);
|
||||||
|
foreach ($this->nmRelations as $nmTable => $nmRelation) {
|
||||||
|
$otherHandler = $nmRelation->getOtherHandler($this);
|
||||||
|
|
||||||
|
$thisIdColumn = $nmRelation->getIdColumn($this);
|
||||||
|
$thisProperties = $nmRelation->getProperties($this);
|
||||||
|
$thisTableName = $this->getTableName();
|
||||||
|
|
||||||
|
$refIdColumn = $nmRelation->getIdColumn($otherHandler);
|
||||||
|
$refProperties = $nmRelation->getProperties($otherHandler);
|
||||||
|
$refTableName = $otherHandler->getTableName();
|
||||||
|
|
||||||
|
$dataColumns = $nmRelation->getDataColumns();
|
||||||
|
|
||||||
|
$relEntityQuery = DatabaseEntityQuery::fetchAll($otherHandler)
|
||||||
|
->addJoin(new Join("INNER", $nmTable, "$nmTable.$refIdColumn", "$refTableName.id"))
|
||||||
|
->where(new CondIn(new Column($thisIdColumn), $entityIds))
|
||||||
|
->getQuery();
|
||||||
|
|
||||||
|
$relEntityQuery->addColumn($thisIdColumn);
|
||||||
|
foreach ($dataColumns as $tableDataColumns) {
|
||||||
|
foreach ($tableDataColumns as $columnName) {
|
||||||
|
$relEntityQuery->addColumn($columnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $relEntityQuery->execute();
|
||||||
|
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);
|
||||||
|
$relEntities[$relId] = $relEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
$thisEntity = $entities[$row[$thisIdColumn]];
|
||||||
|
$relEntity = $relEntities[$relId];
|
||||||
|
$mappings = [
|
||||||
|
[$refProperties, $refTableName, $relEntity, $thisEntity],
|
||||||
|
[$thisProperties, $thisTableName, $thisEntity, $relEntity],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($mappings as $mapping) {
|
||||||
|
list($properties, $tableName, $targetEntity, $entityToAdd) = $mapping;
|
||||||
|
foreach ($properties as $propertyName => $property) {
|
||||||
|
$addToProperty = empty($dataColumns);
|
||||||
|
if (!$addToProperty) {
|
||||||
|
$columnName = $dataColumns[$tableName][$propertyName] ?? null;
|
||||||
|
$addToProperty = ($columnName && $this->sql->parseBool($row[$columnName]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($addToProperty) {
|
||||||
|
$targetArray = $property->getValue($targetEntity);
|
||||||
|
$targetArray[$entityToAdd->getId()] = $entityToAdd;
|
||||||
|
$property->setValue($targetEntity, $targetArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function getSelectQuery(): Select {
|
public function getSelectQuery(): Select {
|
||||||
return $this->sql->select(...$this->getColumnNames())
|
return $this->sql->select(...$this->getColumnNames())
|
||||||
->from($this->tableName);
|
->from($this->tableName);
|
||||||
@ -259,11 +502,14 @@ class DatabaseEntityHandler {
|
|||||||
->first()
|
->first()
|
||||||
->execute();
|
->execute();
|
||||||
|
|
||||||
if ($res === false || $res === null) {
|
if ($res !== false && $res !== null) {
|
||||||
return $res;
|
$res = $this->entityFromRow($res);
|
||||||
} else {
|
if ($res instanceof DatabaseEntity) {
|
||||||
return $this->entityFromRow($res);
|
$this->fetchNMRelations([$res->getId() => $res]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fetchMultiple(?Condition $condition = null): ?array {
|
public function fetchMultiple(?Condition $condition = null): ?array {
|
||||||
@ -284,12 +530,56 @@ class DatabaseEntityHandler {
|
|||||||
$entities[$entity->getId()] = $entity;
|
$entities[$entity->getId()] = $entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->fetchNMRelations($entities);
|
||||||
return $entities;
|
return $entities;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getTableQuery(): CreateTable {
|
public function getCreateQueries(SQL $sql): array {
|
||||||
$query = $this->sql->createTable($this->tableName)
|
|
||||||
|
$queries = [];
|
||||||
|
$queries[] = $this->getTableQuery($sql);
|
||||||
|
|
||||||
|
$table = $this->getTableName();
|
||||||
|
$entityLogConfig = $this->entityClass->getProperty("entityLogConfig");
|
||||||
|
$entityLogConfig->setAccessible(true);
|
||||||
|
$entityLogConfig = $entityLogConfig->getValue();
|
||||||
|
|
||||||
|
if (isset($entityLogConfig["insert"]) && $entityLogConfig["insert"] === true) {
|
||||||
|
$queries[] = $sql->createTrigger("${table}_trg_insert")
|
||||||
|
->after()->insert($table)
|
||||||
|
->exec(new CreateProcedure($sql, "InsertEntityLog"), [
|
||||||
|
"tableName" => new CurrentTable(),
|
||||||
|
"entityId" => new CurrentColumn("id"),
|
||||||
|
"lifetime" => $entityLogConfig["lifetime"] ?? 90,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($entityLogConfig["update"]) && $entityLogConfig["update"] === true) {
|
||||||
|
$queries[] = $sql->createTrigger("${table}_trg_update")
|
||||||
|
->after()->update($table)
|
||||||
|
->exec(new CreateProcedure($sql, "UpdateEntityLog"), [
|
||||||
|
"tableName" => new CurrentTable(),
|
||||||
|
"entityId" => new CurrentColumn("id"),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($entityLogConfig["delete"]) && $entityLogConfig["delete"] === true) {
|
||||||
|
$queries[] = $sql->createTrigger("${table}_trg_delete")
|
||||||
|
->after()->delete($table)
|
||||||
|
->exec(new CreateProcedure($sql, "DeleteEntityLog"), [
|
||||||
|
"tableName" => new CurrentTable(),
|
||||||
|
"entityId" => new CurrentColumn("id"),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $queries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTableQuery(SQL $sql): CreateTable {
|
||||||
|
$query = $sql->createTable($this->tableName)
|
||||||
->onlyIfNotExists()
|
->onlyIfNotExists()
|
||||||
->addSerial("id")
|
->addSerial("id")
|
||||||
->primaryKey("id");
|
->primaryKey("id");
|
||||||
@ -305,12 +595,7 @@ class DatabaseEntityHandler {
|
|||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createTable(): bool {
|
private function prepareRow(DatabaseEntity $entity, string $action, ?array $columns = null): bool|array {
|
||||||
$query = $this->getTableQuery();
|
|
||||||
return $query->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function prepareRow(DatabaseEntity $entity, string $action, ?array $columns = null) {
|
|
||||||
$row = [];
|
$row = [];
|
||||||
foreach ($this->columns as $propertyName => $column) {
|
foreach ($this->columns as $propertyName => $column) {
|
||||||
if ($columns && !in_array($column->getName(), $columns)) {
|
if ($columns && !in_array($column->getName(), $columns)) {
|
||||||
@ -344,7 +629,7 @@ class DatabaseEntityHandler {
|
|||||||
return $row;
|
return $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(DatabaseEntity $entity, ?array $columns = null) {
|
public function update(DatabaseEntity $entity, ?array $columns = null, bool $saveNM = false) {
|
||||||
$row = $this->prepareRow($entity, "update", $columns);
|
$row = $this->prepareRow($entity, "update", $columns);
|
||||||
if ($row === false) {
|
if ($row === false) {
|
||||||
return false;
|
return false;
|
||||||
@ -358,10 +643,15 @@ class DatabaseEntityHandler {
|
|||||||
$query->set($columnName, $value);
|
$query->set($columnName, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $query->execute();
|
$res = $query->execute();
|
||||||
|
if ($res && $saveNM) {
|
||||||
|
$res = $this->updateNM($entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function insert(DatabaseEntity $entity) {
|
public function insert(DatabaseEntity $entity): bool|int {
|
||||||
$row = $this->prepareRow($entity, "insert");
|
$row = $this->prepareRow($entity, "insert");
|
||||||
if ($row === false) {
|
if ($row === false) {
|
||||||
return false;
|
return false;
|
||||||
@ -391,12 +681,12 @@ class DatabaseEntityHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function insertOrUpdate(DatabaseEntity $entity, ?array $columns = null) {
|
public function insertOrUpdate(DatabaseEntity $entity, ?array $columns = null, bool $saveNM = false) {
|
||||||
$id = $entity->getId();
|
$id = $entity->getId();
|
||||||
if ($id === null) {
|
if ($id === null) {
|
||||||
return $this->insert($entity);
|
return $this->insert($entity);
|
||||||
} else {
|
} else {
|
||||||
return $this->update($entity, $columns);
|
return $this->update($entity, $columns, $saveNM);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,4 +705,30 @@ class DatabaseEntityHandler {
|
|||||||
public function getSQL(): SQL {
|
public function getSQL(): SQL {
|
||||||
return $this->sql;
|
return $this->sql;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getInsertQuery(DatabaseEntity|array $entities): ?Insert {
|
||||||
|
|
||||||
|
if (empty($entities)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$firstEntity = (is_array($entities) ? current($entities) : $entities);
|
||||||
|
$firstRow = $this->prepareRow($firstEntity, "insert");
|
||||||
|
|
||||||
|
$statement = $this->sql->insert($this->tableName, array_keys($firstRow))
|
||||||
|
->addRow(...array_values($firstRow));
|
||||||
|
|
||||||
|
if (is_array($entities)) {
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
if ($entity === $firstEntity) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = $this->prepareRow($entity, "insert");
|
||||||
|
$statement->addRow(...array_values($row));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $statement;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Core\Objects\DatabaseEntity;
|
namespace Core\Objects\DatabaseEntity\Controller;
|
||||||
|
|
||||||
use Core\Driver\Logger\Logger;
|
use Core\Driver\Logger\Logger;
|
||||||
use Core\Driver\SQL\Condition\Condition;
|
use Core\Driver\SQL\Condition\Condition;
|
||||||
@ -14,18 +14,25 @@ use Core\Driver\SQL\SQL;
|
|||||||
*/
|
*/
|
||||||
class DatabaseEntityQuery {
|
class DatabaseEntityQuery {
|
||||||
|
|
||||||
|
const FETCH_NONE = 0;
|
||||||
|
const FETCH_DIRECT = 1;
|
||||||
|
const FETCH_RECURSIVE = 2;
|
||||||
|
|
||||||
private Logger $logger;
|
private Logger $logger;
|
||||||
private DatabaseEntityHandler $handler;
|
private DatabaseEntityHandler $handler;
|
||||||
private Select $selectQuery;
|
private Select $selectQuery;
|
||||||
private int $resultType;
|
private int $resultType;
|
||||||
private bool $logVerbose;
|
private bool $logVerbose;
|
||||||
|
|
||||||
|
private int $fetchSubEntities;
|
||||||
|
|
||||||
private function __construct(DatabaseEntityHandler $handler, int $resultType) {
|
private function __construct(DatabaseEntityHandler $handler, int $resultType) {
|
||||||
$this->handler = $handler;
|
$this->handler = $handler;
|
||||||
$this->selectQuery = $handler->getSelectQuery();
|
$this->selectQuery = $handler->getSelectQuery();
|
||||||
$this->logger = new Logger("DB-EntityQuery", $handler->getSQL());
|
$this->logger = new Logger("DB-EntityQuery", $handler->getSQL());
|
||||||
$this->resultType = $resultType;
|
$this->resultType = $resultType;
|
||||||
$this->logVerbose = false;
|
$this->logVerbose = false;
|
||||||
|
$this->fetchSubEntities = self::FETCH_NONE;
|
||||||
|
|
||||||
if ($this->resultType === SQL::FETCH_ONE) {
|
if ($this->resultType === SQL::FETCH_ONE) {
|
||||||
$this->selectQuery->first();
|
$this->selectQuery->first();
|
||||||
@ -50,6 +57,11 @@ class DatabaseEntityQuery {
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function offset(int $offset): static {
|
||||||
|
$this->selectQuery->offset($offset);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function where(Condition ...$condition): DatabaseEntityQuery {
|
public function where(Condition ...$condition): DatabaseEntityQuery {
|
||||||
$this->selectQuery->where(...$condition);
|
$this->selectQuery->where(...$condition);
|
||||||
return $this;
|
return $this;
|
||||||
@ -74,6 +86,7 @@ class DatabaseEntityQuery {
|
|||||||
public function fetchEntities(bool $recursive = false): DatabaseEntityQuery {
|
public function fetchEntities(bool $recursive = false): DatabaseEntityQuery {
|
||||||
|
|
||||||
// $this->selectQuery->dump();
|
// $this->selectQuery->dump();
|
||||||
|
$this->fetchSubEntities = ($recursive ? self::FETCH_RECURSIVE : self::FETCH_DIRECT);
|
||||||
|
|
||||||
$relIndex = 1;
|
$relIndex = 1;
|
||||||
foreach ($this->handler->getRelations() as $propertyName => $relationHandler) {
|
foreach ($this->handler->getRelations() as $propertyName => $relationHandler) {
|
||||||
@ -136,9 +149,19 @@ class DatabaseEntityQuery {
|
|||||||
$entities[$entity->getId()] = $entity;
|
$entities[$entity->getId()] = $entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->fetchSubEntities !== self::FETCH_NONE) {
|
||||||
|
$this->handler->fetchNMRelations($entities, $this->fetchSubEntities === self::FETCH_RECURSIVE);
|
||||||
|
}
|
||||||
|
|
||||||
return $entities;
|
return $entities;
|
||||||
} else if ($this->resultType === SQL::FETCH_ONE) {
|
} else if ($this->resultType === SQL::FETCH_ONE) {
|
||||||
return $this->handler->entityFromRow($res);
|
$entity = $this->handler->entityFromRow($res);
|
||||||
|
if ($entity instanceof DatabaseEntity && $this->fetchSubEntities !== self::FETCH_NONE) {
|
||||||
|
$this->handler->fetchNMRelations([$entity->getId() => $entity], $this->fetchSubEntities === self::FETCH_RECURSIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $entity;
|
||||||
} else {
|
} else {
|
||||||
$this->handler->getLogger()->error("Invalid result type for query builder, must be FETCH_ALL or FETCH_ONE");
|
$this->handler->getLogger()->error("Invalid result type for query builder, must be FETCH_ALL or FETCH_ONE");
|
||||||
return null;
|
return null;
|
||||||
@ -149,4 +172,8 @@ class DatabaseEntityQuery {
|
|||||||
$this->selectQuery->addJoin($join);
|
$this->selectQuery->addJoin($join);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getQuery(): Select {
|
||||||
|
return $this->selectQuery;
|
||||||
|
}
|
||||||
}
|
}
|
134
Core/Objects/DatabaseEntity/Controller/NMRelation.class.php
Normal file
134
Core/Objects/DatabaseEntity/Controller/NMRelation.class.php
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Objects\DatabaseEntity\Controller;
|
||||||
|
|
||||||
|
# TODO: Allow more than 2 relations here?
|
||||||
|
|
||||||
|
use Core\Driver\SQL\Query\CreateTable;
|
||||||
|
use Core\Driver\SQL\SQL;
|
||||||
|
use Core\Driver\SQL\Strategy\CascadeStrategy;
|
||||||
|
|
||||||
|
class NMRelation implements Persistable {
|
||||||
|
|
||||||
|
private DatabaseEntityHandler $handlerA;
|
||||||
|
private DatabaseEntityHandler $handlerB;
|
||||||
|
private array $properties;
|
||||||
|
|
||||||
|
public function __construct(DatabaseEntityHandler $handlerA, DatabaseEntityHandler $handlerB) {
|
||||||
|
$this->handlerA = $handlerA;
|
||||||
|
$this->handlerB = $handlerB;
|
||||||
|
$tableNameA = $handlerA->getTableName();
|
||||||
|
$tableNameB = $handlerB->getTableName();
|
||||||
|
if ($tableNameA === $tableNameB) {
|
||||||
|
throw new \Exception("Cannot create N:M Relation with only one table");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->properties = [
|
||||||
|
$tableNameA => [],
|
||||||
|
$tableNameB => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addProperty(DatabaseEntityHandler $src, \ReflectionProperty $property): void {
|
||||||
|
$this->properties[$src->getTableName()][$property->getName()] = $property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIdColumn(DatabaseEntityHandler $handler): string {
|
||||||
|
return DatabaseEntityHandler::getColumnName($handler->getTableName()) . "_id";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDataColumns(): array {
|
||||||
|
|
||||||
|
$referenceCount = 0;
|
||||||
|
$columnsNeeded = false;
|
||||||
|
|
||||||
|
// if in one of the relations we have multiple references, we need to differentiate
|
||||||
|
foreach ($this->properties as $refProperties) {
|
||||||
|
$referenceCount += count($refProperties);
|
||||||
|
if ($referenceCount > 1) {
|
||||||
|
$columnsNeeded = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$columns = [];
|
||||||
|
if ($columnsNeeded) {
|
||||||
|
foreach ($this->properties as $tableName => $properties) {
|
||||||
|
$columns[$tableName] = [];
|
||||||
|
foreach ($properties as $property) {
|
||||||
|
$columnName = DatabaseEntityHandler::getColumnName($tableName) . "_" .
|
||||||
|
DatabaseEntityHandler::getColumnName($property->getName());
|
||||||
|
$columns[$tableName][$property->getName()] = $columnName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllColumns(): array {
|
||||||
|
$relIdA = $this->getIdColumn($this->handlerA);
|
||||||
|
$relIdB = $this->getIdColumn($this->handlerB);
|
||||||
|
|
||||||
|
$columns = [$relIdA, $relIdB];
|
||||||
|
|
||||||
|
foreach ($this->getDataColumns() as $dataColumns) {
|
||||||
|
foreach ($dataColumns as $columnName) {
|
||||||
|
$columns[] = $columnName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTableQuery(SQL $sql): CreateTable {
|
||||||
|
|
||||||
|
$tableNameA = $this->handlerA->getTableName();
|
||||||
|
$tableNameB = $this->handlerB->getTableName();
|
||||||
|
|
||||||
|
$columns = $this->getAllColumns();
|
||||||
|
list ($relIdA, $relIdB) = $columns;
|
||||||
|
$dataColumns = array_slice($columns, 2);
|
||||||
|
$query = $sql->createTable(self::buildTableName($tableNameA, $tableNameB))
|
||||||
|
->addInt($relIdA)
|
||||||
|
->addInt($relIdB)
|
||||||
|
->foreignKey($relIdA, $tableNameA, "id", new CascadeStrategy())
|
||||||
|
->foreignKey($relIdB, $tableNameB, "id", new CascadeStrategy());
|
||||||
|
|
||||||
|
foreach ($dataColumns as $dataColumn) {
|
||||||
|
$query->addBool($dataColumn, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->unique(...$columns);
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function buildTableName(string ...$tables): string {
|
||||||
|
sort($tables);
|
||||||
|
return "NM_" . implode("_", $tables);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dependsOn(): array {
|
||||||
|
return [$this->handlerA->getTableName(), $this->handlerB->getTableName()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTableName(): string {
|
||||||
|
return self::buildTableName(...$this->dependsOn());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreateQueries(SQL $sql): array {
|
||||||
|
return [$this->getTableQuery($sql)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProperties(DatabaseEntityHandler $handler): array {
|
||||||
|
return $this->properties[$handler->getTableName()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOtherHandler(DatabaseEntityHandler $handler): DatabaseEntityHandler {
|
||||||
|
if ($handler === $this->handlerA) {
|
||||||
|
return $this->handlerB;
|
||||||
|
} else {
|
||||||
|
return $this->handlerA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Objects\DatabaseEntity\Controller;
|
||||||
|
|
||||||
|
use Core\Driver\SQL\SQL;
|
||||||
|
|
||||||
|
interface Persistable {
|
||||||
|
|
||||||
|
public function dependsOn(): array;
|
||||||
|
public function getTableName(): string;
|
||||||
|
public function getCreateQueries(SQL $sql): array;
|
||||||
|
|
||||||
|
}
|
@ -6,6 +6,7 @@ use Core\Driver\SQL\Expression\CurrentTimeStamp;
|
|||||||
use Core\Driver\SQL\SQL;
|
use Core\Driver\SQL\SQL;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||||
|
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||||
|
|
||||||
class GpgKey extends DatabaseEntity {
|
class GpgKey extends DatabaseEntity {
|
||||||
|
|
||||||
|
@ -3,14 +3,27 @@
|
|||||||
namespace Core\Objects\DatabaseEntity;
|
namespace Core\Objects\DatabaseEntity;
|
||||||
|
|
||||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||||
|
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||||
|
|
||||||
class Group extends DatabaseEntity {
|
class Group extends DatabaseEntity {
|
||||||
|
|
||||||
|
const ADMIN = 1;
|
||||||
|
const MODERATOR = 3;
|
||||||
|
const SUPPORT = 2;
|
||||||
|
|
||||||
|
const GROUPS = [
|
||||||
|
self::ADMIN => "Administrator",
|
||||||
|
self::MODERATOR => "Moderator",
|
||||||
|
self::SUPPORT => "Support",
|
||||||
|
];
|
||||||
|
|
||||||
#[MaxLength(32)] public string $name;
|
#[MaxLength(32)] public string $name;
|
||||||
#[MaxLength(10)] public string $color;
|
#[MaxLength(10)] public string $color;
|
||||||
|
|
||||||
public function __construct(?int $id = null) {
|
public function __construct(?int $id, string $name, string $color) {
|
||||||
parent::__construct($id);
|
parent::__construct($id);
|
||||||
|
$this->name = $name;
|
||||||
|
$this->color = $color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function jsonSerialize(): array {
|
public function jsonSerialize(): array {
|
||||||
|
@ -2,14 +2,17 @@
|
|||||||
|
|
||||||
namespace Core\Objects\DatabaseEntity {
|
namespace Core\Objects\DatabaseEntity {
|
||||||
|
|
||||||
use Core\Driver\SQL\SQL;
|
|
||||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\Transient;
|
use Core\Objects\DatabaseEntity\Attribute\Transient;
|
||||||
use Core\Objects\lang\LanguageModule;
|
use Core\Objects\lang\LanguageModule;
|
||||||
|
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||||
|
|
||||||
// TODO: language from cookie?
|
// TODO: language from cookie?
|
||||||
class Language extends DatabaseEntity {
|
class Language extends DatabaseEntity {
|
||||||
|
|
||||||
|
const AMERICAN_ENGLISH = 1;
|
||||||
|
const GERMAN_STANDARD = 2;
|
||||||
|
|
||||||
const LANG_CODE_PATTERN = "/^[a-zA-Z]{2}_[a-zA-Z]{2}$/";
|
const LANG_CODE_PATTERN = "/^[a-zA-Z]{2}_[a-zA-Z]{2}$/";
|
||||||
|
|
||||||
#[MaxLength(5)] private string $code;
|
#[MaxLength(5)] private string $code;
|
||||||
|
124
Core/Objects/DatabaseEntity/MailQueueItem.class.php
Normal file
124
Core/Objects/DatabaseEntity/MailQueueItem.class.php
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Objects\DatabaseEntity;
|
||||||
|
|
||||||
|
use Core\API\Mail\Send;
|
||||||
|
use Core\Driver\SQL\Expression\CurrentTimeStamp;
|
||||||
|
use Core\Objects\Context;
|
||||||
|
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||||
|
use Core\Objects\DatabaseEntity\Attribute\EnumArr;
|
||||||
|
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||||
|
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||||
|
|
||||||
|
class MailQueueItem extends DatabaseEntity {
|
||||||
|
|
||||||
|
protected static array $entityLogConfig = [
|
||||||
|
"update" => true,
|
||||||
|
"delete" => true,
|
||||||
|
"insert" => true,
|
||||||
|
"lifetime" => 30
|
||||||
|
];
|
||||||
|
|
||||||
|
const STATUS_WAITING = "waiting";
|
||||||
|
const STATUS_SUCCESS = "success";
|
||||||
|
const STATUS_ERROR = "error";
|
||||||
|
const STATUS_ITEMS = [self::STATUS_WAITING, self::STATUS_SUCCESS, self::STATUS_ERROR];
|
||||||
|
|
||||||
|
#[MaxLength(64)]
|
||||||
|
private string $from;
|
||||||
|
|
||||||
|
#[MaxLength(64)]
|
||||||
|
private string $to;
|
||||||
|
|
||||||
|
private string $subject;
|
||||||
|
private string $body;
|
||||||
|
|
||||||
|
#[MaxLength(64)]
|
||||||
|
private ?string $replyTo;
|
||||||
|
|
||||||
|
#[MaxLength(64)]
|
||||||
|
private ?string $replyName;
|
||||||
|
|
||||||
|
#[MaxLength(64)]
|
||||||
|
private ?string $gpgFingerprint;
|
||||||
|
|
||||||
|
#[EnumArr(self::STATUS_ITEMS)]
|
||||||
|
#[DefaultValue(self::STATUS_WAITING)]
|
||||||
|
private string $status;
|
||||||
|
|
||||||
|
#[DefaultValue(5)]
|
||||||
|
private int $retryCount;
|
||||||
|
|
||||||
|
#[DefaultValue(CurrentTimeStamp::class)]
|
||||||
|
private \DateTime $nextTry;
|
||||||
|
|
||||||
|
private ?string $errorMessage;
|
||||||
|
|
||||||
|
public function __construct(string $fromMail, string $toMail, string $subject, string $body,
|
||||||
|
?string $replyTo, ?string $replyName, ?string $gpgFingerprint) {
|
||||||
|
parent::__construct();
|
||||||
|
$this->from = $fromMail;
|
||||||
|
$this->to = $toMail;
|
||||||
|
$this->subject = $subject;
|
||||||
|
$this->body = $body;
|
||||||
|
$this->replyTo = $replyTo;
|
||||||
|
$this->replyName = $replyName;
|
||||||
|
$this->gpgFingerprint = $gpgFingerprint;
|
||||||
|
$this->retryCount = 5;
|
||||||
|
$this->nextTry = new \DateTime();
|
||||||
|
$this->errorMessage = null;
|
||||||
|
$this->status = self::STATUS_WAITING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function jsonSerialize(): array {
|
||||||
|
return [
|
||||||
|
"id" => $this->getId(),
|
||||||
|
"from" => $this->from,
|
||||||
|
"to" => $this->to,
|
||||||
|
"gpgFingerprint" => $this->gpgFingerprint,
|
||||||
|
"subject" => $this->subject,
|
||||||
|
"message" => $this->body,
|
||||||
|
"status" => $this->status,
|
||||||
|
"reply" => [
|
||||||
|
"to" => $this->replyTo,
|
||||||
|
"name" => $this->replyName,
|
||||||
|
],
|
||||||
|
"retryCount" => $this->retryCount,
|
||||||
|
"nextTry" => $this->nextTry->getTimestamp(),
|
||||||
|
"errorMessage" => $this->errorMessage,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(Context $context): bool {
|
||||||
|
|
||||||
|
$args = [
|
||||||
|
"to" => $this->to,
|
||||||
|
"subject" => $this->subject,
|
||||||
|
"body" => $this->body,
|
||||||
|
"replyTo" => $this->replyTo,
|
||||||
|
"replyName" => $this->replyName,
|
||||||
|
"gpgFingerprint" => $this->gpgFingerprint,
|
||||||
|
"async" => false
|
||||||
|
];
|
||||||
|
|
||||||
|
$req = new Send($context);
|
||||||
|
$success = $req->execute($args);
|
||||||
|
$this->errorMessage = $req->getLastError();
|
||||||
|
|
||||||
|
$delay = [0, 720, 360, 60, 30, 1];
|
||||||
|
$minutes = $delay[max(0, min(count($delay) - 1, $this->retryCount))];
|
||||||
|
if ($this->retryCount > 0) {
|
||||||
|
$this->retryCount--;
|
||||||
|
$this->nextTry = (new \DateTime())->modify("+$minutes minute");
|
||||||
|
} else if (!$success) {
|
||||||
|
$this->status = self::STATUS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($success) {
|
||||||
|
$this->status = self::STATUS_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->save($context->getSQL());
|
||||||
|
return $success;
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ use Core\API\Parameter\Parameter;
|
|||||||
use Core\Driver\SQL\Expression\CurrentTimeStamp;
|
use Core\Driver\SQL\Expression\CurrentTimeStamp;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||||
|
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||||
|
|
||||||
class News extends DatabaseEntity {
|
class News extends DatabaseEntity {
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ class News extends DatabaseEntity {
|
|||||||
|
|
||||||
public function __construct(?int $id = null) {
|
public function __construct(?int $id = null) {
|
||||||
parent::__construct($id);
|
parent::__construct($id);
|
||||||
|
$this->publishedAt = new \DateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function jsonSerialize(): array {
|
public function jsonSerialize(): array {
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Core\Objects\DatabaseEntity;
|
|
||||||
|
|
||||||
use Core\API\Parameter\Parameter;
|
|
||||||
use Core\Driver\SQL\Expression\CurrentTimeStamp;
|
|
||||||
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
|
||||||
use Core\Objects\DatabaseEntity\Attribute\Enum;
|
|
||||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
|
||||||
|
|
||||||
class Notification extends DatabaseEntity {
|
|
||||||
|
|
||||||
#[Enum('default', 'message', 'warning')] private string $type;
|
|
||||||
#[DefaultValue(CurrentTimeStamp::class)] private \DateTime $createdAt;
|
|
||||||
#[MaxLength(32)] public string $title;
|
|
||||||
#[MaxLength(256)] public string $message;
|
|
||||||
|
|
||||||
public function __construct(?int $id = null) {
|
|
||||||
parent::__construct($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function jsonSerialize(): array {
|
|
||||||
return [
|
|
||||||
"id" => $this->getId(),
|
|
||||||
"createdAt" => $this->createdAt->format(Parameter::DATE_TIME_FORMAT),
|
|
||||||
"title" => $this->title,
|
|
||||||
"message" => $this->message
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
@ -10,6 +10,7 @@ use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
|||||||
use Core\Objects\DatabaseEntity\Attribute\Json;
|
use Core\Objects\DatabaseEntity\Attribute\Json;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\Transient;
|
use Core\Objects\DatabaseEntity\Attribute\Transient;
|
||||||
|
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||||
|
|
||||||
class Session extends DatabaseEntity {
|
class Session extends DatabaseEntity {
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ use Core\Driver\SQL\Expression\CurrentTimeStamp;
|
|||||||
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\Enum;
|
use Core\Objects\DatabaseEntity\Attribute\Enum;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||||
|
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||||
|
|
||||||
class SystemLog extends DatabaseEntity {
|
class SystemLog extends DatabaseEntity {
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ use Core\Objects\DatabaseEntity\Attribute\Enum;
|
|||||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||||
use Core\Objects\TwoFactor\KeyBasedTwoFactorToken;
|
use Core\Objects\TwoFactor\KeyBasedTwoFactorToken;
|
||||||
use Core\Objects\TwoFactor\TimeBasedTwoFactorToken;
|
use Core\Objects\TwoFactor\TimeBasedTwoFactorToken;
|
||||||
|
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||||
|
|
||||||
abstract class TwoFactorToken extends DatabaseEntity {
|
abstract class TwoFactorToken extends DatabaseEntity {
|
||||||
|
|
||||||
|
@ -2,14 +2,13 @@
|
|||||||
|
|
||||||
namespace Core\Objects\DatabaseEntity;
|
namespace Core\Objects\DatabaseEntity;
|
||||||
|
|
||||||
use Core\Driver\SQL\Condition\Compare;
|
|
||||||
use Core\Driver\SQL\Expression\CurrentTimeStamp;
|
use Core\Driver\SQL\Expression\CurrentTimeStamp;
|
||||||
use Core\Driver\SQL\Join;
|
|
||||||
use Core\Driver\SQL\SQL;
|
use Core\Driver\SQL\SQL;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\Transient;
|
use Core\Objects\DatabaseEntity\Attribute\Multiple;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\Unique;
|
use Core\Objects\DatabaseEntity\Attribute\Unique;
|
||||||
|
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||||
|
|
||||||
class User extends DatabaseEntity {
|
class User extends DatabaseEntity {
|
||||||
|
|
||||||
@ -20,30 +19,16 @@ class User extends DatabaseEntity {
|
|||||||
#[MaxLength(64)] public ?string $profilePicture;
|
#[MaxLength(64)] public ?string $profilePicture;
|
||||||
private ?\DateTime $lastOnline;
|
private ?\DateTime $lastOnline;
|
||||||
#[DefaultValue(CurrentTimeStamp::class)] public \DateTime $registeredAt;
|
#[DefaultValue(CurrentTimeStamp::class)] public \DateTime $registeredAt;
|
||||||
public bool $confirmed;
|
#[DefaultValue(false)] public bool $confirmed;
|
||||||
#[DefaultValue(1)] public Language $language;
|
#[DefaultValue(Language::AMERICAN_ENGLISH)] public Language $language;
|
||||||
public ?GpgKey $gpgKey;
|
public ?GpgKey $gpgKey;
|
||||||
private ?TwoFactorToken $twoFactorToken;
|
private ?TwoFactorToken $twoFactorToken;
|
||||||
|
|
||||||
#[Transient] private array $groups;
|
#[Multiple(Group::class)]
|
||||||
|
public array $groups;
|
||||||
|
|
||||||
public function __construct(?int $id = null) {
|
public function __construct(?int $id = null) {
|
||||||
parent::__construct($id);
|
parent::__construct($id);
|
||||||
$this->groups = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function postFetch(SQL $sql, array $row) {
|
|
||||||
parent::postFetch($sql, $row);
|
|
||||||
$this->groups = [];
|
|
||||||
|
|
||||||
$groups = Group::findAllBuilder($sql)
|
|
||||||
->addJoin(new Join("INNER", "UserGroup", "UserGroup.group_id", "Group.id"))
|
|
||||||
->where(new Compare("UserGroup.user_id", $this->id))
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
if ($groups) {
|
|
||||||
$this->groups = $groups;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUsername(): string {
|
public function getUsername(): string {
|
||||||
|
@ -6,6 +6,7 @@ use Core\Driver\SQL\SQL;
|
|||||||
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
use Core\Objects\DatabaseEntity\Attribute\DefaultValue;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\EnumArr;
|
use Core\Objects\DatabaseEntity\Attribute\EnumArr;
|
||||||
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
use Core\Objects\DatabaseEntity\Attribute\MaxLength;
|
||||||
|
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||||
|
|
||||||
class UserToken extends DatabaseEntity {
|
class UserToken extends DatabaseEntity {
|
||||||
|
|
||||||
|
@ -1,26 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
const USER_GROUP_MODERATOR = 1;
|
|
||||||
const USER_GROUP_MODERATOR_NAME = "Moderator";
|
|
||||||
const USER_GROUP_SUPPORT = 2;
|
|
||||||
const USER_GROUP_SUPPORT_NAME = "Support";
|
|
||||||
const USER_GROUP_ADMIN = 3;
|
|
||||||
const USER_GROUP_ADMIN_NAME = "Administrator";
|
|
||||||
|
|
||||||
const DEFAULT_GROUPS = array(
|
|
||||||
USER_GROUP_MODERATOR, USER_GROUP_SUPPORT, USER_GROUP_ADMIN
|
|
||||||
);
|
|
||||||
|
|
||||||
function GroupName($index) {
|
|
||||||
$groupNames = array(
|
|
||||||
USER_GROUP_MODERATOR => USER_GROUP_MODERATOR_NAME,
|
|
||||||
USER_GROUP_SUPPORT => USER_GROUP_SUPPORT_NAME,
|
|
||||||
USER_GROUP_ADMIN => USER_GROUP_ADMIN_NAME,
|
|
||||||
);
|
|
||||||
|
|
||||||
return ($groupNames[$index] ?? "Unknown Group");
|
|
||||||
}
|
|
||||||
|
|
||||||
// adapted from https://www.php.net/manual/en/function.http-response-code.php
|
// adapted from https://www.php.net/manual/en/function.http-response-code.php
|
||||||
const HTTP_STATUS_DESCRIPTIONS = [
|
const HTTP_STATUS_DESCRIPTIONS = [
|
||||||
100 => 'Continue',
|
100 => 'Continue',
|
||||||
|
@ -10,12 +10,12 @@ if (is_file($autoLoad)) {
|
|||||||
require_once $autoLoad;
|
require_once $autoLoad;
|
||||||
}
|
}
|
||||||
|
|
||||||
define("WEBBASE_VERSION", "2.2.0");
|
define("WEBBASE_VERSION", "2.3.0");
|
||||||
|
|
||||||
spl_autoload_extensions(".php");
|
spl_autoload_extensions(".php");
|
||||||
spl_autoload_register(function ($class) {
|
spl_autoload_register(function ($class) {
|
||||||
if (!class_exists($class)) {
|
if (!class_exists($class)) {
|
||||||
$suffixes = ["", ".class", ".trait"];
|
$suffixes = ["", ".class", ".trait", ".interface"];
|
||||||
foreach ($suffixes as $suffix) {
|
foreach ($suffixes as $suffix) {
|
||||||
$full_path = WEBROOT . "/" . getClassPath($class, $suffix);
|
$full_path = WEBROOT . "/" . getClassPath($class, $suffix);
|
||||||
if (file_exists($full_path)) {
|
if (file_exists($full_path)) {
|
||||||
|
7
cli.php
7
cli.php
@ -94,13 +94,8 @@ function handleDatabase(array $argv) {
|
|||||||
$action = $argv[2] ?? "";
|
$action = $argv[2] ?? "";
|
||||||
|
|
||||||
if ($action === "migrate") {
|
if ($action === "migrate") {
|
||||||
$class = $argv[3] ?? null;
|
|
||||||
if (!$class) {
|
|
||||||
_exit("Usage: cli.php db migrate <class name>");
|
|
||||||
}
|
|
||||||
|
|
||||||
$sql = connectSQL() or die();
|
$sql = connectSQL() or die();
|
||||||
applyPatch($sql, $class);
|
|
||||||
} else if (in_array($action, ["export", "import", "shell"])) {
|
} else if (in_array($action, ["export", "import", "shell"])) {
|
||||||
|
|
||||||
// database config
|
// database config
|
||||||
|
@ -4,8 +4,8 @@ use Core\API\Parameter\Parameter;
|
|||||||
use Core\Driver\SQL\Query\CreateTable;
|
use Core\Driver\SQL\Query\CreateTable;
|
||||||
use Core\Driver\SQL\SQL;
|
use Core\Driver\SQL\SQL;
|
||||||
use Core\Objects\Context;
|
use Core\Objects\Context;
|
||||||
use Core\Objects\DatabaseEntity\DatabaseEntity;
|
use Core\Objects\DatabaseEntity\Controller\DatabaseEntity;
|
||||||
use Core\Objects\DatabaseEntity\DatabaseEntityHandler;
|
use Core\Objects\DatabaseEntity\Controller\DatabaseEntityHandler;
|
||||||
use Core\Objects\DatabaseEntity\User;
|
use Core\Objects\DatabaseEntity\User;
|
||||||
|
|
||||||
class DatabaseEntityTest extends \PHPUnit\Framework\TestCase {
|
class DatabaseEntityTest extends \PHPUnit\Framework\TestCase {
|
||||||
@ -28,8 +28,9 @@ class DatabaseEntityTest extends \PHPUnit\Framework\TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testCreateTable() {
|
public function testCreateTable() {
|
||||||
$this->assertInstanceOf(CreateTable::class, self::$HANDLER->getTableQuery());
|
$query = self::$HANDLER->getTableQuery(self::$CONTEXT->getSQL());
|
||||||
$this->assertTrue(self::$HANDLER->createTable());
|
$this->assertInstanceOf(CreateTable::class, $query);
|
||||||
|
$this->assertTrue($query->execute());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testInsertEntity() {
|
public function testInsertEntity() {
|
||||||
|
@ -56,6 +56,7 @@ class ParameterTest extends \PHPUnit\Framework\TestCase {
|
|||||||
// optional single value
|
// optional single value
|
||||||
$arrayType = new ArrayType("int_array_single", Parameter::TYPE_INT, true);
|
$arrayType = new ArrayType("int_array_single", Parameter::TYPE_INT, true);
|
||||||
$this->assertTrue($arrayType->parseParam(1));
|
$this->assertTrue($arrayType->parseParam(1));
|
||||||
|
$this->assertEquals([1], $arrayType->value);
|
||||||
|
|
||||||
// mixed values
|
// mixed values
|
||||||
$arrayType = new ArrayType("mixed_array", Parameter::TYPE_MIXED);
|
$arrayType = new ArrayType("mixed_array", Parameter::TYPE_MIXED);
|
||||||
|
@ -27,17 +27,4 @@ class TimeBasedTwoFactorTokenTest extends PHPUnit\Framework\TestCase {
|
|||||||
$this->assertEquals($code, $generated, "$code != $generated, at=$seed");
|
$this->assertEquals($code, $generated, "$code != $generated, at=$seed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testURL() {
|
|
||||||
$secret = Base32::encode("12345678901234567890");
|
|
||||||
$context = new Context();
|
|
||||||
|
|
||||||
// $context->
|
|
||||||
|
|
||||||
$token = new TimeBasedTwoFactorToken($secret);
|
|
||||||
$siteName = $context->getSettings()->getSiteName();
|
|
||||||
$username = $context->getUser()->getUsername();
|
|
||||||
$url = $token->getUrl($context);
|
|
||||||
$this->assertEquals("otpauth://totp/$username?secret=$secret&issuer=$siteName", $url);
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user