Compare commits
6 Commits
Author | SHA1 | Date |
---|---|---|
Florine W. Dekker | d7c2658ba6 | |
Florine W. Dekker | 2daa74b319 | |
Florine W. Dekker | 6a1be5ac2f | |
Florine W. Dekker | 7b86673590 | |
Florine W. Dekker | e024bc4cf8 | |
Florine W. Dekker | 80835d6a08 |
|
@ -1,7 +1,7 @@
|
|||
const path = require("path");
|
||||
|
||||
module.exports = grunt => {
|
||||
const testTarget = grunt.option("test-target") || ".";
|
||||
const testFilter = grunt.option("test-filter") || "'' .";
|
||||
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON("package.json"),
|
||||
|
@ -71,7 +71,7 @@ module.exports = grunt => {
|
|||
exec: "composer.phar install --no-dev"
|
||||
},
|
||||
phpunit: {
|
||||
exec: `cd dist/ && chmod +x .vendor/bin/phpunit && .vendor/bin/phpunit --testdox ${testTarget}`
|
||||
exec: `cd dist/ && chmod +x .vendor/bin/phpunit && .vendor/bin/phpunit --filter ${testFilter}`
|
||||
},
|
||||
stan: {
|
||||
exec: "vendor/bin/phpstan analyse -l 8 src/main src/test"
|
||||
|
|
38
README.md
38
README.md
|
@ -9,15 +9,16 @@ This tool regularly checks if people are still alive according to Wikipedia, and
|
|||
|
||||
## Development
|
||||
### Requirements
|
||||
* PHP 8.1+ (i.e. `apt install php php-cgi`)
|
||||
* PHP 8.1+ (i.e. `apt install php php-cgi`) (compiled with Argon2 support)
|
||||
* [PHP cURL](https://www.php.net/manual/en/book.curl.php) (i.e. `apt install php-curl`)
|
||||
* [PHP DOM](https://www.php.net/manual/en/book.dom.php) (i.e. `apt install php-dom`)
|
||||
* [PHP mbstring](https://www.php.net/manual/en/book.mbstring.php) (i.e. `apt install php-mbstring`)
|
||||
* [PHP SQLite 3](https://www.php.net/manual/en/book.sqlite3.php) (i.e. `apt install php-sqlite3`)
|
||||
* [composer](https://getcomposer.org/) (make sure `composer.phar` is on your path)
|
||||
* [npm](https://www.npmjs.com/)
|
||||
|
||||
### Setting up
|
||||
Install the latest dependencies.
|
||||
Install the dependencies.
|
||||
Run this after cloning the repo, and each time after pulling new commits.
|
||||
```shell
|
||||
composer.phar install
|
||||
|
@ -45,7 +46,10 @@ composer.phar update
|
|||
npm install
|
||||
```
|
||||
|
||||
### Static analysis
|
||||
### Static analysis and tests
|
||||
Note that PHPUnit suppresses output from `print`.
|
||||
Instead, you can use `fwrite(STDERR, print_r("my message", TRUE));`.
|
||||
|
||||
* Run static analysis
|
||||
```shell
|
||||
npm run analyze
|
||||
|
@ -54,10 +58,24 @@ npm install
|
|||
```shell
|
||||
npm run test
|
||||
```
|
||||
* Run all tests in package
|
||||
* Run only select tests
|
||||
```shell
|
||||
npm run test -- --test-target=com/fwdekker/deathnotifier/wikipedia/
|
||||
npm run test -- --test-filter="test-name file-name"
|
||||
```
|
||||
Note that a `test-name` of `''` and a `file-name` of `.` matches all tests.
|
||||
|
||||
* Run all tests in package
|
||||
```shell
|
||||
npm run test -- --test-filter="'' com/fwdekker/deathnotifier/wikipedia/"
|
||||
```
|
||||
* Run all tests in class
|
||||
```shell
|
||||
npm run test -- --test-filter="'' com/fwdekker/deathnotifier/wikipedia/WikipediaTest.php"
|
||||
```
|
||||
* Run only specific test
|
||||
```shell
|
||||
npm run test -- --test-filter="test_query_detects_dead_person ."
|
||||
```
|
||||
* Run static analysis and tests
|
||||
```shell
|
||||
npm run check
|
||||
|
@ -77,20 +95,22 @@ Inside the installation directory, create `config.ini.php` and use it to overrid
|
|||
Make sure only the user that runs PHP can read/write `config.ini.php`.
|
||||
|
||||
### Cron jobs
|
||||
You should run the `process-email-queue` and `update-all-trackings` actions regularly;
|
||||
You should run the `process-email-queue` and `update-trackings` actions regularly;
|
||||
recommended is every minute and every five minutes, respectively.
|
||||
For example, you can add the following lines to your crontab (e.g. using `sudo -u www crontab -e`):
|
||||
```
|
||||
* * * * * cd /var/www/death-notifier && php /var/www/death-notifier/api.php action=process-email-queue password=secret_password
|
||||
*/5 * * * * cd /var/www/death-notifier && php /var/www/death-notifier/api.php action=update-all-trackings password=secret_password
|
||||
*/5 * * * * cd /var/www/death-notifier && php /var/www/death-notifier/api.php action=update-trackings password=secret_password
|
||||
```
|
||||
Replace `secret_password` with the password you configured in `config.ini.php`.
|
||||
|
||||
### Logs
|
||||
It is recommended to also use a tool such as `newsyslog` to manage log rotation.
|
||||
It is recommended to also use a tool such as
|
||||
[`newsyslog`](https://man.freebsd.org/cgi/man.cgi?query=newsyslog.conf&sektion=5) to manage log rotation.
|
||||
For example, create the file `/etc/newsyslog.conf.d/death-notifier.conf` with the following contents:
|
||||
```
|
||||
/var/www/death-notifier/.death-notifier.log www:www 644 7 * @T00 JpE
|
||||
/var/www/death-notifier/.death-notifier.log www:www 600 7 * @T00 JE
|
||||
/var/www/death-notifier/.death-notifier.db.log www:www 600 7 * $W0D23 JpE
|
||||
```
|
||||
|
||||
### Initialize
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "fwdekker/death-notifier",
|
||||
"description": "Get notified when a famous person dies.",
|
||||
"version": "1.0.1", "_comment_version": "Also update version in `package.json`!",
|
||||
"version": "1.3.0", "_comment_version": "Also update version in `package.json`!",
|
||||
"type": "project",
|
||||
"license": "MIT",
|
||||
"homepage": "https://git.fwdekker.com/tools/death-notifier",
|
||||
|
@ -19,12 +19,12 @@
|
|||
"composer/semver": "^3.3",
|
||||
"ext-curl": "*",
|
||||
"ext-pdo": "*",
|
||||
"monolog/monolog": "^3.3",
|
||||
"monolog/monolog": "^3.4",
|
||||
"phpmailer/phpmailer": "^6.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.9",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
"phpstan/phpstan": "^1.10.32",
|
||||
"phpunit/phpunit": "^10.3.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
Binary file not shown.
Binary file not shown.
12
package.json
12
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "death-notifier",
|
||||
"version": "1.0.1", "_comment_version": "Also update version in `composer.json`!",
|
||||
"version": "1.3.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",
|
||||
|
@ -27,10 +27,10 @@
|
|||
"grunt-focus": "^1.0.0",
|
||||
"grunt-run": "^0.8.1",
|
||||
"grunt-text-replace": "^0.4.0",
|
||||
"grunt-webpack": "^5.0.0",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^5.0.2",
|
||||
"webpack": "^5.76.2",
|
||||
"webpack-cli": "^5.0.1"
|
||||
"grunt-webpack": "^6.0.0",
|
||||
"ts-loader": "^9.4.4",
|
||||
"typescript": "^5.2.2",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ use com\fwdekker\deathnotifier\Util;
|
|||
require_once __DIR__ . "/.vendor/autoload.php";
|
||||
|
||||
|
||||
$logger = LoggerUtil::with_name();
|
||||
$logger = LoggerUtil::with_name(); // This also registers error handler
|
||||
|
||||
|
||||
// Wrap everything in try-catch to always return *something* to user
|
||||
|
|
|
@ -1,30 +1,37 @@
|
|||
;<?php exit(); ?>
|
||||
|
||||
[admin]
|
||||
# bcrypt hash of password to use the CLI of `api.php`. If set to its default value, or if empty, the CLI is disabled.
|
||||
cli_password = REPLACE THIS WITH A SECRET VALUE
|
||||
# PHC-formatted hash of password for the CLI of `api.php`. You can create one using PHP's `password_hash`. Escaping
|
||||
# dollar symbols is optional. If set to its default value, or if empty, the CLI is disabled.
|
||||
cli_password = "REPLACE THIS WITH A SECRET VALUE"
|
||||
|
||||
[database]
|
||||
# Relative path to SQLite database.
|
||||
filename = .death-notifier.db
|
||||
# File to store SQLite database in.
|
||||
filename = ".death-notifier.db"
|
||||
|
||||
[logger]
|
||||
# File to store logs in.
|
||||
file = .death-notifier.log
|
||||
# Log level. See https://seldaek.github.io/monolog/doc/01-usage.html#log-levels
|
||||
# File to store general logs in.
|
||||
filename = ".death-notifier.log"
|
||||
# Log level for general log events. See https://seldaek.github.io/monolog/doc/01-usage.html#log-levels
|
||||
level = 250
|
||||
|
||||
[logger_db]
|
||||
# File to store database logs in.
|
||||
filename = ".death-notifier.db.log"
|
||||
# Log level for database log events. See https://seldaek.github.io/monolog/doc/01-usage.html#log-levels
|
||||
level = 250
|
||||
|
||||
[mail]
|
||||
# Host name of SMTP server to send mail through.
|
||||
host = TODO
|
||||
host = "TODO"
|
||||
# Port of SMTP server to send mail through.
|
||||
port = TODO
|
||||
# Username to authenticate with at SMTP server.
|
||||
username = TODO
|
||||
username = "TODO"
|
||||
# Password to authenticate with at SMTP server.
|
||||
password = TODO
|
||||
password = "TODO"
|
||||
# Name to show to recipient.
|
||||
from_name = TODO
|
||||
from_name = "TODO"
|
||||
|
||||
[security]
|
||||
# `true` if and only if insecure file permissions for config files should be tolerated.
|
||||
|
@ -32,8 +39,13 @@ allow_config_insecure_permissions = false
|
|||
|
||||
[server]
|
||||
# The path to the directory containing the site's main page.
|
||||
base_path = https://example.com/death-notifier/
|
||||
base_path = "https://example.com/death-notifier/"
|
||||
# The path at which users can report bugs, or an empty string if there is no such path.
|
||||
issue_path =
|
||||
issue_path = ""
|
||||
# The message to display at the top of all pages. A blank string hides the message.
|
||||
global_message =
|
||||
global_message = ""
|
||||
|
||||
[wikipedia]
|
||||
# Contact information to include in requests to Wikipedia's API. Typically a URL and an email address separated by a
|
||||
# semicolon. See also https://www.mediawiki.org/wiki/API:Etiquette.
|
||||
user_agent_contact = "https://example.com/death-notifier; name@example.com"
|
||||
|
|
|
@ -409,7 +409,7 @@
|
|||
|
||||
<input id="delete-account-actual-email" type="hidden" name="email" />
|
||||
|
||||
<label for="delete-account-email">Confirm email</label>
|
||||
<label for="delete-account-email">Confirm email address</label>
|
||||
<input id="delete-account-email" name="email" autocomplete="off" />
|
||||
<small id="delete-account-email-hint" data-hint-for="delete-account-email"></small>
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ class Database
|
|||
/**
|
||||
* @var Logger the logger to use for logging
|
||||
*/
|
||||
private readonly Logger $logger;
|
||||
private readonly Logger $db_logger;
|
||||
|
||||
/**
|
||||
* @var PDO the PDO object that connects to the database
|
||||
|
@ -39,7 +39,7 @@ class Database
|
|||
*/
|
||||
public function __construct(string $filename)
|
||||
{
|
||||
$this->logger = LoggerUtil::with_name($this::class);
|
||||
$this->db_logger = LoggerUtil::db_with_name($this::class);
|
||||
|
||||
$this->conn = new PDO("sqlite:$filename", options: array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));
|
||||
$this->conn->exec("PRAGMA foreign_keys = ON;");
|
||||
|
@ -64,7 +64,7 @@ class Database
|
|||
if ($stmt->fetch()[0] !== 0)
|
||||
return;
|
||||
|
||||
$this->logger->notice("Database does not exist. Installing new database.");
|
||||
$this->db_logger->notice("Database does not exist. Installing new database.");
|
||||
|
||||
// Create `meta` table
|
||||
$this->conn->exec("CREATE TABLE meta(k TEXT NOT NULL UNIQUE PRIMARY KEY, v TEXT);");
|
||||
|
@ -77,7 +77,7 @@ class Database
|
|||
$user_list->install();
|
||||
$tracking_list->install();
|
||||
|
||||
$this->logger->notice("Installation complete.");
|
||||
$this->db_logger->notice("Installation complete.");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ class Database
|
|||
if (Comparator::greaterThanOrEqualTo($db_version, Database::LATEST_VERSION))
|
||||
return;
|
||||
|
||||
$this->logger->notice("Current db is v$db_version. Will migrate to v" . Database::LATEST_VERSION . ".");
|
||||
$this->db_logger->notice("Current db is v$db_version. Will migrate to v" . Database::LATEST_VERSION . ".");
|
||||
|
||||
// Get current version
|
||||
$stmt = $this->conn->prepare("SELECT v FROM meta WHERE k='version';");
|
||||
|
@ -115,7 +115,7 @@ class Database
|
|||
$stmt->bindValue(":version", Database::LATEST_VERSION);
|
||||
$stmt->execute();
|
||||
|
||||
$this->logger->notice("Completed migration to v" . Database::LATEST_VERSION . ".");
|
||||
$this->db_logger->notice("Completed migration to v" . Database::LATEST_VERSION . ".");
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ class Database
|
|||
*/
|
||||
private function migrate_0_5_0(): void
|
||||
{
|
||||
$this->logger->notice("Migrating to v0.5.0.");
|
||||
$this->db_logger->notice("Migrating to v0.5.0.");
|
||||
|
||||
$this->conn->exec("ALTER TABLE users
|
||||
ADD COLUMN email_notifications_enabled INT NOT NULL DEFAULT(1);");
|
||||
|
@ -141,7 +141,7 @@ class Database
|
|||
*/
|
||||
private function migrate_0_8_0(): void
|
||||
{
|
||||
$this->logger->notice("Migrating to v0.8.0.");
|
||||
$this->db_logger->notice("Migrating to v0.8.0.");
|
||||
|
||||
$this->conn->exec("CREATE TABLE new_email_tasks(type TEXT NOT NULL,
|
||||
recipient TEXT NOT NULL,
|
||||
|
@ -163,7 +163,7 @@ class Database
|
|||
*/
|
||||
private function migrate_0_10_0(): void
|
||||
{
|
||||
$this->logger->notice("Migrating to v0.10.0.");
|
||||
$this->db_logger->notice("Migrating to v0.10.0.");
|
||||
|
||||
$this->conn->exec("CREATE TABLE new_email_tasks(type TEXT NOT NULL,
|
||||
recipient TEXT NOT NULL,
|
||||
|
@ -188,7 +188,7 @@ class Database
|
|||
*/
|
||||
private function migrate_0_16_0(): void
|
||||
{
|
||||
$this->logger->notice("Migrating to v0.16.0.");
|
||||
$this->db_logger->notice("Migrating to v0.16.0.");
|
||||
|
||||
$this->conn->exec("DROP TABLE email_tasks;");
|
||||
$this->conn->exec("CREATE TABLE email_tasks(type_key TEXT NOT NULL,
|
||||
|
@ -206,7 +206,7 @@ class Database
|
|||
*/
|
||||
private function migrate_0_18_0(): void
|
||||
{
|
||||
$this->logger->notice("Migrating to v0.18.0.");
|
||||
$this->db_logger->notice("Migrating to v0.18.0.");
|
||||
|
||||
$this->conn->exec("CREATE TABLE new_trackings(user_uuid TEXT NOT NULL,
|
||||
person_name TEXT NOT NULL,
|
||||
|
|
|
@ -15,28 +15,49 @@ use Monolog\Logger;
|
|||
class LoggerUtil
|
||||
{
|
||||
/**
|
||||
* @var Logger|null the main logger instance that other loggers are derived from, or `null` if the main logger has
|
||||
* not been created yet
|
||||
* @var Logger|null the logger instance for general log events not covered by other loggers
|
||||
*/
|
||||
private static ?Logger $main_logger = null;
|
||||
private static ?Logger $general_logger = null;
|
||||
/**
|
||||
* @var Logger|null the logger instance for database log events
|
||||
*/
|
||||
private static ?Logger $db_logger = null;
|
||||
|
||||
|
||||
/**
|
||||
* Returns a logger with the given name.
|
||||
* Returns a general logger with the given name.
|
||||
*
|
||||
* @param string $name the name of the logger to return
|
||||
* @return Logger a logger with the given name
|
||||
* @param string $name the name of the general logger to return
|
||||
* @return Logger a general logger with the given name
|
||||
*/
|
||||
public static function with_name(string $name = "main"): Logger
|
||||
public static function with_name(string $name = "general"): Logger
|
||||
{
|
||||
if (self::$main_logger === null) {
|
||||
if (self::$general_logger === null) {
|
||||
$config = Config::get("logger");
|
||||
|
||||
self::$main_logger = new Logger("main");
|
||||
self::$main_logger->pushHandler(new StreamHandler($config["file"], $config["level"]));
|
||||
ErrorHandler::register(self::$main_logger);
|
||||
self::$general_logger = new Logger("general");
|
||||
self::$general_logger->pushHandler(new StreamHandler($config["filename"], $config["level"]));
|
||||
ErrorHandler::register(self::$general_logger);
|
||||
}
|
||||
|
||||
return self::$main_logger->withName($name);
|
||||
return self::$general_logger->withName($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a database logger with the given name.
|
||||
*
|
||||
* @param string $name the name of the database logger to return
|
||||
* @return Logger a database logger with the given name
|
||||
*/
|
||||
public static function db_with_name(string $name = "database"): Logger
|
||||
{
|
||||
if (self::$db_logger === null) {
|
||||
$config = Config::get("logger_db");
|
||||
|
||||
self::$db_logger = new Logger("general");
|
||||
self::$db_logger->pushHandler(new StreamHandler($config["filename"], $config["level"]));
|
||||
}
|
||||
|
||||
return self::$db_logger->withName($name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ class ProcessEmailQueueAction extends Action
|
|||
$mailer->SMTPKeepAlive = true;
|
||||
$mailer->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
|
||||
$mailer->Host = $config["host"];
|
||||
$mailer->Port = $config["port"];
|
||||
$mailer->Port = intval($config["port"]);
|
||||
$mailer->Username = $config["username"];
|
||||
$mailer->Password = $config["password"];
|
||||
try {
|
||||
|
|
|
@ -31,6 +31,10 @@ class UpdateTrackingsAction extends Action
|
|||
* @var Logger the logger to log with
|
||||
*/
|
||||
private readonly Logger $logger;
|
||||
/**
|
||||
* @var Logger the database logger to log with
|
||||
*/
|
||||
private readonly Logger $db_logger;
|
||||
/**
|
||||
* @var TrackingList the list of trackings to update
|
||||
*/
|
||||
|
@ -55,6 +59,7 @@ class UpdateTrackingsAction extends Action
|
|||
public function __construct(TrackingList $tracking_list, Wikipedia $wikipedia, EmailQueue $mailer)
|
||||
{
|
||||
$this->logger = LoggerUtil::with_name($this::class);
|
||||
$this->db_logger = LoggerUtil::db_with_name($this::class);
|
||||
$this->tracking_list = $tracking_list;
|
||||
$this->wikipedia = $wikipedia;
|
||||
$this->mailer = $mailer;
|
||||
|
@ -105,21 +110,20 @@ class UpdateTrackingsAction extends Action
|
|||
// Send mails, log events
|
||||
// TODO: Restrict number of notifications to 1 per hour (excluding "oops we're not sure" message)
|
||||
// TODO: Wait for some time after person has been marked as dead before sending the email
|
||||
$logger = LoggerUtil::with_name($this::class);
|
||||
$emails = [];
|
||||
|
||||
$person_names = $new_deletions + $new_undeletions + array_keys($new_status_changes);
|
||||
$trackers = $this->tracking_list->list_trackers($person_names);
|
||||
|
||||
foreach ($new_deletions as $new_deletion) {
|
||||
$logger->notice("Deleted article $new_deletion.");
|
||||
$this->db_logger->notice("Deleted article $new_deletion.");
|
||||
|
||||
foreach ($trackers[$new_deletion] as $user_email)
|
||||
$emails[] = new NotifyArticleDeletedEmail($user_email, $new_deletion);
|
||||
}
|
||||
|
||||
foreach ($new_undeletions as $new_undeletion) {
|
||||
$logger->notice("Undeleted article $new_undeletion.");
|
||||
$this->db_logger->notice("Undeleted article $new_undeletion.");
|
||||
|
||||
foreach ($trackers[$new_undeletion] as $user_email)
|
||||
$emails[] = new NotifyArticleUndeletedEmail($user_email, $new_undeletion);
|
||||
|
@ -127,7 +131,7 @@ class UpdateTrackingsAction extends Action
|
|||
|
||||
foreach ($new_status_changes as $person_name => $person_status) {
|
||||
if ($person_status === PersonStatus::Alive)
|
||||
$logger->notice("Person $person_name is now alive again.");
|
||||
$this->db_logger->notice("Person $person_name is now alive again.");
|
||||
|
||||
foreach ($trackers[$person_name] as $user_email)
|
||||
$emails[] = new NotifyStatusChangedEmail($user_email, $person_name, $person_status->value);
|
||||
|
@ -179,7 +183,7 @@ class NotifyStatusChangedEmail extends Email
|
|||
|
||||
public function get_subject(): string
|
||||
{
|
||||
return "$this->name may be $this->new_status";
|
||||
return "Death Notifier: $this->name may be $this->new_status";
|
||||
}
|
||||
|
||||
public function get_body(): string
|
||||
|
@ -191,6 +195,10 @@ class NotifyStatusChangedEmail extends Email
|
|||
"For more information, read their Wikipedia article at " .
|
||||
"https://en.wikipedia.org/wiki/" . rawurlencode($this->name) .
|
||||
"\n\n" .
|
||||
"Note that Wikipedia is not a reliable source of information and is subject to vandalism and errors. " .
|
||||
"Please check if reliable news sources have reported on $this->name before spreading this message to " .
|
||||
"others." .
|
||||
"\n\n" .
|
||||
"You are receiving this message because of the preferences in your Death Notifier account. " .
|
||||
"To unsubscribe from these messages, go to the Death Notifier website, log in, and change your email " .
|
||||
"preferences." .
|
||||
|
@ -233,7 +241,7 @@ class NotifyArticleDeletedEmail extends Email
|
|||
|
||||
public function get_subject(): string
|
||||
{
|
||||
return "$this->name article has been deleted";
|
||||
return "Death Notifier: $this->name article has been deleted";
|
||||
}
|
||||
|
||||
public function get_body(): string
|
||||
|
@ -288,7 +296,7 @@ class NotifyArticleUndeletedEmail extends Email
|
|||
|
||||
public function get_subject(): string
|
||||
{
|
||||
return "$this->name article has been re-created";
|
||||
return "Death Notifier: $this->name article has been re-created";
|
||||
}
|
||||
|
||||
public function get_body(): string
|
||||
|
|
|
@ -104,7 +104,7 @@ class UserList
|
|||
VALUES (:email, :password)
|
||||
RETURNING email_verification_token;");
|
||||
$stmt->bindValue(":email", $email);
|
||||
$stmt->bindValue(":password", password_hash($password, PASSWORD_BCRYPT));
|
||||
$stmt->bindValue(":password", password_hash($password, PASSWORD_ARGON2ID));
|
||||
$stmt->execute();
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC)[0]["email_verification_token"];
|
||||
}
|
||||
|
@ -265,11 +265,12 @@ class UserList
|
|||
public function set_password(string $uuid, string $password): void
|
||||
{
|
||||
$stmt = $this->database->conn->prepare("UPDATE users
|
||||
SET password=:password, password_last_change=unixepoch(),
|
||||
SET password=:password,
|
||||
password_last_change=unixepoch(),
|
||||
password_reset_token=null
|
||||
WHERE uuid=:uuid;");
|
||||
$stmt->bindValue(":uuid", $uuid);
|
||||
$stmt->bindValue(":password", password_hash($password, PASSWORD_BCRYPT));
|
||||
$stmt->bindValue(":password", password_hash($password, PASSWORD_ARGON2ID));
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace com\fwdekker\deathnotifier\wikipedia;
|
||||
|
||||
use com\fwdekker\deathnotifier\LoggerUtil;
|
||||
use com\fwdekker\deathnotifier\Config;
|
||||
use com\fwdekker\deathnotifier\Util;
|
||||
use JsonException;
|
||||
|
||||
|
@ -19,9 +19,7 @@ class Wikipedia
|
|||
/**
|
||||
* The user agent used to represent the death notifier to Wikipedia.
|
||||
*/
|
||||
private const USER_AGENT =
|
||||
"death-notifier/%%VERSION_NUMBER%% " .
|
||||
"(https://git.fwdekker.com/tools/death-notifier; florine@fwdekker.com)";
|
||||
private const USER_AGENT_FORMAT = "death-notifier/%%VERSION_NUMBER%% (%1\$s) %2\$s";
|
||||
/**
|
||||
* Number of articles to query per query.
|
||||
*/
|
||||
|
@ -48,10 +46,13 @@ class Wikipedia
|
|||
*/
|
||||
private function api_fetch(array $params): mixed
|
||||
{
|
||||
$curl_version = curl_version()["version"] ?? "unknown";
|
||||
$user_agent = sprintf(self::USER_AGENT_FORMAT, Config::get("wikipedia")["user_agent_contact"], $curl_version);
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_URL, self::API_URL . http_build_query($params));
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, self::USER_AGENT);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
|
||||
|
||||
$output = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
@ -262,7 +263,7 @@ class Wikipedia
|
|||
return null;
|
||||
|
||||
$category_titles = array_column($article["categories"], "title");
|
||||
$dead_regex = "/^Category:([0-9]{1,4}s? (BC |AD )?deaths|Year of death (missing|unknown))$/";
|
||||
$dead_regex = "/^Category:([0-9]{1,4}s? (BC |AD )?(deaths|suicides)|Year of death (missing|unknown))$/";
|
||||
|
||||
if (!empty(array_filter($category_titles, fn($it) => preg_match($dead_regex, $it))))
|
||||
return PersonStatus::Dead;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
namespace com\fwdekker\deathnotifier\validation;
|
||||
namespace com\fwdekker\deathnotifier;
|
||||
|
||||
use com\fwdekker\deathnotifier\Config;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
|
|
@ -44,9 +44,9 @@ class EqualsCliPasswordRuleTest extends TestCase
|
|||
* Returns the test cases.
|
||||
*
|
||||
* @return array<string, array{string|null, string|null, class-string<Throwable>|null, string|null}> the test cases
|
||||
* @see RuleTest::test_check()
|
||||
* @see RuleTestTemplate::test_check()
|
||||
*/
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$hash = "\$2y\$04\$fwXTw7Rjzw0EpU094u4agOBaBNqtCHGc4TMoxfbPrxuqO5tpYyRka"; # Hash of "password"
|
||||
|
||||
|
|
|
@ -8,9 +8,9 @@ use InvalidArgumentException;
|
|||
/**
|
||||
* Unit tests for {@see EqualsRule}.
|
||||
*/
|
||||
class EqualsRuleTest extends RuleTest
|
||||
class EqualsRuleTest extends RuleTestTemplate
|
||||
{
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
$value = InvalidValueException::class;
|
||||
|
|
|
@ -8,9 +8,9 @@ use InvalidArgumentException;
|
|||
/**
|
||||
* Unit tests for {@see HasStringLengthRule}.
|
||||
*/
|
||||
class HasStringLengthRuleTest extends RuleTest
|
||||
class HasStringLengthRuleTest extends RuleTestTemplate
|
||||
{
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
$value = InvalidValueException::class;
|
||||
|
|
|
@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
|
|||
/**
|
||||
* Unit tests for {@see IsBooleanRule}.
|
||||
*/
|
||||
class IsBooleanRuleTest extends RuleTest
|
||||
class IsBooleanRuleTest extends RuleTestTemplate
|
||||
{
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
|
|||
/**
|
||||
* Unit tests for {@see IsEmailRule}.
|
||||
*/
|
||||
class IsEmailRuleTest extends RuleTest
|
||||
class IsEmailRuleTest extends RuleTestTemplate
|
||||
{
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
$value = InvalidValueException::class;
|
||||
|
|
|
@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
|
|||
/**
|
||||
* Unit tests for {@see IsNotBlankRule}.
|
||||
*/
|
||||
class IsNotBlankRuleTest extends RuleTest
|
||||
class IsNotBlankRuleTest extends RuleTestTemplate
|
||||
{
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
$value = InvalidValueException::class;
|
||||
|
|
|
@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
|
|||
/**
|
||||
* Unit tests for {@see IsStringRule}.
|
||||
*/
|
||||
class IsStringRuleTest extends RuleTest
|
||||
class IsStringRuleTest extends RuleTestTemplate
|
||||
{
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
|
||||
|
|
|
@ -6,9 +6,9 @@ namespace com\fwdekker\deathnotifier\validation;
|
|||
/**
|
||||
* Unit tests for {@see IsValidCsrfTokenRule}.
|
||||
*/
|
||||
class IsValidCsrfTokenRuleTest extends RuleTest
|
||||
class IsValidCsrfTokenRuleTest extends RuleTestTemplate
|
||||
{
|
||||
public function check_provider(): array
|
||||
public static function check_provider(): array
|
||||
{
|
||||
$type = InvalidTypeException::class;
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ use Throwable;
|
|||
/**
|
||||
* Unit tests for {@see Rule} implementations.
|
||||
*/
|
||||
abstract class RuleTest extends TestCase
|
||||
abstract class RuleTestTemplate extends TestCase
|
||||
{
|
||||
/**
|
||||
* Tests the output of {@see Rule::check()}.
|
||||
|
@ -40,7 +40,7 @@ abstract class RuleTest extends TestCase
|
|||
* Returns the test cases.
|
||||
*
|
||||
* @return array<string, array{Rule, mixed|null, class-string<Throwable>|null, string|null}> the test cases
|
||||
* @see RuleTest::test_check()
|
||||
* @see RuleTestTemplate::test_check()
|
||||
*/
|
||||
abstract public function check_provider(): array;
|
||||
abstract public static function check_provider(): array;
|
||||
}
|
|
@ -135,6 +135,13 @@ class WikipediaTest extends TestCase
|
|||
}
|
||||
|
||||
public function test_query_detects_dead_person(): void
|
||||
{
|
||||
$output = $this->wikipedia->query_people_info(["Sophie (musician)"], resolve_moves: false);
|
||||
|
||||
self::assertEquals(PersonStatus::Dead, $output->results["Sophie (musician)"]["status"]);
|
||||
}
|
||||
|
||||
public function test_query_detects_suicide_person(): void
|
||||
{
|
||||
$output = $this->wikipedia->query_people_info(["Adolf Hitler"], resolve_moves: false);
|
||||
|
||||
|
@ -150,8 +157,8 @@ class WikipediaTest extends TestCase
|
|||
|
||||
public function test_query_detects_possibly_living_person(): void
|
||||
{
|
||||
$output = $this->wikipedia->query_people_info(["Judge Edward Aaron"], resolve_moves: false);
|
||||
$output = $this->wikipedia->query_people_info(["Angela Anderes"], resolve_moves: false);
|
||||
|
||||
self::assertEquals(PersonStatus::PossiblyAlive, $output->results["Judge Edward Aaron"]["status"]);
|
||||
self::assertEquals(PersonStatus::PossiblyAlive, $output->results["Angela Anderes"]["status"]);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue