Bugfix
This commit is contained in:
parent
ce647d4423
commit
58c905acf5
@ -49,10 +49,8 @@ namespace Api\Mail {
|
|||||||
use Driver\SQL\Column\Column;
|
use Driver\SQL\Column\Column;
|
||||||
use Driver\SQL\Condition\Compare;
|
use Driver\SQL\Condition\Compare;
|
||||||
use Driver\SQL\Condition\CondIn;
|
use Driver\SQL\Condition\CondIn;
|
||||||
use Driver\SQL\Strategy\UpdateStrategy;
|
|
||||||
use External\PHPMailer\Exception;
|
use External\PHPMailer\Exception;
|
||||||
use External\PHPMailer\PHPMailer;
|
use External\PHPMailer\PHPMailer;
|
||||||
use Objects\ConnectionData;
|
|
||||||
use Objects\Context;
|
use Objects\Context;
|
||||||
use Objects\DatabaseEntity\GpgKey;
|
use Objects\DatabaseEntity\GpgKey;
|
||||||
|
|
||||||
@ -201,267 +199,6 @@ namespace Api\Mail {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: IMAP mail settings :(
|
|
||||||
// TODO: attachments
|
|
||||||
class Sync extends MailAPI {
|
|
||||||
|
|
||||||
public function __construct(Context $context, bool $externalCall = false) {
|
|
||||||
parent::__construct($context, $externalCall, array());
|
|
||||||
$this->loginRequired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function fetchMessageIds() {
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
$res = $sql->select("id", "messageId")
|
|
||||||
->from("ContactRequest")
|
|
||||||
->where(new Compare("messageId", NULL, "!="))
|
|
||||||
->execute();
|
|
||||||
|
|
||||||
$this->success = ($res !== false);
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
if (!$this->success) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$messageIds = [];
|
|
||||||
foreach ($res as $row) {
|
|
||||||
$messageIds[$row["messageId"]] = $row["id"];
|
|
||||||
}
|
|
||||||
return $messageIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function findContactRequest(array &$messageIds, array &$references): ?int {
|
|
||||||
foreach ($references as &$ref) {
|
|
||||||
if (isset($messageIds[$ref])) {
|
|
||||||
return $messageIds[$ref];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function parseBody(string $body): string {
|
|
||||||
// TODO clean this up
|
|
||||||
return trim($body);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function insertMessages($messages): bool {
|
|
||||||
$sql = $this->context->getSQL();
|
|
||||||
|
|
||||||
$query = $sql->insert("ContactMessage", ["request_id", "user_id", "message", "messageId", "created_at"])
|
|
||||||
->onDuplicateKeyStrategy(new UpdateStrategy(["messageId"], ["message" => new Column("message")]));
|
|
||||||
|
|
||||||
$entityIds = [];
|
|
||||||
foreach ($messages as $message) {
|
|
||||||
$requestId = $message["requestId"];
|
|
||||||
$query->addRow(
|
|
||||||
$requestId,
|
|
||||||
$sql->select("id")->from("User")->where(new Compare("email", $message["from"]))->limit(1),
|
|
||||||
$message["body"],
|
|
||||||
$message["messageId"],
|
|
||||||
(new \DateTime())->setTimeStamp($message["timestamp"]),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!in_array($requestId, $entityIds)) {
|
|
||||||
$entityIds[] = $requestId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->success = $query->execute();
|
|
||||||
$this->lastError = $sql->getLastError();
|
|
||||||
|
|
||||||
// Update entity log
|
|
||||||
if ($this->success && count($entityIds) > 0) {
|
|
||||||
$sql->update("EntityLog")
|
|
||||||
->set("modified", $sql->now())
|
|
||||||
->where(new CondIn(new Column("entityId"), $entityIds))
|
|
||||||
->execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function parseDate($date) {
|
|
||||||
$formats = [null, "D M d Y H:i:s e+", "D, j M Y H:i:s e+"];
|
|
||||||
foreach ($formats as $format) {
|
|
||||||
try {
|
|
||||||
$dateObj = ($format === null ? new \DateTime($date) : \DateTime::createFromFormat($format, $date));
|
|
||||||
if ($dateObj) {
|
|
||||||
return $dateObj;
|
|
||||||
}
|
|
||||||
} catch (\Exception $exception) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->createError("Could not parse date: $date");
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getReference(ConnectionData $mailConfig): string {
|
|
||||||
$port = 993;
|
|
||||||
$host = str_replace("smtp", "imap", $mailConfig->getHost());
|
|
||||||
$flags = ["/ssl"];
|
|
||||||
return '{' . $host . ':' . $port . implode("", $flags) . '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
private function connect(ConnectionData $mailConfig) {
|
|
||||||
|
|
||||||
$username = $mailConfig->getLogin();
|
|
||||||
$password = $mailConfig->getPassword();
|
|
||||||
$ref = $this->getReference($mailConfig);
|
|
||||||
$mbox = @imap_open($ref, $username, $password, OP_READONLY);
|
|
||||||
if (!$mbox) {
|
|
||||||
return $this->createError("Can't connect to mail server via IMAP: " . imap_last_error());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $mbox;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function listFolders(ConnectionData $mailConfig, $mbox) {
|
|
||||||
|
|
||||||
$boxes = @imap_list($mbox, $this->getReference($mailConfig), '*');
|
|
||||||
if (!$boxes) {
|
|
||||||
return $this->createError("Error listing imap folders: " . imap_last_error());
|
|
||||||
}
|
|
||||||
|
|
||||||
return $boxes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getSenderAddress($header): string {
|
|
||||||
if (property_exists($header, "reply_to") && count($header->reply_to) > 0) {
|
|
||||||
$mailBox = $header->reply_to[0]->mailbox;
|
|
||||||
$host = $header->reply_to[0]->host;
|
|
||||||
} else if (property_exists($header, "from") && count($header->from) > 0) {
|
|
||||||
$mailBox = $header->from[0]->mailbox;
|
|
||||||
$host = $header->from[0]->host;
|
|
||||||
} else {
|
|
||||||
return "unknown_addr";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "$mailBox@$host";
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
return empty($err) ? true : $this->createError("Could not run search: $err");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($result as $msgNo) {
|
|
||||||
$header = imap_headerinfo($mbox, $msgNo);
|
|
||||||
$date = $this->parseDate($header->date);
|
|
||||||
if ($date === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($lastSyncDateTime === null || \datetimeDiff($lastSyncDateTime, $date) > 0) {
|
|
||||||
|
|
||||||
$references = property_exists($header, "references") ?
|
|
||||||
explode(" ", $header->references) : [];
|
|
||||||
|
|
||||||
$requestId = $this->findContactRequest($messageIds, $references);
|
|
||||||
if ($requestId) {
|
|
||||||
$messageId = $header->message_id;
|
|
||||||
$senderAddress = $this->getSenderAddress($header);
|
|
||||||
|
|
||||||
$structure = imap_fetchstructure($mbox, $msgNo);
|
|
||||||
$attachments = [];
|
|
||||||
$hasAttachments = (property_exists($structure, "parts"));
|
|
||||||
if ($hasAttachments) {
|
|
||||||
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";
|
|
||||||
});
|
|
||||||
if (count($fileName) > 0) {
|
|
||||||
$attachments[] = $fileName[0]->value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$body = imap_fetchbody($mbox, $msgNo, "1");
|
|
||||||
$body = $this->parseBody($body);
|
|
||||||
|
|
||||||
if (!isset($messageId[$messageId])) {
|
|
||||||
$messages[$messageId] = [
|
|
||||||
"messageId" => $messageId,
|
|
||||||
"requestId" => $requestId,
|
|
||||||
"timestamp" => $date->getTimestamp(),
|
|
||||||
"from" => $senderAddress,
|
|
||||||
"body" => $body,
|
|
||||||
"attachments" => $attachments
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function _execute(): bool {
|
|
||||||
|
|
||||||
if (!function_exists("imap_open")) {
|
|
||||||
return $this->createError("IMAP is not enabled. Enable it inside the php config. For more information visit: https://www.php.net/manual/en/imap.setup.php");
|
|
||||||
}
|
|
||||||
|
|
||||||
$messageIds = $this->fetchMessageIds();
|
|
||||||
if ($messageIds === false) {
|
|
||||||
return false;
|
|
||||||
} else if (count($messageIds) === 0) {
|
|
||||||
// nothing to sync here
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$mailConfig = $this->getMailConfig();
|
|
||||||
if (!$this->success || $mailConfig === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$mbox = $this->connect($mailConfig);
|
|
||||||
if ($mbox === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$boxes = $this->listFolders($mailConfig, $mbox);
|
|
||||||
if ($boxes === false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$now = (new \DateTime())->getTimestamp();
|
|
||||||
$lastSync = intval($mailConfig->getProperty("last_sync", "0"));
|
|
||||||
if ($lastSync > 0) {
|
|
||||||
$lastSyncDateTime = (new \DateTime())->setTimeStamp($lastSync);
|
|
||||||
$dateStr = $lastSyncDateTime->format("d-M-Y");
|
|
||||||
$searchCriteria = "SINCE \"$dateStr\"";
|
|
||||||
} else {
|
|
||||||
$lastSyncDateTime = null;
|
|
||||||
$searchCriteria = "ALL";
|
|
||||||
}
|
|
||||||
|
|
||||||
$messages = [];
|
|
||||||
foreach ($boxes as $box) {
|
|
||||||
imap_reopen($mbox, $box);
|
|
||||||
if (!$this->runSearch($mbox, $searchCriteria, $lastSyncDateTime, $messageIds, $messages)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@imap_close($mbox);
|
|
||||||
if (!empty($messages) && !$this->insertMessages($messages)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
$req = new \Api\Settings\Set($this->context);
|
|
||||||
$this->success = $req->execute(array("settings" => array("mail_last_sync" => "$now")));
|
|
||||||
$this->lastError = $req->getLastError();
|
|
||||||
return $this->success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SendQueue extends MailAPI {
|
class SendQueue extends MailAPI {
|
||||||
public function __construct(Context $context, bool $externalCall = false) {
|
public function __construct(Context $context, bool $externalCall = false) {
|
||||||
parent::__construct($context, $externalCall, [
|
parent::__construct($context, $externalCall, [
|
||||||
|
@ -246,8 +246,9 @@ namespace Documents\Install {
|
|||||||
$success = false;
|
$success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version_compare(PHP_VERSION, '7.4', '<')) {
|
$requiredVersion = '8.0';
|
||||||
$failedRequirements[] = "PHP Version <b>>= 7.4</b> is required. Got: <b>" . PHP_VERSION . "</b>";
|
if (version_compare(PHP_VERSION, $requiredVersion, '<')) {
|
||||||
|
$failedRequirements[] = "PHP Version <b>>= $requiredVersion</b> is required. Got: <b>" . PHP_VERSION . "</b>";
|
||||||
$success = false;
|
$success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ abstract class View extends StaticView {
|
|||||||
$attributes["class"] = implode(" ", $classes);
|
$attributes["class"] = implode(" ", $classes);
|
||||||
}
|
}
|
||||||
|
|
||||||
$content = array_map(function ($item) { html_tag("li", [], $item, false); }, $items);
|
$content = array_map(function ($item) { return html_tag("li", [], $item, false); }, $items);
|
||||||
return html_tag_ex($tag, $attributes, $content, false);
|
return html_tag_ex($tag, $attributes, $content, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,11 @@ class ApiRoute extends AbstractRoute {
|
|||||||
parent::__construct("/api/{endpoint:?}/{method:?}", false);
|
parent::__construct("/api/{endpoint:?}/{method:?}", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function checkClass(string $className): bool {
|
||||||
|
$classPath = getClassPath($className);
|
||||||
|
return file_exists($classPath) && class_exists($className);
|
||||||
|
}
|
||||||
|
|
||||||
public function call(Router $router, array $params): string {
|
public function call(Router $router, array $params): string {
|
||||||
if (empty($params["endpoint"])) {
|
if (empty($params["endpoint"])) {
|
||||||
header("Content-Type: text/html");
|
header("Content-Type: text/html");
|
||||||
@ -22,7 +27,8 @@ class ApiRoute extends AbstractRoute {
|
|||||||
$response = createError("Invalid Method");
|
$response = createError("Invalid Method");
|
||||||
} else {
|
} else {
|
||||||
$apiEndpoint = ucfirst($params["endpoint"]);
|
$apiEndpoint = ucfirst($params["endpoint"]);
|
||||||
if (!empty($params["method"])) {
|
$isNestedAPI = !empty($params["method"]);
|
||||||
|
if ($isNestedAPI) {
|
||||||
$apiMethod = ucfirst($params["method"]);
|
$apiMethod = ucfirst($params["method"]);
|
||||||
$parentClass = "\\Api\\${apiEndpoint}API";
|
$parentClass = "\\Api\\${apiEndpoint}API";
|
||||||
$apiClass = "\\Api\\${apiEndpoint}\\${apiMethod}";
|
$apiClass = "\\Api\\${apiEndpoint}\\${apiMethod}";
|
||||||
@ -32,11 +38,18 @@ class ApiRoute extends AbstractRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$file = getClassPath($parentClass);
|
$classFound = False;
|
||||||
if (!file_exists($file) || !class_exists($parentClass) || !class_exists($apiClass)) {
|
|
||||||
http_response_code(404);
|
// first: check if the parent class exists, for example:
|
||||||
$response = createError("Not found");
|
// /stats => Stats.class.php
|
||||||
} else {
|
// /mail/send => MailAPI.class.php
|
||||||
|
if ($this->checkClass($parentClass)) {
|
||||||
|
if (!$isNestedAPI || class_exists($apiClass)) {
|
||||||
|
$classFound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($classFound) {
|
||||||
$apiClass = new ReflectionClass($apiClass);
|
$apiClass = new ReflectionClass($apiClass);
|
||||||
if (!$apiClass->isSubclassOf(Request::class) || !$apiClass->isInstantiable()) {
|
if (!$apiClass->isSubclassOf(Request::class) || !$apiClass->isInstantiable()) {
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
@ -48,6 +61,9 @@ class ApiRoute extends AbstractRoute {
|
|||||||
$response["success"] = $success;
|
$response["success"] = $success;
|
||||||
$response["msg"] = $request->getLastError();
|
$response["msg"] = $request->getLastError();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
http_response_code(404);
|
||||||
|
$response = createError("Not found");
|
||||||
}
|
}
|
||||||
} catch (ReflectionException $e) {
|
} catch (ReflectionException $e) {
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
|
Loading…
Reference in New Issue
Block a user