270 lines
9.9 KiB
PHP
270 lines
9.9 KiB
PHP
<?php
|
|
|
|
namespace php;
|
|
|
|
use Monolog\Logger;
|
|
use PDO;
|
|
use PHPMailer\PHPMailer\Exception;
|
|
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 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 Logger $logger the logger to use for logging
|
|
* @param array $config the configuration to use for mailing
|
|
*/
|
|
public function __construct(Logger $logger, array $config, 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,
|
|
arg1 text default null,
|
|
arg2 text default null,
|
|
PRIMARY KEY (type, arg1, arg2));");
|
|
}
|
|
|
|
|
|
/**
|
|
* Queues an email to be sent for a newly registered user.
|
|
*
|
|
* @param string $email the email address to send the email to
|
|
* @param string $token the token the user can verify their email address with
|
|
* @return Response an empty satisfied response
|
|
*/
|
|
public function queue_registration(string $email, string $token): Response
|
|
{
|
|
$stmt = $this->conn->prepare("INSERT OR IGNORE INTO email_tasks (type, arg1, arg2)
|
|
VALUES ('register', :email, :token);");
|
|
$stmt->bindValue(":email", $email);
|
|
$stmt->bindValue(":token", $token);
|
|
$stmt->execute();
|
|
|
|
return new Response(payload: null, satisfied: true);
|
|
}
|
|
|
|
/**
|
|
* Creates the email subject and body to be sent to a newly registered user.
|
|
*
|
|
* @param string $email the email address of the newly registered user
|
|
* @param string $token the email address verification token of the newly registered user
|
|
* @return string[] the subject and body of the email
|
|
*/
|
|
private function create_register_email(string $email, string $token): array
|
|
{
|
|
$verify_path = $this->config["server"]["base_path"] .
|
|
"?action=verify-email" .
|
|
"&email=" . rawurlencode($email) .
|
|
"&token=$token";
|
|
|
|
// TODO: Nicer message!
|
|
// TODO: What if user did not create account themselves?
|
|
return [
|
|
"Created account for Death Notifier",
|
|
"An account has been created for $email for Death Notifier. " .
|
|
"Until you verify your email address, you will not receive any notifications. " .
|
|
"You can verify your email address by clicking the link below." .
|
|
"\n\n" .
|
|
"Verify: $verify_path"
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Queues an email to be sent to a user who should verify their email address.
|
|
*
|
|
* @param string $email the email address to send the email to
|
|
* @param string $token the token the user can verify their email address with
|
|
* @return Response an empty satisfied response
|
|
*/
|
|
public function queue_verification(string $email, string $token): Response
|
|
{
|
|
$stmt = $this->conn->prepare("INSERT OR IGNORE INTO email_tasks (type, arg1, arg2)
|
|
VALUES ('verify', :email, :token);");
|
|
$stmt->bindValue(":email", $email);
|
|
$stmt->bindValue(":token", $token);
|
|
$stmt->execute();
|
|
|
|
return new Response(payload: null, satisfied: true);
|
|
}
|
|
|
|
/**
|
|
* Creates the email subject and body to be sent to a user who should verify their email address.
|
|
*
|
|
* @param string $email the email address of the user
|
|
* @param string $token the email address verification token of the user
|
|
* @return string[] the subject and body of the email
|
|
*/
|
|
private function create_verify_email(string $email, string $token): array
|
|
{
|
|
$base_path = $this->config["server"]["base_path"];
|
|
$verify_path = "$base_path?action=verify-email&email=" . rawurlencode($email) . "&token=$token";
|
|
|
|
// TODO: What if user did not change email address?
|
|
// TODO: Separate "verify after changing email" and "resending verify email"
|
|
return [
|
|
"Verify your email address",
|
|
"Your email address for Death Notifier has not been verified yet. " .
|
|
"Until you verify your email address, you will not receive any notifications. " .
|
|
"You can verify your email address by clicking the link below." .
|
|
"\n\n" .
|
|
"Verify: $verify_path"
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Queues an email to be sent to a user to notify them of a tracked person's death.
|
|
*
|
|
* @param string $email the email address to send the death notification to
|
|
* @param string $name the name of the person who probably passed away
|
|
* @return void
|
|
*/
|
|
public function queue_death_notification(string $email, string $name): void
|
|
{
|
|
$stmt = $this->conn->prepare("INSERT OR IGNORE INTO email_tasks (type, arg1, arg2)
|
|
VALUES ('notify-death', :email, :name);");
|
|
$stmt->bindValue(":email", $email);
|
|
$stmt->bindValue(":name", $name);
|
|
$stmt->execute();
|
|
}
|
|
|
|
/**
|
|
* Creates the email subject and body to be sent to notify a user of a tracked person's death.
|
|
*
|
|
* @param string $name the name of the person who has likely passed away
|
|
* @return string[] the subject and body of the email
|
|
*/
|
|
private function create_death_notification_email(string $name): array
|
|
{
|
|
// TODO: Add unsubscribe link
|
|
return [
|
|
"$name may have passed away",
|
|
"Someone has edited the Wikipedia page of $name to state that they have passed away. " .
|
|
"For more information, read their Wikipedia page at " .
|
|
"https://en.wikipedia.org/wiki/" . rawurlencode($name)
|
|
];
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 (Exception $exception) {
|
|
$this->logger->error("Failed to set 'from' address while processing queue.", ["cause" => $exception]);
|
|
$mailer->smtpClose();
|
|
}
|
|
|
|
// Get queue
|
|
$stmt = $this->conn->prepare("SELECT type, 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 arg1=:arg1 AND arg2=:arg2;");
|
|
$stmt->bindParam(":type", $type);
|
|
$stmt->bindParam(":arg1", $arg1);
|
|
$stmt->bindParam(":arg2", $arg2);
|
|
foreach ($email_tasks as ["type" => $type, "arg1" => $arg1, "arg2" => $arg2]) {
|
|
// TODO: Reduce duplication between branches
|
|
switch ($type) {
|
|
case "register":
|
|
[$mailer->Subject, $mailer->Body] = $this->create_register_email($arg1, $arg2);
|
|
|
|
try {
|
|
$mailer->addAddress($arg1);
|
|
$mailer->send();
|
|
$stmt->execute();
|
|
} catch (Exception $exception) {
|
|
$this->logger->error(
|
|
"Failed to send register mail.",
|
|
["cause" => $exception, "recipient" => $arg1]
|
|
);
|
|
$mailer->getSMTPInstance()->reset();
|
|
}
|
|
break;
|
|
case "verify":
|
|
[$mailer->Subject, $mailer->Body] = $this->create_verify_email($arg1, $arg2);
|
|
|
|
try {
|
|
// TODO: Also send to old email address (by storing old email in `users` table?)
|
|
$mailer->addAddress($arg1);
|
|
$mailer->send();
|
|
$stmt->execute();
|
|
} catch (Exception $exception) {
|
|
$this->logger->error(
|
|
"Failed to send verify mail.",
|
|
["cause" => $exception, "recipient" => $arg1]
|
|
);
|
|
$mailer->getSMTPInstance()->reset();
|
|
}
|
|
break;
|
|
case "notify-death":
|
|
// TODO: Set name somehow
|
|
[$mailer->Subject, $mailer->Body] = $this->create_death_notification_email($arg2);
|
|
|
|
try {
|
|
$mailer->addAddress($arg1);
|
|
$mailer->send();
|
|
$stmt->execute();
|
|
} catch (Exception $exception) {
|
|
$this->logger->error(
|
|
"Failed to send death notification mail.",
|
|
["cause" => $exception, "recipient" => $arg1]
|
|
);
|
|
$mailer->getSMTPInstance()->reset();
|
|
}
|
|
}
|
|
$mailer->clearAddresses();
|
|
}
|
|
}
|
|
}
|