Revamp email system, config, and db exceptions

This commit is contained in:
Florine W. Dekker 2022-12-02 23:05:25 +01:00
parent 19922907c8
commit 5898c95709
Signed by: FWDekker
GPG Key ID: D3DCFAA8A4560BE0
22 changed files with 243 additions and 272 deletions

View File

@ -1,7 +1,7 @@
{
"name": "fwdekker/death-notifier",
"description": "Get notified when a famous person dies.",
"version": "0.15.6", "_comment_version": "Also update version in `package.json`!",
"version": "0.16.0", "_comment_version": "Also update version in `package.json`!",
"type": "project",
"license": "MIT",
"homepage": "https://git.fwdekker.com/tools/death-notifier",

BIN
composer.lock generated

Binary file not shown.

BIN
package-lock.json generated

Binary file not shown.

View File

@ -1,6 +1,6 @@
{
"name": "death-notifier",
"version": "0.15.6", "_comment_version": "Also update version in `composer.json`!",
"version": "0.16.0", "_comment_version": "Also update version in `composer.json`!",
"description": "Get notified when a famous person dies.",
"author": "Florine W. Dekker",
"browser": "dist/bundle.js",

View File

@ -2,13 +2,13 @@
use com\fwdekker\deathnotifier\ActionDispatcher;
use com\fwdekker\deathnotifier\ActionMethod;
use com\fwdekker\deathnotifier\Config;
use com\fwdekker\deathnotifier\Database;
use com\fwdekker\deathnotifier\EmulateCronAction;
use com\fwdekker\deathnotifier\LoggerUtil;
use com\fwdekker\deathnotifier\mailer\MailManager;
use com\fwdekker\deathnotifier\mailer\ProcessEmailQueueAction;
use com\fwdekker\deathnotifier\mediawiki\MediaWiki;
use com\fwdekker\deathnotifier\ProtectedAction;
use com\fwdekker\deathnotifier\Response;
use com\fwdekker\deathnotifier\StartSessionAction;
use com\fwdekker\deathnotifier\tracking\AddTrackingAction;
@ -37,18 +37,15 @@ require_once __DIR__ . "/.vendor/autoload.php";
// Preamble
$config = Util::read_config();
LoggerUtil::configure($config["logger"]);
$logger = LoggerUtil::with_name();
$config = Config::get();
if (hash_equals($config["admin"]["cli_secret"], "REPLACE THIS WITH A SECRET VALUE")) {
$logger->error("You must set a CLI secret in the configuration file before running Death Notifier.");
exit(1);
}
$db = new Database($config["database"]["filename"]);
$mediawiki = new MediaWiki();
$mail_manager = new MailManager($db->conn);
$user_manager = new UserManager($db->conn, $mail_manager);
@ -88,8 +85,8 @@ try {
$dispatcher->register_action(new RemoveTrackingAction($tracking_manager));
// CLI actions
$cli_actions = [
new UpdateTrackingsAction($db->conn, $tracking_manager, $mediawiki, $mail_manager, $config["admin"]["cli_secret"]),
new ProcessEmailQueueAction($config, $mail_manager, $config["admin"]["cli_secret"]),
new UpdateTrackingsAction($db->conn, $tracking_manager, $mediawiki, $mail_manager),
new ProcessEmailQueueAction($mail_manager),
];
$dispatcher->register_action($cli_actions[0]);
$dispatcher->register_action($cli_actions[1]);
@ -114,5 +111,5 @@ header("Content-type:application/json;charset=utf-8");
exit(json_encode([
"payload" => $response->payload,
"satisfied" => $response->satisfied,
"token" => $_SESSION["token"]
"token" => $_SESSION["token"],
]));

View File

@ -0,0 +1,55 @@
<?php
namespace com\fwdekker\deathnotifier;
use InvalidArgumentException;
/**
* The application's configuration, read lazily from configuration files.
*
* Contains global state, but that's fine since it's read-only.
*/
class Config
{
/**
* @var array<string, mixed>|null the application's configuration, or `null` if it has not been read yet
*/
private static ?array $config = null;
/**
* Returns the application's configuration.
*
* @return array<string, mixed> the application's configuration
*/
public static function get(): array
{
if (self::$config === null)
self::$config = self::read_config();
return self::$config;
}
/**
* Reads the configuration file and overrides it with the user's custom values.
*
* @return array<string, mixed> the application's configuration
*/
private static function read_config(): array
{
// TODO: Check permissions, return `null` if too permissive
$config = parse_ini_file("config.default.ini.php", process_sections: true, scanner_mode: INI_SCANNER_TYPED);
if ($config === false) throw new InvalidArgumentException("Invalid `config.default.ini.php` file.");
if (file_exists("config.ini.php")) {
$config_custom = parse_ini_file("config.ini.php", process_sections: true, scanner_mode: INI_SCANNER_TYPED);
if ($config_custom === false) throw new InvalidArgumentException("Invalid `config.ini.php` file.");
$config = array_replace_recursive($config, $config_custom);
}
return $config;
}
}

View File

@ -6,6 +6,8 @@ use com\fwdekker\deathnotifier\mailer\MailManager;
use com\fwdekker\deathnotifier\tracking\TrackingManager;
use com\fwdekker\deathnotifier\user\UserManager;
use Composer\Semver\Comparator;
use Error;
use Exception;
use Monolog\Logger;
use PDO;
@ -47,19 +49,20 @@ class Database
/**
* Installs all necessary data structures to get the database working.
*
* @param MailManager $mailer the mailer to install
* @param UserManager $userManager the use manager to install
* @param TrackingManager $trackingManager the tracking manager to install
* @param MailManager $mail_manager the mail manager to install
* @param UserManager $user_manager the user manager to install
* @param TrackingManager $tracking_manager the tracking manager to install
* @return void
*/
public function auto_install(MailManager $mailer, UserManager $userManager, TrackingManager $trackingManager): void
public function auto_install(MailManager $mail_manager, UserManager $user_manager,
TrackingManager $tracking_manager): void
{
// TODO: Keep track of statistics, such as #users, mails sent, etc.
self::transaction($this->conn, function () use ($mailer, $userManager, $trackingManager) {
self::transaction($this->conn, function () use ($mail_manager, $user_manager, $tracking_manager) {
// Check if already installed
$stmt = $this->conn->prepare("SELECT count(*) FROM sqlite_master WHERE type = 'table';");
$stmt->execute();
if ($stmt->fetch()[0] !== 0) return Response::satisfied();
if ($stmt->fetch()[0] !== 0)
return;
$this->logger->info("Database does not exist. Installing new database.");
@ -70,12 +73,11 @@ class Database
$stmt->execute();
// Create other tables
$mailer->install();
$userManager->install();
$trackingManager->install();
$mail_manager->install();
$user_manager->install();
$tracking_manager->install();
$this->logger->info("Installation complete.");
return Response::satisfied();
});
}
@ -92,7 +94,7 @@ class Database
$stmt->execute();
$db_version = $stmt->fetchAll(PDO::FETCH_ASSOC)[0]["v"];
if (Comparator::greaterThanOrEqualTo($db_version, Database::LATEST_VERSION))
return Response::satisfied();
return;
$this->logger->info("Current db is v$db_version. Will migrate to v" . Database::LATEST_VERSION . ".");
@ -105,6 +107,7 @@ class Database
if (Comparator::lessThan($db_version, "0.5.0")) self::migrate_0_5_0();
if (Comparator::lessThan($db_version, "0.8.0")) self::migrate_0_8_0();
if (Comparator::lessThan($db_version, "0.10.0")) self::migrate_0_10_0();
if (Comparator::lessThan($db_version, "0.16.0")) self::migrate_0_16_0();
// Update version
$stmt = $this->conn->prepare("UPDATE meta SET v=:version WHERE k='version';");
@ -112,11 +115,10 @@ class Database
$stmt->execute();
$this->logger->info("Completed migration to v" . Database::LATEST_VERSION . ".");
return Response::satisfied();
});
}
/**
* Migrates the database from a previous version to one compatible with v0.5.0.
*
@ -126,14 +128,8 @@ class Database
{
$this->logger->info("Migrating to v0.5.0.");
$res = $this->conn->exec("ALTER TABLE users
ADD COLUMN email_notifications_enabled INT NOT NULL DEFAULT(1);") !== false;
if (!$res) {
$this->logger->error("Failed migrating to v0.5.0.", ["error" => $this->conn->errorInfo()]);
$this->conn->rollBack();
Util::http_exit(500);
}
$this->conn->exec("ALTER TABLE users
ADD COLUMN email_notifications_enabled INT NOT NULL DEFAULT(1);");
}
/**
@ -146,22 +142,15 @@ class Database
{
$this->logger->info("Migrating to v0.8.0.");
$res =
$this->conn->exec("CREATE TABLE new_email_tasks(type TEXT NOT NULL,
recipient TEXT NOT NULL,
arg1 TEXT DEFAULT(NULL),
PRIMARY KEY (type, recipient, arg1));") !== false &&
$this->conn->exec("INSERT INTO new_email_tasks (type, recipient, arg1)
SELECT type, arg1, arg2
FROM email_tasks WHERE arg1 NOT NULL;") !== false &&
$this->conn->exec("DROP TABLE email_tasks;") !== false &&
$this->conn->exec("ALTER TABLE new_email_tasks RENAME TO email_tasks;") !== false;
if (!$res) {
$this->logger->error("Failed migrating to v0.8.0.", ["error" => $this->conn->errorInfo()]);
$this->conn->rollBack();
Util::http_exit(500);
}
$this->conn->exec("CREATE TABLE new_email_tasks(type TEXT NOT NULL,
recipient TEXT NOT NULL,
arg1 TEXT DEFAULT(NULL),
PRIMARY KEY (type, recipient, arg1));");
$this->conn->exec("INSERT INTO new_email_tasks (type, recipient, arg1)
SELECT type, arg1, arg2
FROM email_tasks WHERE arg1 NOT NULL;");
$this->conn->exec("DROP TABLE email_tasks;");
$this->conn->exec("ALTER TABLE new_email_tasks RENAME TO email_tasks;");
}
/**
@ -174,59 +163,67 @@ class Database
{
$this->logger->info("Migrating to v0.10.0.");
$res =
$this->conn->exec("CREATE TABLE new_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));") !== false &&
$this->conn->exec("INSERT INTO new_email_tasks (type, recipient, arg1)
SELECT type, recipient, arg1
FROM email_tasks;") !== false &&
$this->conn->exec("DROP TABLE email_tasks;") !== false &&
$this->conn->exec("ALTER TABLE new_email_tasks RENAME TO email_tasks;") !== false &&
$this->conn->exec("UPDATE email_tasks
SET type='notify-status-changed' AND arg2='dead'
WHERE type='notify-death'") !== false;
$this->conn->exec("CREATE TABLE new_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));");
$this->conn->exec("INSERT INTO new_email_tasks (type, recipient, arg1)
SELECT type, recipient, arg1
FROM email_tasks;");
$this->conn->exec("DROP TABLE email_tasks;");
$this->conn->exec("ALTER TABLE new_email_tasks RENAME TO email_tasks;");
$this->conn->exec("UPDATE email_tasks
SET type='notify-status-changed' AND arg2='dead'
WHERE type='notify-death'");
}
if (!$res) {
$this->logger->error("Failed migrating to v0.10.0.", ["error" => $this->conn->errorInfo()]);
$this->conn->rollBack();
Util::http_exit(500);
}
/**
* Migrates the database from a previous version to one compatible with v0.16.0.
*
* @return void
* @noinspection SqlResolve Function necessarily refers to old schema which is not detected by tools
*/
private function migrate_0_16_0(): void
{
$this->logger->info("Migrating to v0.16.0.");
$this->conn->exec("DROP TABLE email_tasks;");
$this->conn->exec("CREATE TABLE email_tasks(type_key TEXT NOT NULL,
recipient TEXT NOT NULL,
subject TEXT NOT NULL,
body TEXT NOT NULL,
PRIMARY KEY (type_key, recipient));");
}
/**
* Executes `lambda` within a single transaction, allowing nesting.
* Executes `lambda` within a transaction, allowing nesting.
*
* If no transaction has been started when this function is invoked, a new transaction is started. If `lambda`
* returns a satisfied `Response`, the transaction is committed, otherwise the transaction is rolled back.
*
* If a transaction has been started when this function is invoked, the transaction is continued, and `lambda` does
* not by itself commit or roll back the transaction, this function does not commit or roll back the transaction
* either.
* throws an exception, the transaction is rolled back, otherwise, if this method is not invoked while a transaction
* was already ongoing, the transaction is committed.
*
* @param PDO $conn the connection to perform the transaction over
* @param callable(): (Response|null) $lambda the function to execute within the transaction, returning a satisfied
* `Response` or `null` if the transaction should be committed, or an unsatisfied `Response` if the transaction
* should be rolled back
* @return Response the `Response` returned by `lambda`, or a satisfied `Response` if `lambda` returns `void` or
* `null`
* @param callable(): void $lambda the function to execute within a transaction
*/
public static function transaction(PDO $conn, callable $lambda): Response
public static function transaction(PDO $conn, callable $lambda): void
{
$initially_in_transaction = $conn->inTransaction();
if (!$initially_in_transaction)
$conn->beginTransaction();
$output = $lambda() ?? Response::satisfied();
try {
$lambda();
} catch (Error $exception) {
if ($conn->inTransaction())
$conn->rollBack();
throw $exception;
}
if ($conn->inTransaction() && !$initially_in_transaction)
if ($output->satisfied) $conn->commit();
else $conn->rollBack();
return $output;
$conn->commit();
}
}

View File

@ -16,27 +16,12 @@ use Monolog\Logger;
class LoggerUtil
{
/**
* @var Logger|null the main logger instance that other loggers are derived from
* @var Logger|null the main logger instance that other loggers are derived from, or `null` if the main logger has
* not been created yet
*/
private static ?Logger $main_logger = null;
/**
* Configures the main logger.
*
* @param array<string, mixed> $config the logger configuration
* @return void
*/
public static function configure(array $config): void
{
if (self::$main_logger !== null)
throw new IllegalStateException("Logger has already been created.");
self::$main_logger = new Logger("main");
self::$main_logger->pushHandler(new StreamHandler($config["file"], $config["level"]));
ErrorHandler::register(self::$main_logger);
}
/**
* Returns a logger with the given name.
*
@ -45,8 +30,13 @@ class LoggerUtil
*/
public static function with_name(string $name = "main"): Logger
{
if (self::$main_logger === null)
throw new IllegalStateException("Logger has not been created yet.");
if (self::$main_logger === null) {
$config = Config::get();
self::$main_logger = new Logger("main");
self::$main_logger->pushHandler(new StreamHandler($config["file"], $config["level"]));
ErrorHandler::register(self::$main_logger);
}
return self::$main_logger->withName($name);
}

View File

@ -27,27 +27,6 @@ class Util
}
/**
* Reads the configuration file and overrides it with the user's custom values.
*
* @return array<string, array<string, mixed>> the configuration
*/
static function read_config(): array
{
// TODO: Check permissions, return `null` if too permissive
$config = parse_ini_file("config.default.ini.php", process_sections: true, scanner_mode: INI_SCANNER_TYPED);
if ($config === false) throw new InvalidArgumentException("Invalid `config.default.ini.php` file.");
if (file_exists("config.ini.php")) {
$config_custom = parse_ini_file("config.ini.php", process_sections: true, scanner_mode: INI_SCANNER_TYPED);
if ($config_custom === false) throw new InvalidArgumentException("Invalid `config.ini.php` file.");
$config = array_replace_recursive($config, $config_custom);
}
return $config;
}
/**
* Parses POST values from JSON-based inputs.
*

View File

@ -11,32 +11,36 @@ use com\fwdekker\deathnotifier\user\ChangedPasswordEmail;
use com\fwdekker\deathnotifier\user\RegisterEmail;
use com\fwdekker\deathnotifier\user\ResetPasswordEmail;
use com\fwdekker\deathnotifier\user\VerifyEmailEmail;
use JsonSerializable;
/**
* A serializable email that can be queued in a database and can be sent.
*
* When serialized, an email is represented by the type of email and two arguments. When deserialized, an email can be
* constructed from those arguments, and the implementation returns a specific subject and message.
* An email that can be queued in a database and then sent.
*/
abstract class Email
{
/**
* @var string a string identifying the type of email
* @var string a string identifying the type of email and distinguishing it from similar instances of the same type
*/
public string $type;
public readonly string $type_key;
/**
* @var string the intended recipient of the email
*/
public string $recipient;
public readonly string $recipient;
/**
* @var string the first argument to construct the email
* Constructs a new email.
*
* @param string $type_key a string identifying the type of email and distinguishing it from similar instances of
* the same type
* @param string $recipient the intended recipient of the email
*/
public string $arg1 = "";
/**
* @var string the second argument to construct the email
*/
public string $arg2 = "";
public function __construct(string $type_key, string $recipient)
{
$this->type_key = $type_key;
$this->recipient = $recipient;
}
/**
@ -44,42 +48,12 @@ abstract class Email
*
* @return string the subject header of the email
*/
public abstract function getSubject(): string;
public abstract function get_subject(): string;
/**
* Returns the body of the email.
*
* @param array<string, array<string, mixed>> $config the software configuration
* @return string the body of the email
*/
public abstract function getBody(array $config): string;
/**
* Deserializes an email back into an instance.
*
* Only types of emails that are known can be deserialized.
*
* @param string $type the type of email to deserialize
* @param string $recipient the intended recipient of the email
* @param string $arg1 the first argument to construct the email
* @param string $arg2 the second argument to construct the email
* @return Email a deserialized email
*/
public static function deserialize(string $type, string $recipient, string $arg1, string $arg2): Email
{
// TODO: Dynamically instantiate class from class name
// TODO: Add serialize and deserialize functions, implemented by each subclass
return match ($type) {
RegisterEmail::TYPE => new RegisterEmail($recipient, $arg1),
VerifyEmailEmail::TYPE => new VerifyEmailEmail($recipient, $arg1),
ChangedEmailEmail::TYPE => new ChangedEmailEmail($recipient, $arg1),
ChangedPasswordEmail::TYPE => new ChangedPasswordEmail($recipient),
ResetPasswordEmail::TYPE => new ResetPasswordEmail($recipient, $arg1),
NotifyArticleDeletedEmail::TYPE => new NotifyArticleDeletedEmail($recipient, $arg1),
NotifyArticleUndeletedEmail::TYPE => new NotifyArticleUndeletedEmail($recipient, $arg1),
NotifyStatusChangedEmail::TYPE => new NotifyStatusChangedEmail($recipient, $arg1, $arg2),
default => throw new IllegalStateException("Unknown email type $type."),
};
}
public abstract function get_body(): string;
}

View File

@ -34,11 +34,11 @@ class MailManager
*/
public function install(): void
{
$this->conn->exec("CREATE TABLE email_tasks(type TEXT NOT NULL,
$this->conn->exec("CREATE TABLE email_tasks(type_key TEXT NOT NULL,
recipient TEXT NOT NULL,
arg1 TEXT NOT NULL DEFAULT(''),
arg2 TEXT NOT NULL DEFAULT(''),
PRIMARY KEY (type, recipient, arg1, arg2));");
subject TEXT NOT NULL,
body TEXT NOT NULL,
PRIMARY KEY (type_key, recipient));");
}
@ -50,53 +50,42 @@ class MailManager
*/
public function queue_email(Email $email): void
{
$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 = $this->conn->prepare("INSERT OR IGNORE INTO email_tasks (type_key, recipient, subject, body)
VALUES (:type_key, :recipient, :subject, :body);");
$stmt->bindValue(":type_key", $email->type_key);
$stmt->bindValue(":recipient", $email->recipient);
$stmt->bindValue(":arg1", $email->arg1);
$stmt->bindValue(":arg2", $email->arg2);
$stmt->bindValue(":subject", $email->get_subject());
$stmt->bindValue(":body", $email->get_body());
$stmt->execute();
}
/**
* Returns all queued emails.
*
* @return array<Email> the queued emails
* @return array<array{"type_key": string, "recipient": string, "subject": string, "body": string}> the queued
* emails
*/
public function get_queue(): array
{
$stmt = $this->conn->prepare("SELECT type, recipient, arg1, arg2 FROM email_tasks;");
$stmt = $this->conn->prepare("SELECT type_key, recipient, subject, body FROM email_tasks;");
$stmt->execute();
$emails = $stmt->fetchAll(PDO::FETCH_ASSOC);
return array_map(
fn($row) => Email::deserialize($row["type"], $row["recipient"], $row["arg1"], $row["arg2"]),
$emails
);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Removes mails from the queue.
*
* @param array<Email> $emails the emails to remove from the queue
* @param array<array{"type_key": string, "recipient": string}> $emails the emails to remove from the queue
* @return void
*/
public function unqueue_emails(array $emails): void
{
$stmt = $this->conn->prepare("DELETE FROM email_tasks
WHERE type=:type AND recipient=:recipient AND arg1=:arg1 AND arg2=:arg2;");
$stmt->bindParam(":type", $type);
WHERE type_key=:type_key AND recipient=:recipient;");
$stmt->bindParam(":type_key", $type_key);
$stmt->bindParam(":recipient", $recipient);
$stmt->bindParam(":arg1", $arg1);
$stmt->bindParam(":arg2", $arg2);
foreach ($emails as $email) {
$type = $email->type;
$recipient = $email->recipient;
$arg1 = $email->arg1;
$arg2 = $email->arg2;
foreach ($emails as ["type_key" => $type_key, "recipient" => $recipient])
$stmt->execute();
}
}
}

View File

@ -5,6 +5,7 @@ namespace com\fwdekker\deathnotifier\mailer;
use com\fwdekker\deathnotifier\Action;
use com\fwdekker\deathnotifier\ActionException;
use com\fwdekker\deathnotifier\ActionMethod;
use com\fwdekker\deathnotifier\Config;
use com\fwdekker\deathnotifier\LoggerUtil;
use com\fwdekker\deathnotifier\validator\IsEqualToRule;
use Monolog\Logger;
@ -22,10 +23,6 @@ class ProcessEmailQueueAction extends Action
* @var Logger the logger to log with
*/
private readonly Logger $logger;
/**
* @var array<string, mixed> the application's configuration
*/
private readonly array $config;
/**
* @var MailManager the manager to process the queue with
*/
@ -35,20 +32,19 @@ class ProcessEmailQueueAction extends Action
/**
* Constructs a new `ProcessEmailQueueAction`.
*
* @param array<string, mixed> $config the application's configuration
* @param MailManager $mail_manager the manager to process the queue with
* @param string $password the admin password required to perform this function
*/
public function __construct(array $config, MailManager $mail_manager, string $password)
public function __construct(MailManager $mail_manager)
{
parent::__construct(
ActionMethod::CLI,
"process-email-queue",
rule_lists: ["password" => [new IsEqualToRule($password, "Incorrect password.")]],
rule_lists: [
"password" => [new IsEqualToRule(Config::get()["admin"]["cli_secret"], "Incorrect password.")]
],
);
$this->logger = LoggerUtil::with_name($this::class);
$this->config = $config;
$this->mail_manager = $mail_manager;
}
@ -65,11 +61,11 @@ class ProcessEmailQueueAction extends Action
$mailer = $this->create_mailer();
foreach ($emails as $email) {
$mailer->Subject = $email->getSubject();
$mailer->Body = $email->getBody($this->config);
$mailer->Subject = $email["subject"];
$mailer->Body = $email["body"];
try {
$mailer->addAddress($email->recipient);
$mailer->addAddress($email["recipient"]);
$mailer->send();
} catch (PHPMailerException $exception) {
$mailer->getSMTPInstance()->reset();
@ -94,6 +90,8 @@ class ProcessEmailQueueAction extends Action
*/
private function create_mailer(): PHPMailer
{
$config = Config::get();
$mailer = new PHPMailer();
$mailer->IsSMTP();
$mailer->CharSet = "UTF-8";
@ -102,12 +100,12 @@ class ProcessEmailQueueAction extends Action
$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"];
$mailer->Host = $config["mail"]["host"];
$mailer->Port = $config["mail"]["port"];
$mailer->Username = $config["mail"]["username"];
$mailer->Password = $config["mail"]["password"];
try {
$mailer->setFrom($this->config["mail"]["username"], $this->config["mail"]["from_name"]);
$mailer->setFrom($config["mail"]["username"], $config["mail"]["from_name"]);
} catch (PHPMailerException $exception) {
$mailer->smtpClose();

View File

@ -2,6 +2,7 @@
namespace com\fwdekker\deathnotifier\tracking;
use com\fwdekker\deathnotifier\Config;
use com\fwdekker\deathnotifier\mailer\Email;
@ -29,22 +30,20 @@ class NotifyArticleDeletedEmail extends Email
*/
public function __construct(string $recipient, string $name)
{
$this->type = self::TYPE;
$this->recipient = $recipient;
parent::__construct($this::TYPE . "/" . $name, $recipient);
$this->name = $name;
$this->arg1 = $name;
}
public function getSubject(): string
public function get_subject(): string
{
return "$this->name article has been deleted";
}
public function getBody(array $config): string
public function get_body(): string
{
$base_path = $config["server"]["base_path"];
$base_path = Config::get()["server"]["base_path"];
return
"The Wikipedia article about $this->name has been deleted. " .

View File

@ -2,6 +2,7 @@
namespace com\fwdekker\deathnotifier\tracking;
use com\fwdekker\deathnotifier\Config;
use com\fwdekker\deathnotifier\mailer\Email;
@ -29,22 +30,20 @@ class NotifyArticleUndeletedEmail extends Email
*/
public function __construct(string $recipient, string $name)
{
$this->type = self::TYPE;
$this->recipient = $recipient;
parent::__construct($this::TYPE . "/" . $name, $recipient);
$this->name = $name;
$this->arg1 = $name;
}
public function getSubject(): string
public function get_subject(): string
{
return "$this->name article has been re-created";
}
public function getBody(array $config): string
public function get_body(): string
{
$base_path = $config["server"]["base_path"];
$base_path = Config::get()["server"]["base_path"];
return
"The Wikipedia article about $this->name has been re-created. " .

View File

@ -2,6 +2,7 @@
namespace com\fwdekker\deathnotifier\tracking;
use com\fwdekker\deathnotifier\Config;
use com\fwdekker\deathnotifier\mailer\Email;
@ -34,25 +35,21 @@ class NotifyStatusChangedEmail extends Email
*/
public function __construct(string $recipient, string $name, string $new_status)
{
$this->type = self::TYPE;
$this->recipient = $recipient;
parent::__construct($this::TYPE . "/" . $name, $recipient);
$this->name = $name;
$this->arg1 = $name;
$this->new_status = $new_status;
$this->arg2 = $new_status;
}
public function getSubject(): string
public function get_subject(): string
{
return "$this->name may be $this->new_status";
}
public function getBody(array $config): string
public function get_body(): string
{
$base_path = $config["server"]["base_path"];
$base_path = Config::get()["server"]["base_path"];
return
"Someone has edited Wikipedia to state that $this->name is $this->new_status. " .

View File

@ -264,8 +264,8 @@ class TrackingManager
*
* @param array<string, array{"type": ArticleType, "status": PersonStatus|null}> $statuses the current statuses of
* people
* @return array{"undeletions": string[], "status_changes": array<string, string>} the list of articles that were
* actually undeleted, and a mapping of articles that were actually changes to the new status
* @return array{string[], array<string, string>} the list of articles that were actually undeleted, and a mapping
* of articles that were actually changes to the new status
*/
public function update_statuses(array $statuses): array
{
@ -274,6 +274,7 @@ class TrackingManager
Database::transaction($this->conn, function () use ($statuses, &$undeletions, &$status_changes) {
// Query to mark person as no longer deleted, returning `name` to determine whether something changed
// TODO: Split this into two methods, one for `undelete`, one for `update_statuses`?
$undelete = $this->conn->prepare("UPDATE people
SET is_deleted=0
WHERE name=:name AND is_deleted<>0
@ -302,6 +303,6 @@ class TrackingManager
}
});
return ["undeletions" => $undeletions, "status_changes" => $status_changes];
return [$undeletions, $status_changes];
}
}

View File

@ -5,6 +5,7 @@ namespace com\fwdekker\deathnotifier\tracking;
use com\fwdekker\deathnotifier\Action;
use com\fwdekker\deathnotifier\ActionException;
use com\fwdekker\deathnotifier\ActionMethod;
use com\fwdekker\deathnotifier\Config;
use com\fwdekker\deathnotifier\Database;
use com\fwdekker\deathnotifier\LoggerUtil;
use com\fwdekker\deathnotifier\mailer\MailManager;
@ -49,15 +50,15 @@ class UpdateTrackingsAction extends Action
* @param TrackingManager $tracking_manager the manager through which trackings should be updated
* @param MediaWiki $mediawiki the instance to connect to Wikipedia with
* @param MailManager $mailer the mailer to send emails with
* @param string $password the admin password required to perform this function
*/
public function __construct(PDO $conn, TrackingManager $tracking_manager, MediaWiki $mediawiki, MailManager $mailer,
string $password)
public function __construct(PDO $conn, TrackingManager $tracking_manager, MediaWiki $mediawiki, MailManager $mailer)
{
parent::__construct(
ActionMethod::CLI,
"update-trackings",
rule_lists: ["password" => [new IsEqualToRule($password, "Incorrect password.")]],
rule_lists: [
"password" => [new IsEqualToRule(Config::get()["admin"]["cli_secret"], "Incorrect password.")]
],
);
$this->logger = LoggerUtil::with_name($this::class);
@ -101,8 +102,7 @@ class UpdateTrackingsAction extends Action
) {
$this->tracking_manager->rename_persons($people_statuses->redirects);
$actual_deletions = $this->tracking_manager->delete_persons($people_statuses->missing);
["undeletions" => $undeletions, "status_changes" => $status_changes] =
$this->tracking_manager->update_statuses($people_statuses->results);
[$undeletions, $status_changes] = $this->tracking_manager->update_statuses($people_statuses->results);
}
);

View File

@ -2,6 +2,7 @@
namespace com\fwdekker\deathnotifier\user;
use com\fwdekker\deathnotifier\Config;
use com\fwdekker\deathnotifier\mailer\Email;
@ -29,22 +30,20 @@ class ChangedEmailEmail extends Email
*/
public function __construct(string $recipient, string $token)
{
$this->type = self::TYPE;
$this->recipient = $recipient;
parent::__construct($this::TYPE, $recipient);
$this->token = $token;
$this->arg1 = $token;
}
public function getSubject(): string
public function get_subject(): string
{
return "Verify your new email address";
}
public function getBody(array $config): string
public function get_body(): string
{
$base_path = $config["server"]["base_path"];
$base_path = Config::get()["server"]["base_path"];
$verify_path = "$base_path?action=verify-email&email=" . rawurlencode($this->recipient) . "&token=$this->token";
return

View File

@ -2,6 +2,7 @@
namespace com\fwdekker\deathnotifier\user;
use com\fwdekker\deathnotifier\Config;
use com\fwdekker\deathnotifier\mailer\Email;
@ -23,19 +24,18 @@ class ChangedPasswordEmail extends Email
*/
public function __construct(string $recipient)
{
$this->type = self::TYPE;
$this->recipient = $recipient;
parent::__construct($this::TYPE, $recipient);
}
public function getSubject(): string
public function get_subject(): string
{
return "Your password has been changed";
}
public function getBody(array $config): string
public function get_body(): string
{
$base_path = $config["server"]["base_path"];
$base_path = Config::get()["server"]["base_path"];
return
"You changed the password of your Death Notifier account." .

View File

@ -2,6 +2,7 @@
namespace com\fwdekker\deathnotifier\user;
use com\fwdekker\deathnotifier\Config;
use com\fwdekker\deathnotifier\mailer\Email;
@ -29,21 +30,19 @@ class RegisterEmail extends Email
*/
public function __construct(string $recipient, string $token)
{
$this->type = self::TYPE;
$this->recipient = $recipient;
parent::__construct($this::TYPE, $recipient);
$this->token = $token;
$this->arg1 = $token;
}
public function getSubject(): string
public function get_subject(): string
{
return "Welcome to Death Notifier";
}
public function getBody(array $config): string
public function get_body(): string
{
$base_path = $config["server"]["base_path"];
$base_path = Config::get()["server"]["base_path"];
$verify_path = "$base_path?action=verify-email&email=" . rawurlencode($this->recipient) . "&token=$this->token";
return

View File

@ -2,6 +2,7 @@
namespace com\fwdekker\deathnotifier\user;
use com\fwdekker\deathnotifier\Config;
use com\fwdekker\deathnotifier\mailer\Email;
@ -29,22 +30,20 @@ class ResetPasswordEmail extends Email
*/
public function __construct(string $recipient, string $token)
{
$this->type = self::TYPE;
$this->recipient = $recipient;
parent::__construct($this::TYPE, $recipient);
$this->token = $token;
$this->arg1 = $token;
}
public function getSubject(): string
public function get_subject(): string
{
return "Reset your password";
}
public function getBody(array $config): string
public function get_body(): string
{
$base_path = $config["server"]["base_path"];
$base_path = Config::get()["server"]["base_path"];
$verify_path =
"$base_path?action=reset-password&email=" . rawurlencode($this->recipient) . "&token=$this->token";

View File

@ -2,6 +2,7 @@
namespace com\fwdekker\deathnotifier\user;
use com\fwdekker\deathnotifier\Config;
use com\fwdekker\deathnotifier\mailer\Email;
@ -29,22 +30,20 @@ class VerifyEmailEmail extends Email
*/
public function __construct(string $recipient, string $token)
{
$this->type = self::TYPE;
$this->recipient = $recipient;
parent::__construct($this::TYPE, $recipient);
$this->token = $token;
$this->arg1 = $token;
}
public function getSubject(): string
public function get_subject(): string
{
return "Verify your email address";
}
public function getBody(array $config): string
public function get_body(): string
{
$base_path = $config["server"]["base_path"];
$base_path = Config::get()["server"]["base_path"];
$verify_path = "$base_path?action=verify-email&email=" . rawurlencode($this->recipient) . "&token=$this->token";
return