death-notifier/src/main/php/com/fwdekker/deathnotifier/mailer/Mailer.php

150 lines
5.0 KiB
PHP

<?php
namespace com\fwdekker\deathnotifier\mailer;
use com\fwdekker\deathnotifier\Response;
use Exception;
use Monolog\Logger;
use PDO;
use PHPMailer\PHPMailer\Exception as MailerException;
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
/**
* Queues up mails and sends them when appropriate.
*/
class Mailer
{
/**
* @var Logger The logger to use for logging.
*/
private Logger $logger;
/**
* @var array<string, array<string, mixed>> The configuration to use for mailing.
*/
private array $config;
/**
* @var PDO The database connection to interact with.
*/
private PDO $conn;
/**
* Constructs a new mailer.
*
* @param array<string, array<string, mixed>> $config the configuration to use for mailing
* @param Logger $logger the logger to use for logging
* @param PDO $conn the connection to the email database
*/
public function __construct(array $config, Logger $logger, PDO $conn)
{
$this->logger = $logger;
$this->config = $config;
$this->conn = $conn;
}
/**
* Populates the database with the necessary structures for emails.
*
* @return void
*/
public function install(): void
{
$this->conn->exec("CREATE TABLE email_tasks(type TEXT NOT NULL,
recipient TEXT NOT NULL,
arg1 TEXT NOT NULL DEFAULT(''),
arg2 TEXT NOT NULL DEFAULT(''),
PRIMARY KEY (type, recipient, arg1, arg2));");
}
/**
* Queues an email to be sent.
*
* @param Email $email the email to queue
* @return Response a satisfied `Response` if the email was queued, or an unsatisfied `Response` otherwise
*/
public function queue_email(Email $email): Response
{
$stmt = $this->conn->prepare("INSERT OR IGNORE INTO email_tasks (type, recipient, arg1, arg2)
VALUES (:type, :recipient, :arg1, :arg2);");
$stmt->bindValue(":type", $email->type);
$stmt->bindValue(":recipient", $email->recipient);
$stmt->bindValue(":arg1", $email->arg1);
$stmt->bindValue(":arg2", $email->arg2);
return $stmt->execute() ? Response::satisfied() : Response::unsatisfied(null);
}
/**
* Sends all emails in the queue.
*
* @return void
*/
public function process_queue(): void
{
// Open mailer
$mailer = new PHPMailer();
$mailer->IsSMTP();
$mailer->CharSet = "UTF-8";
$mailer->SMTPAuth = true;
$mailer->SMTPDebug = SMTP::DEBUG_OFF;
$mailer->SMTPKeepAlive = true;
$mailer->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
$mailer->Host = $this->config["mail"]["host"];
$mailer->Port = $this->config["mail"]["port"];
$mailer->Username = $this->config["mail"]["username"];
$mailer->Password = $this->config["mail"]["password"];
try {
$mailer->setFrom($this->config["mail"]["username"], $this->config["mail"]["from_name"]);
} catch (MailerException $exception) {
$this->logger->error("Failed to set 'from' address while processing queue.", ["cause" => $exception]);
$mailer->smtpClose();
}
// Get queue
$stmt = $this->conn->prepare("SELECT type, recipient, arg1, arg2 FROM email_tasks;");
$stmt->execute();
$email_tasks = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Process queue
$stmt = $this->conn->prepare("DELETE FROM email_tasks
WHERE type=:type AND recipient=:recipient AND arg1=:arg1 AND arg2=:arg2;");
$stmt->bindParam(":type", $type);
$stmt->bindParam(":recipient", $recipient);
$stmt->bindParam(":arg1", $arg1);
$stmt->bindParam(":arg2", $arg2);
foreach ($email_tasks as ["type" => $type, "recipient" => $recipient, "arg1" => $arg1, "arg2" => $arg2]) {
try {
$email = Email::deserialize($type, $recipient, $arg1, $arg2);
$mailer->Subject = $email->getSubject();
$mailer->Body = $email->getBody($this->config);
try {
$mailer->addAddress($recipient);
$mailer->send();
} catch (MailerException $exception) {
$this->logger->error(
"Failed to send mail.",
["cause" => $exception, "email" => $email]
);
$mailer->getSMTPInstance()->reset();
}
$mailer->clearAddresses();
} catch (Exception $exception) {
$this->logger->error(
"Failed to send mail.",
["cause" => $exception]
);
$mailer->getSMTPInstance()->reset();
}
$stmt->execute();
}
}
}