web-base/Core/API/MailAPI.class.php

302 lines
10 KiB
PHP
Raw Normal View History

2020-06-27 22:47:12 +02:00
<?php
2022-11-18 18:06:46 +01:00
namespace Core\API {
2021-04-09 16:05:36 +02:00
2022-11-18 18:06:46 +01:00
use Core\Objects\ConnectionData;
use Core\Objects\Context;
2021-04-09 16:05:36 +02:00
2020-07-01 21:10:25 +02:00
abstract class MailAPI extends Request {
2022-06-20 19:52:31 +02:00
public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
parent::__construct($context, $externalCall, $params);
}
2021-04-09 16:05:36 +02:00
protected function getMailConfig(): ?ConnectionData {
2022-11-18 18:06:46 +01:00
$req = new \Core\API\Settings\Get($this->context);
2021-04-09 16:05:36 +02:00
$this->success = $req->execute(array("key" => "^mail_"));
$this->lastError = $req->getLastError();
if ($this->success) {
$settings = $req->getResult()["settings"];
if (!isset($settings["mail_enabled"]) || $settings["mail_enabled"] !== "1") {
$this->createError("Mail is not configured yet.");
return null;
}
$host = $settings["mail_host"] ?? "localhost";
$port = intval($settings["mail_port"] ?? "25");
$login = $settings["mail_username"] ?? "";
$password = $settings["mail_password"] ?? "";
$connectionData = new ConnectionData($host, $port, $login, $password);
$connectionData->setProperty("from", $settings["mail_from"] ?? "");
$connectionData->setProperty("last_sync", $settings["mail_last_sync"] ?? "");
2022-02-20 16:53:26 +01:00
$connectionData->setProperty("mail_footer", $settings["mail_footer"] ?? "");
2022-11-19 01:15:34 +01:00
$connectionData->setProperty("mail_async", $settings["mail_async"] ?? false);
2021-04-09 16:05:36 +02:00
return $connectionData;
}
2020-06-27 22:47:12 +02:00
2021-04-09 16:05:36 +02:00
return null;
}
2020-06-27 22:47:12 +02:00
}
}
2022-11-18 18:06:46 +01:00
namespace Core\API\Mail {
2020-06-27 22:47:12 +02:00
2022-11-18 18:06:46 +01:00
use Core\API\MailAPI;
use Core\API\Parameter\Parameter;
use Core\API\Parameter\StringType;
2021-12-08 16:53:43 +01:00
use DateTimeInterface;
2022-11-18 18:06:46 +01:00
use Core\Driver\SQL\Column\Column;
use Core\Driver\SQL\Condition\Compare;
use Core\Driver\SQL\Condition\CondIn;
use Core\External\PHPMailer\Exception;
use Core\External\PHPMailer\PHPMailer;
use Core\Objects\Context;
use Core\Objects\DatabaseEntity\GpgKey;
2020-06-27 22:47:12 +02:00
class Test extends MailAPI {
2022-06-20 19:52:31 +02:00
public function __construct(Context $context, bool $externalCall = false) {
parent::__construct($context, $externalCall, array(
2022-02-20 16:53:26 +01:00
"receiver" => new Parameter("receiver", Parameter::TYPE_EMAIL),
"gpgFingerprint" => new StringType("gpgFingerprint", 64, true, null)
2020-06-27 22:47:12 +02:00
));
}
2022-02-21 13:01:03 +01:00
public function _execute(): bool {
2020-06-27 22:47:12 +02:00
$receiver = $this->getParam("receiver");
2022-11-18 18:06:46 +01:00
$req = new \Core\API\Mail\Send($this->context);
2020-06-27 22:47:12 +02:00
$this->success = $req->execute(array(
"to" => $receiver,
"subject" => "Test E-Mail",
2022-02-20 16:53:26 +01:00
"body" => "Hey! If you receive this e-mail, your mail configuration seems to be working.",
"gpgFingerprint" => $this->getParam("gpgFingerprint"),
2022-10-23 21:26:27 +02:00
"async" => false
2020-06-27 22:47:12 +02:00
));
$this->lastError = $req->getLastError();
return $this->success;
}
}
class Send extends MailAPI {
2022-06-20 19:52:31 +02:00
public function __construct(Context $context, $externalCall = false) {
parent::__construct($context, $externalCall, array(
2021-04-09 16:05:36 +02:00
'to' => new Parameter('to', Parameter::TYPE_EMAIL, true, null),
'subject' => new StringType('subject', -1),
2020-06-27 22:47:12 +02:00
'body' => new StringType('body', -1),
2021-04-09 16:05:36 +02:00
'replyTo' => new Parameter('replyTo', Parameter::TYPE_EMAIL, true, null),
2022-02-20 16:53:26 +01:00
'replyName' => new StringType('replyName', 32, true, ""),
2022-06-20 19:52:31 +02:00
'gpgFingerprint' => new StringType("gpgFingerprint", 64, true, null),
2022-11-19 01:15:34 +01:00
'async' => new Parameter("async", Parameter::TYPE_BOOLEAN, true, null)
2020-06-27 22:47:12 +02:00
));
$this->isPublic = false;
}
2022-02-21 13:01:03 +01:00
public function _execute(): bool {
2020-06-27 22:47:12 +02:00
$mailConfig = $this->getMailConfig();
2022-06-01 09:47:31 +02:00
if (!$this->success || $mailConfig === null) {
2020-06-27 22:47:12 +02:00
return false;
}
2021-04-09 16:05:36 +02:00
$fromMail = $mailConfig->getProperty('from');
2022-02-20 16:53:26 +01:00
$mailFooter = $mailConfig->getProperty('mail_footer');
2021-04-09 16:05:36 +02:00
$toMail = $this->getParam('to') ?? $fromMail;
$subject = $this->getParam('subject');
$replyTo = $this->getParam('replyTo');
$replyName = $this->getParam('replyName');
2021-12-08 16:53:43 +01:00
$body = $this->getParam('body');
2022-02-20 16:53:26 +01:00
$gpgFingerprint = $this->getParam("gpgFingerprint");
2022-11-19 01:15:34 +01:00
$mailAsync = $this->getParam("async");
if ($mailAsync === null) {
// not set? grab from settings
$mailAsync = $mailConfig->getProperty("mail_async", false);
}
if ($mailAsync) {
2022-06-20 19:52:31 +02:00
$sql = $this->context->getSQL();
2022-02-20 16:53:26 +01:00
$this->success = $sql->insert("MailQueue", ["from", "to", "subject", "body",
"replyTo", "replyName", "gpgFingerprint"])
->addRow($fromMail, $toMail, $subject, $body, $replyTo, $replyName, $gpgFingerprint)
->execute() !== false;
$this->lastError = $sql->getLastError();
return $this->success;
}
2021-12-08 16:53:43 +01:00
if (stripos($body, "<body") === false) {
$body = "<body>$body</body>";
}
if (stripos($body, "<html") === false) {
$body = "<html>$body</html>";
}
2021-04-09 16:05:36 +02:00
2022-02-20 16:53:26 +01:00
if (!empty($mailFooter)) {
$email_signature = realpath(WEBROOT . DIRECTORY_SEPARATOR . $mailFooter);
if (is_file($email_signature)) {
$email_signature = file_get_contents($email_signature);
$body .= $email_signature;
}
}
2020-06-27 22:47:12 +02:00
try {
$mail = new PHPMailer;
$mail->IsSMTP();
2021-04-09 16:05:36 +02:00
$mail->setFrom($fromMail);
$mail->addAddress($toMail);
if ($replyTo) {
$mail->addReplyTo($replyTo, $replyName);
}
$mail->Subject = $subject;
2020-06-27 22:47:12 +02:00
$mail->SMTPDebug = 0;
$mail->Host = $mailConfig->getHost();
$mail->Port = $mailConfig->getPort();
$mail->SMTPAuth = true;
2022-02-20 16:53:26 +01:00
$mail->Timeout = 15;
2020-06-27 22:47:12 +02:00
$mail->Username = $mailConfig->getLogin();
$mail->Password = $mailConfig->getPassword();
$mail->SMTPSecure = 'tls';
$mail->CharSet = 'UTF-8';
2022-02-20 16:53:26 +01:00
if ($gpgFingerprint) {
$encryptedHeaders = implode("\r\n", [
"Date: " . (new \DateTime())->format(DateTimeInterface::RFC2822),
"Content-Type: text/html",
"Content-Transfer-Encoding: quoted-printable"
]);
$mimeBody = $encryptedHeaders . "\r\n\r\n" . quoted_printable_encode($body);
$res = GpgKey::encrypt($mimeBody, $gpgFingerprint);
if ($res["success"]) {
$encryptedBody = $res["data"];
$mail->AltBody = '';
$mail->Body = '';
$mail->AllowEmpty = true;
$mail->ContentType = PHPMailer::CONTENT_TYPE_MULTIPART_ENCRYPTED;
$mail->addStringAttachment("Version: 1", null, PHPMailer::ENCODING_BASE64, "application/pgp-encrypted", "");
$mail->addStringAttachment($encryptedBody, "encrypted.asc", PHPMailer::ENCODING_7BIT, "application/octet-stream", "");
} else {
return $this->createError($res["error"]);
}
} else {
$mail->msgHTML($body);
$mail->AltBody = strip_tags($body);
}
2020-06-27 22:47:12 +02:00
$this->success = @$mail->Send();
if (!$this->success) {
$this->lastError = "Error sending Mail: $mail->ErrorInfo";
2022-06-17 20:53:35 +02:00
$this->logger->error("sendMail() failed: $mail->ErrorInfo");
2021-04-09 16:05:36 +02:00
} else {
$this->result["messageId"] = $mail->getLastMessageID();
2020-06-27 22:47:12 +02:00
}
} catch (Exception $e) {
$this->success = false;
$this->lastError = "Error sending Mail: $e";
2022-06-17 20:53:35 +02:00
$this->logger->error($this->lastError);
2020-06-27 22:47:12 +02:00
}
return $this->success;
}
}
2021-04-09 16:05:36 +02:00
2022-02-20 16:53:26 +01:00
class SendQueue extends MailAPI {
2022-06-20 19:52:31 +02:00
public function __construct(Context $context, bool $externalCall = false) {
parent::__construct($context, $externalCall, [
2022-02-20 16:53:26 +01:00
"debug" => new Parameter("debug", Parameter::TYPE_BOOLEAN, true, false)
]);
$this->isPublic = false;
}
2022-02-21 13:01:03 +01:00
public function _execute(): bool {
2022-02-20 16:53:26 +01:00
$debug = $this->getParam("debug");
$startTime = time();
if ($debug) {
echo "Start of processing mail queue at $startTime" . PHP_EOL;
}
2022-06-20 19:52:31 +02:00
$sql = $this->context->getSQL();
$res = $sql->select("id", "from", "to", "subject", "body",
2022-02-20 16:53:26 +01:00
"replyTo", "replyName", "gpgFingerprint", "retryCount")
->from("MailQueue")
->where(new Compare("retryCount", 0, ">"))
->where(new Compare("status", "waiting"))
->where(new Compare("nextTry", $sql->now(), "<="))
->execute();
$this->success = ($res !== false);
$this->lastError = $sql->getLastError();
if ($this->success && is_array($res)) {
if ($debug) {
echo "Found " . count($res) . " mails to send" . PHP_EOL;
}
$successfulMails = [];
foreach ($res as $row) {
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;
}
2022-06-20 19:52:31 +02:00
$mailId = intval($row["id"]);
2022-02-20 16:53:26 +01:00
$retryCount = intval($row["retryCount"]);
2022-06-20 19:52:31 +02:00
$req = new Send($this->context);
2022-02-20 16:53:26 +01:00
$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)
2022-06-20 19:52:31 +02:00
->where(new Compare("id", $mailId))
2022-02-20 16:53:26 +01:00
->execute();
} else {
$successfulMails[] = $mailId;
}
}
$this->success = count($successfulMails) === count($res);
if (!empty($successfulMails)) {
$res = $sql->update("MailQueue")
->set("status", "success")
2022-06-20 19:52:31 +02:00
->where(new CondIn(new Column("id"), $successfulMails))
2022-02-20 16:53:26 +01:00
->execute();
$this->success = $res !== false;
$this->lastError = $sql->getLastError();
}
}
return $this->success;
}
}
2020-06-27 22:47:12 +02:00
}