Core v2.3, N:M Relations

This commit is contained in:
2022-11-20 17:13:53 +01:00
parent b5b8f9b856
commit 303a5b69b5
41 changed files with 962 additions and 1047 deletions

View File

@@ -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;
}
}
}

View File

@@ -31,6 +31,7 @@ namespace Core\API\Groups {
use Core\API\Parameter\Parameter;
use Core\API\Parameter\StringType;
use Core\Objects\Context;
use Core\Objects\DatabaseEntity\Controller\NMRelation;
use Core\Objects\DatabaseEntity\Group;
class Fetch extends GroupsAPI {
@@ -46,20 +47,6 @@ namespace Core\API\Groups {
$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 {
$page = $this->getParam("page");
if($page < 1) {
@@ -71,39 +58,42 @@ namespace Core\API\Groups {
return $this->createError("Invalid fetch count");
}
if (!$this->fetchGroupCount()) {
return false;
$sql = $this->context->getSQL();
$groupCount = Group::count($sql);
if ($groupCount === false) {
return $this->createError("Error fetching group count: " . $sql->getLastError());
}
$sql = $this->context->getSQL();
$res = $sql->select("Group.id as groupId", "Group.name as groupName", "Group.color as groupColor", $sql->count("UserGroup.user_id"))
->from("Group")
->leftJoin("UserGroup", "UserGroup.group_id", "Group.id")
->groupBy("Group.id")
->orderBy("Group.id")
$groups = Group::findBy(Group::createBuilder($sql, false)
->orderBy("id")
->ascending()
->limit($count)
->offset(($page - 1) * $count)
->execute();
->offset(($page - 1) * $count));
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
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,
);
}
if ($groups !== false) {
$this->result["groups"] = [];
$this->result["pageCount"] = intval(ceil($this->groupCount / $count));
$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;
@@ -162,7 +152,7 @@ namespace Core\API\Groups {
public function _execute(): bool {
$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.");
}

View File

@@ -47,7 +47,8 @@ namespace Core\API\Logs {
$shownLogLevels = array_slice(Logger::LOG_LEVELS, $logLevel);
}
$query = SystemLog::findAllBuilder($sql)
$query = SystemLog::createBuilder($sql, false)
->orderBy("timestamp")
->descending();
@@ -59,7 +60,7 @@ namespace Core\API\Logs {
$query->where(new CondIn(new Column("severity"), $shownLogLevels));
}
$logEntries = $query->execute();
$logEntries = SystemLog::findBy($query);
$this->success = $logEntries !== false;
$this->lastError = $sql->getLastError();

View File

@@ -46,6 +46,7 @@ namespace Core\API\Mail {
use Core\API\MailAPI;
use Core\API\Parameter\Parameter;
use Core\API\Parameter\StringType;
use Core\Objects\DatabaseEntity\MailQueueItem;
use DateTimeInterface;
use Core\Driver\SQL\Column\Column;
use Core\Driver\SQL\Condition\Compare;
@@ -84,7 +85,7 @@ namespace Core\API\Mail {
class Send extends MailAPI {
public function __construct(Context $context, $externalCall = false) {
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),
'body' => new StringType('body', -1),
'replyTo' => new Parameter('replyTo', Parameter::TYPE_EMAIL, true, null),
@@ -104,7 +105,7 @@ namespace Core\API\Mail {
$fromMail = $mailConfig->getProperty('from');
$mailFooter = $mailConfig->getProperty('mail_footer');
$toMail = $this->getParam('to') ?? $fromMail;
$toMail = $this->getParam('to');
$subject = $this->getParam('subject');
$replyTo = $this->getParam('replyTo');
$replyName = $this->getParam('replyName');
@@ -119,10 +120,8 @@ namespace Core\API\Mail {
if ($mailAsync) {
$sql = $this->context->getSQL();
$this->success = $sql->insert("MailQueue", ["from", "to", "subject", "body",
"replyTo", "replyName", "gpgFingerprint"])
->addRow($fromMail, $toMail, $subject, $body, $replyTo, $replyName, $gpgFingerprint)
->execute() !== false;
$mailQueueItem = new MailQueueItem($fromMail, $toMail, $subject, $body, $replyTo, $replyName, $gpgFingerprint);
$this->success = $mailQueueItem->save($sql);
$this->lastError = $sql->getLastError();
return $this->success;
}
@@ -223,77 +222,37 @@ namespace Core\API\Mail {
}
$sql = $this->context->getSQL();
$res = $sql->select("id", "from", "to", "subject", "body",
"replyTo", "replyName", "gpgFingerprint", "retryCount")
->from("MailQueue")
$mailQueueItems = MailQueueItem::findBy(MailQueueItem::createBuilder($sql, false)
->where(new Compare("retryCount", 0, ">"))
->where(new Compare("status", "waiting"))
->where(new Compare("nextTry", $sql->now(), "<="))
->execute();
->where(new Compare("nextTry", $sql->now(), "<=")));
$this->success = ($res !== false);
$this->success = ($mailQueueItems !== false);
$this->lastError = $sql->getLastError();
if ($this->success && is_array($res)) {
if ($this->success && is_array($mailQueueItems)) {
if ($debug) {
echo "Found " . count($res) . " mails to send" . PHP_EOL;
echo "Found " . count($mailQueueItems) . " mails to send" . PHP_EOL;
}
$successfulMails = [];
foreach ($res as $row) {
$successfulMails = 0;
foreach ($mailQueueItems as $mailQueueItem) {
if (time() - $startTime >= 45) {
$this->lastError = "Not able to process whole mail queue within 45 seconds, will continue on next time";
break;
}
$to = $row["to"];
$subject = $row["subject"];
if ($debug) {
echo "Sending subject=$subject to=$to" . PHP_EOL;
echo "Sending subject=$mailQueueItem->subject to=$mailQueueItem->to" . PHP_EOL;
}
$mailId = intval($row["id"]);
$retryCount = intval($row["retryCount"]);
$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;
if ($mailQueueItem->send($this->context)) {
$successfulMails++;
}
}
$this->success = count($successfulMails) === count($res);
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();
}
$this->success = $successfulMails === count($mailQueueItems);
}
return $this->success;

View File

@@ -19,6 +19,7 @@ namespace Core\API\News {
use Core\API\Parameter\StringType;
use Core\Driver\SQL\Condition\Compare;
use Core\Objects\Context;
use Core\Objects\DatabaseEntity\Group;
use Core\Objects\DatabaseEntity\News;
class Get extends NewsAPI {
@@ -40,7 +41,7 @@ namespace Core\API\News {
}
$sql = $this->context->getSQL();
$newsQuery = News::findAllBuilder($sql)
$newsQuery = News::createBuilder($sql, false)
->limit($limit)
->orderBy("published_at")
->descending()
@@ -50,7 +51,7 @@ namespace Core\API\News {
$newsQuery->where(new Compare("published_at", $since, ">="));
}
$newsArray = $newsQuery->execute();
$newsArray = News::findBy($newsQuery);
$this->success = $newsArray !== null;
$this->lastError = $sql->getLastError();
@@ -113,7 +114,7 @@ namespace Core\API\News {
return false;
} else if ($news === null) {
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.");
}
@@ -144,7 +145,7 @@ namespace Core\API\News {
return false;
} else if ($news === null) {
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.");
}

View File

@@ -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;
}
}
}

View File

@@ -3,6 +3,7 @@
namespace Core\API {
use Core\Objects\Context;
use Core\Objects\DatabaseEntity\Group;
abstract class PermissionAPI extends Request {
@@ -12,7 +13,7 @@ namespace Core\API {
protected function checkStaticPermission(): bool {
$user = $this->context->getUser();
if (!$user || !$user->hasGroup(USER_GROUP_ADMIN)) {
if (!$user || !$user->hasGroup(Group::ADMIN)) {
return $this->createError("Permission denied.");
}
@@ -34,7 +35,6 @@ namespace Core\API\Permission {
use Core\Driver\SQL\Strategy\UpdateStrategy;
use Core\Objects\Context;
use Core\Objects\DatabaseEntity\Group;
use Core\Objects\DatabaseEntity\User;
class Check extends PermissionAPI {

View File

@@ -4,6 +4,7 @@ namespace Core\API;
use Core\API\Parameter\StringType;
use Core\Objects\Context;
use Core\Objects\DatabaseEntity\Group;
use Core\Objects\DatabaseEntity\User;
class Swagger extends Request {
@@ -90,7 +91,7 @@ class Swagger extends Request {
}
// 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;
}

View File

@@ -75,7 +75,7 @@ namespace Core\API {
$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();
$user = new User();
@@ -86,6 +86,7 @@ namespace Core\API {
$user->email = $email;
$user->confirmed = $confirmed;
$user->fullName = $fullName ?? "";
$user->groups = $groups;
$this->success = ($user->save($sql) !== FALSE);
$this->lastError = $sql->getLastError();
@@ -107,12 +108,11 @@ namespace Core\API {
protected function checkToken(string $token) : UserToken|bool {
$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.valid_until", $sql->now(), ">"))
->where(new Compare("UserToken.used", 0))
->fetchEntities()
->execute();
->fetchEntities());
if ($userToken === false) {
return $this->createError("Error verifying token: " . $sql->getLastError());
@@ -128,11 +128,16 @@ namespace Core\API {
namespace Core\API\User {
use Core\API\Parameter\ArrayType;
use Core\API\Parameter\Parameter;
use Core\API\Parameter\StringType;
use Core\API\Template\Render;
use Core\API\UserAPI;
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\Driver\SQL\Column\Column;
use Core\Driver\SQL\Condition\Compare;
@@ -146,12 +151,15 @@ namespace Core\API\User {
class Create extends UserAPI {
private User $user;
public function __construct(Context $context, $externalCall = false) {
parent::__construct($context, $externalCall, array(
'username' => new StringType('username', 32),
'email' => new Parameter('email', Parameter::TYPE_EMAIL, true, NULL),
'password' => new StringType('password'),
'confirmPassword' => new StringType('confirmPassword'),
'groups' => new ArrayType("groups", Parameter::TYPE_INT, true, true, [])
));
$this->loginRequired = true;
@@ -171,21 +179,36 @@ namespace Core\API\User {
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
$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) {
$this->user = $user;
$this->result["userId"] = $user->getId();
}
return $this->success;
}
public function getUser(): User {
return $this->user;
}
}
class Fetch extends UserAPI {
private int $userCount;
public function __construct(Context $context, $externalCall = false) {
parent::__construct($context, $externalCall, array(
'page' => new Parameter('page', Parameter::TYPE_INT, true, 1),
@@ -193,21 +216,7 @@ namespace Core\API\User {
));
}
private function getUserCount(): 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) {
private function selectIds($page, $count): array|bool {
$sql = $this->context->getSQL();
$res = $sql->select("User.id")
->from("User")
@@ -241,72 +250,57 @@ namespace Core\API\User {
return $this->createError("Invalid fetch count");
}
if (!$this->getUserCount()) {
return false;
}
$userIds = $this->selectIds($page, $count);
if ($userIds === false) {
return false;
$condition = null;
$currentUser = $this->context->getUser();
$fullInfo = ($currentUser->hasGroup(Group::ADMIN) ||
$currentUser->hasGroup(Group::SUPPORT));
if (!$fullInfo) {
$condition = new CondOr(
new Compare("User.id", $currentUser->getId()),
new CondBool("User.confirmed")
);
}
$sql = $this->context->getSQL();
$res = $sql->select("User.id as userId", "User.name", "User.email", "User.registered_at", "User.confirmed",
"User.profile_picture", "User.full_name", "Group.id as groupId", "User.last_online",
"Group.name as groupName", "Group.color as groupColor")
->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();
$userCount = User::count($sql, $condition);
if ($userCount === false) {
return $this->createError("Error fetching user count: " . $sql->getLastError());
}
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
$currentUser = $this->context->getUser();
$userQuery = User::createBuilder($sql, false)
->orderBy("id")
->ascending()
->limit($count)
->offset(($page - 1) * $count)
->fetchEntities();
if ($this->success) {
$this->result["users"] = array();
foreach ($res as $row) {
$userId = intval($row["userId"]);
$groupId = $row["groupId"];
$groupName = $row["groupName"];
$groupColor = $row["groupColor"];
if ($condition) {
$userQuery->where($condition);
}
$fullInfo = ($userId === $currentUser->getId() ||
$currentUser->hasGroup(USER_GROUP_ADMIN) ||
$currentUser->hasGroup(USER_GROUP_SUPPORT));
$users = User::findBy($userQuery);
if ($users !== false) {
$this->result["users"] = [];
if (!isset($this->result["users"][$userId])) {
$user = array(
"id" => $userId,
"name" => $row["name"],
"fullName" => $row["full_name"],
"profilePicture" => $row["profile_picture"],
"email" => $row["email"],
"confirmed" => $sql->parseBool($row["confirmed"]),
"groups" => array(),
);
foreach ($users as $userId => $user) {
$serialized = $user->jsonSerialize();
if ($fullInfo) {
$user["registered_at"] = $row["registered_at"];
$user["last_online"] = $row["last_online"];
} else if (!$sql->parseBool($row["confirmed"])) {
continue;
if (!$fullInfo && $userId !== $currentUser->getId()) {
$publicAttributes = ["id", "name", "fullName", "profilePicture", "email", "groups"];
foreach (array_keys($serialized) as $attr) {
if (!in_array($attr, $publicAttributes)) {
unset($serialized[$attr]);
}
}
$this->result["users"][$userId] = $user;
}
if (!is_null($groupId)) {
$this->result["users"][$userId]["groups"][intval($groupId)] = array(
"id" => intval($groupId),
"name" => $groupName,
"color" => $groupColor
);
}
$this->result["users"][$userId] = $serialized;
}
$this->result["pageCount"] = intval(ceil($this->userCount / $count));
$this->result["totalCount"] = $this->userCount;
} else {
return $this->createError("Error fetching users: " . $sql->getLastError());
}
return $this->success;
@@ -338,13 +332,13 @@ namespace Core\API\User {
// either we are querying own info or we are support / admin
$currentUser = $this->context->getUser();
$canView = ($userId === $currentUser->getId() ||
$currentUser->hasGroup(USER_GROUP_ADMIN) ||
$currentUser->hasGroup(USER_GROUP_SUPPORT));
$currentUser->hasGroup(Group::ADMIN) ||
$currentUser->hasGroup(Group::SUPPORT));
// full info only when we have administrative privileges, or we are querying ourselves
$fullInfo = ($userId === $currentUser->getId() ||
$currentUser->hasGroup(USER_GROUP_ADMIN) ||
$currentUser->hasGroup(USER_GROUP_SUPPORT));
$currentUser->hasGroup(Group::ADMIN) ||
$currentUser->hasGroup(Group::SUPPORT));
if (!$canView) {
@@ -617,10 +611,9 @@ namespace Core\API\User {
$stayLoggedIn = $this->getParam('stayLoggedIn');
$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))
->fetchEntities()
->execute();
->fetchEntities());
if ($user !== false) {
if ($user === null) {
@@ -842,7 +835,7 @@ namespace Core\API\User {
$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.");
}
}
@@ -958,10 +951,9 @@ namespace Core\API\User {
$sql = $this->context->getSQL();
$email = $this->getParam("email");
$user = User::findBuilder($sql)
$user = User::findBy(User::createBuilder($sql, true)
->where(new Compare("email", $email))
->fetchEntities()
->execute();
->fetchEntities());
if ($user === false) {
return $this->createError("Could not fetch user details: " . $sql->getLastError());
} else if ($user !== null) {
@@ -1040,10 +1032,9 @@ namespace Core\API\User {
$email = $this->getParam("email");
$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.confirmed", false))
->execute();
->where(new Compare("User.confirmed", false)));
if ($user === false) {
return $this->createError("Error retrieving user details: " . $sql->getLastError());
@@ -1052,11 +1043,10 @@ namespace Core\API\User {
return true;
}
$userToken = UserToken::findBuilder($sql)
$userToken = UserToken::findBy(UserToken::createBuilder($sql, true)
->where(new Compare("used", false))
->where(new Compare("tokenType", UserToken::TYPE_EMAIL_CONFIRM))
->where(new Compare("user_id", $user->getId()))
->execute();
->where(new Compare("user_id", $user->getId())));
$validHours = 48;
if ($userToken === false) {
@@ -1366,12 +1356,11 @@ namespace Core\API\User {
$token = $this->getParam("token");
$sql = $this->context->getSQL();
$userToken = UserToken::findBuilder($sql)
$userToken = UserToken::findBy(UserToken::createBuilder($sql, true)
->where(new Compare("token", $token))
->where(new Compare("valid_until", $sql->now(), ">="))
->where(new Compare("user_id", $currentUser->getId()))
->where(new Compare("token_type", UserToken::TYPE_GPG_CONFIRM))
->execute();
->where(new Compare("token_type", UserToken::TYPE_GPG_CONFIRM)));
if ($userToken !== false) {
if ($userToken === null) {