From 896bbe76b4d6b9bbf0ae647a8c0907671767b585 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 10 Apr 2021 01:33:40 +0200 Subject: [PATCH] SQL CaseWhen/Sum + ContactRequest API fix --- core/Api/ContactAPI.class.php | 11 +++++-- core/Api/MailAPI.class.php | 29 +++++++++++-------- core/Driver/SQL/Expression/CaseWhen.class.php | 23 +++++++++++++++ core/Driver/SQL/Expression/Sum.class.php | 20 +++++++++++++ core/Driver/SQL/MySQL.class.php | 12 +++++--- core/Driver/SQL/PostgreSQL.class.php | 12 +++++--- core/Driver/SQL/Query/Select.class.php | 24 ++++++++++----- core/Driver/SQL/SQL.class.php | 28 ++++++++++++------ 8 files changed, 121 insertions(+), 38 deletions(-) create mode 100644 core/Driver/SQL/Expression/CaseWhen.class.php create mode 100644 core/Driver/SQL/Expression/Sum.class.php diff --git a/core/Api/ContactAPI.class.php b/core/Api/ContactAPI.class.php index 084a7a3..b4668bf 100644 --- a/core/Api/ContactAPI.class.php +++ b/core/Api/ContactAPI.class.php @@ -11,6 +11,8 @@ namespace Api { public function __construct(User $user, bool $externalCall, array $params) { parent::__construct($user, $externalCall, $params); $this->messageId = null; + $this->csrfTokenRequired = false; + } protected function sendMail(string $name, ?string $fromEmail, string $subject, string $message, ?string $to = null): bool { @@ -23,6 +25,7 @@ namespace Api { "to" => $to )); + $this->lastError = $request->getLastError(); if ($this->success) { $this->messageId = $request->getResult()["messageId"]; } @@ -39,6 +42,9 @@ namespace Api\Contact { use Api\Parameter\StringType; use Api\VerifyCaptcha; use Driver\SQL\Condition\Compare; + use Driver\SQL\Condition\CondNot; + use Driver\SQL\Expression\CaseWhen; + use Driver\SQL\Expression\Sum; use Objects\User; class Request extends ContactAPI { @@ -219,9 +225,10 @@ namespace Api\Contact { } $sql = $this->user->getSQL(); - $res = $sql->select("ContactRequest.uid", "from_name", "from_email", "from_name", $sql->sum("read")) + $res = $sql->select("ContactRequest.uid", "from_name", "from_email", "from_name", + new Sum(new CaseWhen(new CondNot("ContactMessage.read"), 1, 0), "unread")) ->from("ContactRequest") - ->groupBy("uid") + ->groupBy("ContactRequest.uid") ->leftJoin("ContactMessage", "ContactRequest.uid", "ContactMessage.request_id") ->execute(); diff --git a/core/Api/MailAPI.class.php b/core/Api/MailAPI.class.php index 1578d60..0b51213 100644 --- a/core/Api/MailAPI.class.php +++ b/core/Api/MailAPI.class.php @@ -187,7 +187,7 @@ namespace Api\Mail { $sql = $this->user->getSQL(); $query = $sql->insert("ContactMessage", ["request_id", "user_id", "message", "messageId", "created_at"]) - ->onDuplicateKeyStrategy(new UpdateStrategy(["message_id"], ["message" => new Column("message")])); + ->onDuplicateKeyStrategy(new UpdateStrategy(["messageId"], ["message" => new Column("message")])); $entityIds = []; foreach ($messages as $message) { @@ -279,6 +279,7 @@ namespace Api\Mail { } private function runSearch($mbox, string $searchCriteria, ?\DateTime $lastSyncDateTime, array $messageIds, array &$messages) { + $result = @imap_search($mbox, $searchCriteria); if ($result === false) { $err = imap_last_error(); // might return false, if not messages were found, so we can just abort without throwing an error @@ -287,7 +288,7 @@ namespace Api\Mail { foreach ($result as $msgNo) { $header = imap_headerinfo($mbox, $msgNo); - $date = $this->parseDate($header->date); + $date = $this->parseDate($header->date); if ($date === false) { return false; } @@ -309,7 +310,9 @@ namespace Api\Mail { foreach ($structure->parts as $part) { $disposition = (property_exists($part, "disposition") ? $part->disposition : null); if ($disposition === "attachment") { - $fileName = array_filter($part->dparameters, function($param) { return $param->attribute === "filename"; }); + $fileName = array_filter($part->dparameters, function ($param) { + return $param->attribute === "filename"; + }); if (count($fileName) > 0) { $attachments[] = $fileName[0]->value; } @@ -320,14 +323,16 @@ namespace Api\Mail { $body = imap_fetchbody($mbox, $msgNo, "1"); $body = $this->parseBody($body); - $messages[] = [ - "messageId" => $messageId, - "requestId" => $requestId, - "timestamp" => $date->getTimestamp(), - "from" => $senderAddress, - "body" => $body, - "attachments" => $attachments - ]; + if (!isset($messageId[$messageId])) { + $messages[$messageId] = [ + "messageId" => $messageId, + "requestId" => $requestId, + "timestamp" => $date->getTimestamp(), + "from" => $senderAddress, + "body" => $body, + "attachments" => $attachments + ]; + } } } } @@ -381,7 +386,7 @@ namespace Api\Mail { $messages = []; foreach ($boxes as $box) { imap_reopen($mbox, $box); - if (!$this->runSearch($mbox, $searchCriteria, $lastSyncDateTime, $messageIds,$messages)) { + if (!$this->runSearch($mbox, $searchCriteria, $lastSyncDateTime, $messageIds, $messages)) { return false; } } diff --git a/core/Driver/SQL/Expression/CaseWhen.class.php b/core/Driver/SQL/Expression/CaseWhen.class.php new file mode 100644 index 0000000..4c44941 --- /dev/null +++ b/core/Driver/SQL/Expression/CaseWhen.class.php @@ -0,0 +1,23 @@ +condition = $condition; + $this->trueCase = $trueCase; + $this->falseCase = $falseCase; + } + + public function getCondition(): Condition { return $this->condition; } + public function getTrueCase() { return $this->trueCase; } + public function getFalseCase() { return $this->falseCase; } + +} \ No newline at end of file diff --git a/core/Driver/SQL/Expression/Sum.class.php b/core/Driver/SQL/Expression/Sum.class.php new file mode 100644 index 0000000..8bbb39f --- /dev/null +++ b/core/Driver/SQL/Expression/Sum.class.php @@ -0,0 +1,20 @@ +value = $value; + $this->alias = $alias; + } + + public function getValue() { return $this->value; } + public function getAlias(): string { return $this->alias; } + +} \ No newline at end of file diff --git a/core/Driver/SQL/MySQL.class.php b/core/Driver/SQL/MySQL.class.php index 5948a8b..7a8f7a8 100644 --- a/core/Driver/SQL/MySQL.class.php +++ b/core/Driver/SQL/MySQL.class.php @@ -290,7 +290,7 @@ class MySQL extends SQL { } } - public function addValue($val, &$params = NULL) { + public function addValue($val, &$params = NULL, bool $unsafe = false) { if ($val instanceof Keyword) { return $val->getValue(); } else if ($val instanceof CurrentColumn) { @@ -300,8 +300,12 @@ class MySQL extends SQL { } else if ($val instanceof Expression) { return $this->createExpression($val, $params); } else { - $params[] = $val; - return "?"; + if ($unsafe) { + return $this->getUnsafeValue($val); + } else { + $params[] = $val; + return "?"; + } } } @@ -403,7 +407,7 @@ class MySQL extends SQL { return $query; } - protected function createExpression(Expression $exp, array &$params) { + protected function createExpression(Expression $exp, array &$params): ?string { if ($exp instanceof DateAdd || $exp instanceof DateSub) { $lhs = $this->addValue($exp->getLHS(), $params); $rhs = $this->addValue($exp->getRHS(), $params); diff --git a/core/Driver/SQL/PostgreSQL.class.php b/core/Driver/SQL/PostgreSQL.class.php index 9333bec..7a8a40b 100644 --- a/core/Driver/SQL/PostgreSQL.class.php +++ b/core/Driver/SQL/PostgreSQL.class.php @@ -276,7 +276,7 @@ class PostgreSQL extends SQL { } } - public function addValue($val, &$params = NULL) { + public function addValue($val, &$params = NULL, bool $unsafe = false) { if ($val instanceof Keyword) { return $val->getValue(); } else if ($val instanceof CurrentTable) { @@ -288,8 +288,12 @@ class PostgreSQL extends SQL { } else if ($val instanceof Expression) { return $this->createExpression($val, $params); } else { - $params[] = is_bool($val) ? ($val ? "TRUE" : "FALSE") : $val; - return '$' . count($params); + if ($unsafe) { + return $this->getUnsafeValue($val); + } else { + $params[] = is_bool($val) ? ($val ? "TRUE" : "FALSE") : $val; + return '$' . count($params); + } } } @@ -419,7 +423,7 @@ class PostgreSQL extends SQL { return $query; } - protected function createExpression(Expression $exp, array &$params) { + protected function createExpression(Expression $exp, array &$params): ?string { if ($exp instanceof DateAdd || $exp instanceof DateSub) { $lhs = $this->addValue($exp->getLHS(), $params); $rhs = $this->addValue($exp->getRHS(), $params); diff --git a/core/Driver/SQL/Query/Select.class.php b/core/Driver/SQL/Query/Select.class.php index 905a94c..bf51178 100644 --- a/core/Driver/SQL/Query/Select.class.php +++ b/core/Driver/SQL/Query/Select.class.php @@ -8,7 +8,7 @@ use Driver\SQL\SQL; class Select extends Query { - private array $columns; + private array $selectValues; private array $tables; private array $conditions; private array $joins; @@ -18,9 +18,9 @@ class Select extends Query { private int $limit; private int $offset; - public function __construct($sql, ...$columns) { + public function __construct($sql, ...$selectValues) { parent::__construct($sql); - $this->columns = (!empty($columns) && is_array($columns[0])) ? $columns[0] : $columns; + $this->selectValues = (!empty($selectValues) && is_array($selectValues[0])) ? $selectValues[0] : $selectValues; $this->tables = array(); $this->conditions = array(); $this->joins = array(); @@ -85,7 +85,7 @@ class Select extends Query { return $this->sql->executeQuery($this, true); } - public function getColumns(): array { return $this->columns; } + public function getSelectValues(): array { return $this->selectValues; } public function getTables(): array { return $this->tables; } public function getConditions(): array { return $this->conditions; } public function getJoins(): array { return $this->joins; } @@ -96,11 +96,21 @@ class Select extends Query { public function getGroupBy(): array { return $this->groupColumns; } public function build(array &$params): ?string { - $columns = $this->sql->columnName($this->getColumns()); + + $selectValues = []; + foreach ($this->selectValues as $value) { + if (is_string($value)) { + $selectValues[] = $this->sql->columnName($value); + } else { + $selectValues[] = $this->sql->addValue($value, $params); + } + } + $tables = $this->getTables(); + $selectValues = implode(",", $selectValues); if (!$tables) { - return "SELECT $columns"; + return "SELECT $selectValues"; } $tables = $this->sql->tableName($tables); @@ -135,6 +145,6 @@ class Select extends Query { $limit = ($this->getLimit() > 0 ? (" LIMIT " . $this->getLimit()) : ""); $offset = ($this->getOffset() > 0 ? (" OFFSET " . $this->getOffset()) : ""); - return "SELECT $columns FROM $tables$joinStr$where$groupBy$orderBy$limit$offset"; + return "SELECT $selectValues FROM $tables$joinStr$where$groupBy$orderBy$limit$offset"; } } \ No newline at end of file diff --git a/core/Driver/SQL/SQL.class.php b/core/Driver/SQL/SQL.class.php index ab26632..21cdcf5 100644 --- a/core/Driver/SQL/SQL.class.php +++ b/core/Driver/SQL/SQL.class.php @@ -15,8 +15,10 @@ use Driver\SQL\Constraint\Constraint; use \Driver\SQL\Constraint\Unique; use \Driver\SQL\Constraint\PrimaryKey; use \Driver\SQL\Constraint\ForeignKey; +use Driver\SQL\Expression\CaseWhen; use Driver\SQL\Expression\CurrentTimeStamp; use Driver\SQL\Expression\Expression; +use Driver\SQL\Expression\Sum; use Driver\SQL\Query\AlterTable; use Driver\SQL\Query\CreateProcedure; use Driver\SQL\Query\CreateTable; @@ -187,8 +189,10 @@ abstract class SQL { } protected function getUnsafeValue($value): ?string { - if (is_string($value) || is_numeric($value) || is_bool($value)) { + if (is_string($value)) { return "'" . addslashes("$value") . "'"; // unsafe operation here... + } else if (is_numeric($value) || is_bool($value)) { + return $value; } else if ($value instanceof Column) { return $this->columnName($value); } else if ($value === null) { @@ -200,7 +204,7 @@ abstract class SQL { } protected abstract function getValueDefinition($val); - public abstract function addValue($val, &$params = NULL); + public abstract function addValue($val, &$params = NULL, bool $unsafe = false); protected abstract function buildUnsafe(Query $statement): string; public abstract function tableName($table): string; @@ -222,12 +226,6 @@ abstract class SQL { } } - public function sum($col): Keyword { - $sumCol = strtolower(str_replace(".","_", $col)) . "_sum"; - $col = $this->columnName($col); - return new Keyword("SUM($col) AS $sumCol"); - } - public function distinct($col): Keyword { $col = $this->columnName($col); return new Keyword("DISTINCT($col)"); @@ -312,11 +310,23 @@ abstract class SQL { } } - protected function createExpression(Expression $exp, array &$params) { + protected function createExpression(Expression $exp, array &$params): ?string { if ($exp instanceof Column) { return $this->columnName($exp); } else if ($exp instanceof Query) { return "(" . $exp->build($params) . ")"; + } else if ($exp instanceof CaseWhen) { + $condition = $this->buildCondition($exp->getCondition(), $params); + + // psql requires constant values here + $trueCase = $this->addValue($exp->getTrueCase(), $params, true); + $falseCase = $this->addValue($exp->getFalseCase(), $params, true); + + return "CASE WHEN $condition THEN $trueCase ELSE $falseCase END"; + } else if ($exp instanceof Sum) { + $value = $this->addValue($exp->getValue(), $params); + $alias = $exp->getAlias(); + return "SUM($value) AS $alias"; } else { $this->lastError = "Unsupported expression type: " . get_class($exp); return null;