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"];
|
|
|
|
|
2024-04-11 20:41:03 +02:00
|
|
|
if (!isset($settings["mail_enabled"]) || !$settings["mail_enabled"]) {
|
2023-01-18 14:37:34 +01:00
|
|
|
$this->createError("Mailing is not configured on this server yet.");
|
2021-04-09 16:05:36 +02:00
|
|
|
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;
|
2023-01-16 21:47:23 +01:00
|
|
|
use Core\Objects\DatabaseEntity\Group;
|
2022-11-20 17:13:53 +01:00
|
|
|
use Core\Objects\DatabaseEntity\MailQueueItem;
|
2021-12-08 16:53:43 +01:00
|
|
|
use DateTimeInterface;
|
2022-11-18 18:06:46 +01:00
|
|
|
use Core\Driver\SQL\Condition\Compare;
|
|
|
|
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) {
|
2024-04-06 19:09:12 +02:00
|
|
|
parent::__construct($context, $externalCall, [
|
2022-02-20 16:53:26 +01:00
|
|
|
"receiver" => new Parameter("receiver", Parameter::TYPE_EMAIL),
|
|
|
|
"gpgFingerprint" => new StringType("gpgFingerprint", 64, true, null)
|
2024-04-06 19:09:12 +02:00
|
|
|
]);
|
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"),
|
2024-04-06 19:09:12 +02:00
|
|
|
"async" => false,
|
|
|
|
"debug" => true,
|
2020-06-27 22:47:12 +02:00
|
|
|
));
|
|
|
|
|
|
|
|
$this->lastError = $req->getLastError();
|
2024-04-06 19:09:12 +02:00
|
|
|
$this->result["output"] = $req->getResult()["output"];
|
2020-06-27 22:47:12 +02:00
|
|
|
return $this->success;
|
|
|
|
}
|
2023-01-16 21:47:23 +01:00
|
|
|
|
2024-04-23 12:14:28 +02:00
|
|
|
public static function getDescription(): string {
|
|
|
|
return "Allows users to send a test email to verify configuration";
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function getDefaultPermittedGroups(): array {
|
|
|
|
return [Group::ADMIN];
|
2023-01-16 21:47:23 +01:00
|
|
|
}
|
2020-06-27 22:47:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
class Send extends MailAPI {
|
2022-06-20 19:52:31 +02:00
|
|
|
public function __construct(Context $context, $externalCall = false) {
|
|
|
|
parent::__construct($context, $externalCall, array(
|
2022-11-20 17:13:53 +01:00
|
|
|
'to' => new Parameter('to', Parameter::TYPE_EMAIL),
|
2021-04-09 16:05:36 +02:00
|
|
|
'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),
|
2024-04-06 19:09:12 +02:00
|
|
|
'async' => new Parameter("async", Parameter::TYPE_BOOLEAN, true, null),
|
|
|
|
'debug' => new Parameter("debug", Parameter::TYPE_BOOLEAN, true, false)
|
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');
|
2022-11-20 17:13:53 +01:00
|
|
|
$toMail = $this->getParam('to');
|
2021-04-09 16:05:36 +02:00
|
|
|
$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");
|
2024-04-06 19:09:12 +02:00
|
|
|
$debug = $this->getParam("debug");
|
2022-02-20 16:53:26 +01:00
|
|
|
|
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-11-20 17:13:53 +01:00
|
|
|
$mailQueueItem = new MailQueueItem($fromMail, $toMail, $subject, $body, $replyTo, $replyName, $gpgFingerprint);
|
|
|
|
$this->success = $mailQueueItem->save($sql);
|
2022-02-20 16:53:26 +01:00
|
|
|
$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);
|
|
|
|
}
|
|
|
|
|
2024-04-06 19:09:12 +02:00
|
|
|
|
2021-04-09 16:05:36 +02:00
|
|
|
$mail->Subject = $subject;
|
2024-04-06 19:09:12 +02:00
|
|
|
$mail->SMTPDebug = $debug ? 2 : 0;
|
2020-06-27 22:47:12 +02:00
|
|
|
$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 {
|
2024-04-05 14:17:50 +02:00
|
|
|
$this->logger->error("Error encrypting with gpg: " . $res["error"]);
|
2022-02-20 16:53:26 +01:00
|
|
|
return $this->createError($res["error"]);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$mail->msgHTML($body);
|
|
|
|
$mail->AltBody = strip_tags($body);
|
|
|
|
}
|
2020-06-27 22:47:12 +02:00
|
|
|
|
2024-04-06 19:09:12 +02:00
|
|
|
ob_start();
|
2020-06-27 22:47:12 +02:00
|
|
|
$this->success = @$mail->Send();
|
2024-04-06 19:09:12 +02:00
|
|
|
$output = ob_get_contents();
|
|
|
|
ob_end_clean();
|
2020-06-27 22:47:12 +02:00
|
|
|
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");
|
2024-04-06 19:09:12 +02:00
|
|
|
if ($debug) {
|
|
|
|
$this->logger->debug($output);
|
|
|
|
$this->result["output"] = $output;
|
|
|
|
}
|
2021-04-09 16:05:36 +02:00
|
|
|
} else {
|
|
|
|
$this->result["messageId"] = $mail->getLastMessageID();
|
2024-04-06 19:09:12 +02:00
|
|
|
if ($debug) {
|
|
|
|
$this->result["output"] = $output;
|
|
|
|
}
|
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;
|
|
|
|
}
|
2024-04-23 12:14:28 +02:00
|
|
|
|
|
|
|
public static function getDescription(): string {
|
|
|
|
return "Sends an email or puts it into an asynchronous queue. This function is for internal use only.";
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function hasConfigurablePermissions(): bool {
|
|
|
|
return false;
|
|
|
|
}
|
2020-06-27 22:47:12 +02:00
|
|
|
}
|
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();
|
2022-11-20 17:13:53 +01:00
|
|
|
$mailQueueItems = MailQueueItem::findBy(MailQueueItem::createBuilder($sql, false)
|
2023-03-05 15:30:06 +01:00
|
|
|
->whereGt("retry_count", 0)
|
2022-11-26 23:57:28 +01:00
|
|
|
->whereEq("status", "waiting")
|
2023-03-05 15:30:06 +01:00
|
|
|
->where(new Compare("next_try", $sql->now(), "<=")));
|
2022-02-20 16:53:26 +01:00
|
|
|
|
2022-11-20 17:13:53 +01:00
|
|
|
$this->success = ($mailQueueItems !== false);
|
2022-02-20 16:53:26 +01:00
|
|
|
$this->lastError = $sql->getLastError();
|
|
|
|
|
2022-11-20 17:13:53 +01:00
|
|
|
if ($this->success && is_array($mailQueueItems)) {
|
2022-02-20 16:53:26 +01:00
|
|
|
if ($debug) {
|
2022-11-20 17:13:53 +01:00
|
|
|
echo "Found " . count($mailQueueItems) . " mails to send" . PHP_EOL;
|
2024-04-05 14:17:50 +02:00
|
|
|
$this->logger->debug("Found " . count($mailQueueItems) . " mails to send");
|
2022-02-20 16:53:26 +01:00
|
|
|
}
|
|
|
|
|
2022-11-20 17:13:53 +01:00
|
|
|
$successfulMails = 0;
|
|
|
|
foreach ($mailQueueItems as $mailQueueItem) {
|
2022-02-20 16:53:26 +01:00
|
|
|
|
|
|
|
if (time() - $startTime >= 45) {
|
|
|
|
$this->lastError = "Not able to process whole mail queue within 45 seconds, will continue on next time";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($debug) {
|
2022-11-20 17:13:53 +01:00
|
|
|
echo "Sending subject=$mailQueueItem->subject to=$mailQueueItem->to" . PHP_EOL;
|
2024-04-05 14:17:50 +02:00
|
|
|
$this->logger->debug("Sending subject=$mailQueueItem->subject to=$mailQueueItem->to");
|
2022-02-20 16:53:26 +01:00
|
|
|
}
|
|
|
|
|
2022-11-20 17:13:53 +01:00
|
|
|
if ($mailQueueItem->send($this->context)) {
|
|
|
|
$successfulMails++;
|
2022-02-20 16:53:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-20 17:13:53 +01:00
|
|
|
$this->success = $successfulMails === count($mailQueueItems);
|
2024-04-05 14:17:50 +02:00
|
|
|
if ($successfulMails > 0) {
|
|
|
|
$this->logger->debug("Sent $successfulMails emails successfully");
|
|
|
|
}
|
2022-02-20 16:53:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return $this->success;
|
|
|
|
}
|
2024-04-23 12:14:28 +02:00
|
|
|
|
|
|
|
public static function getDescription(): string {
|
|
|
|
return "Processes the asynchronous mailing queue. This function is for internal use only.";
|
|
|
|
}
|
|
|
|
|
|
|
|
public static function hasConfigurablePermissions(): bool {
|
|
|
|
return false;
|
|
|
|
}
|
2022-02-20 16:53:26 +01:00
|
|
|
}
|
2020-06-27 22:47:12 +02:00
|
|
|
}
|