death-notifier/src/main/php/com/fwdekker/deathnotifier/tracking/AddTrackingAction.php

143 lines
6.2 KiB
PHP

<?php
namespace com\fwdekker\deathnotifier\tracking;
use com\fwdekker\deathnotifier\Action;
use com\fwdekker\deathnotifier\IllegalStateError;
use com\fwdekker\deathnotifier\UnexpectedException;
use com\fwdekker\deathnotifier\validation\HasStringLengthRule;
use com\fwdekker\deathnotifier\validation\InvalidTypeException;
use com\fwdekker\deathnotifier\validation\InvalidValueException;
use com\fwdekker\deathnotifier\validation\IsNotBlankRule;
use com\fwdekker\deathnotifier\validation\IsValidCsrfTokenRule;
use com\fwdekker\deathnotifier\validation\RuleSet;
use com\fwdekker\deathnotifier\validation\LoginValidator;
use com\fwdekker\deathnotifier\wikipedia\ArticleType;
use com\fwdekker\deathnotifier\wikipedia\PersonStatus;
use com\fwdekker\deathnotifier\wikipedia\Wikipedia;
use com\fwdekker\deathnotifier\wikipedia\WikipediaException;
/**
* Adds a tracking.
*/
class AddTrackingAction extends Action
{
/**
* @var TrackingList the list to add the tracking to
*/
private readonly TrackingList $tracking_list;
/**
* @var Wikipedia the API of Wikipedia
*/
private readonly Wikipedia $wikipedia;
/**
* Constructs a new `AddTrackingAction`.
*
* @param TrackingList $tracking_list the list to add the tracking to
* @param Wikipedia $wikipedia the API of Wikipedia
*/
public function __construct(TrackingList $tracking_list, Wikipedia $wikipedia)
{
$this->tracking_list = $tracking_list;
$this->wikipedia = $wikipedia;
}
/**
* Adds a tracking by the current user of the specified person.
*
* Requires that the user is logged in and that a valid CSRF token is present.
*
* @param array<int|string, mixed> $inputs `"token": string`: a valid CSRF token, `"person_name": string`: the name
* of the person to track
* @return array{"input_name": string, "normalized_name": string} the person's name as given by the user, and the
* normalized version of that name
* @throws InvalidTypeException if any of the inputs has the incorrect type
* @throws InvalidValueException if the user is not logged in, if no valid CSRF token is present, if the article
* title is a blank string, if the article title is too short or too long, if the specified article does not exist,
* if the specified article is not about a person, or if the user is already tracking this article
* @throws UnexpectedException if Wikipedia could not be reached
*/
public function handle(array $inputs): array
{
(new LoginValidator(validate_logged_in: true))->check($_SESSION);
(new RuleSet([
"token" => [new IsValidCsrfTokenRule()],
"person_name" => [
new IsNotBlankRule(),
new HasStringLengthRule(TrackingList::MIN_TITLE_LENGTH, TrackingList::MAX_TITLE_LENGTH)
],
]))->check($inputs);
[$normalized_name, $status] = $this->get_and_validate_page_info(strval($inputs["person_name"]));
$this->tracking_list->transaction(function () use ($normalized_name, $status) {
if ($this->tracking_list->has_tracking($_SESSION["uuid"], $normalized_name))
throw new InvalidValueException("You are already tracking <b>$normalized_name</b>.");
$this->tracking_list->add_tracking($_SESSION["uuid"], $normalized_name, $status);
});
return ["input_name" => $inputs["person_name"], "normalized_name" => $normalized_name];
}
/**
* Validates that {@see $person_name} is an article about a person, and returns information about that article.
*
* @param string $person_name the title of the article about a person to return the information of
* @return array{string, PersonStatus} the normalized name and `PersonStatus` of the specified article
* @throws InvalidValueException if the article about {@see $person_name} does not exist or is not about a person
* @throws UnexpectedException if Wikipedia could not be reached
*/
private function get_and_validate_page_info(string $person_name): array
{
try {
$info = $this->wikipedia->query_person_info([$person_name]);
$normalized_name = $info->redirects[$person_name];
$type = $info->results[$normalized_name]["type"];
$status = $info->results[$normalized_name]["status"];
} catch (WikipediaException $exception) {
throw new UnexpectedException(
"Could not reach Wikipedia. Maybe the website is down?",
previous: $exception
);
}
if (in_array($normalized_name, $info->missing)) {
throw new InvalidValueException(
$this->override_message ??
"Wikipedia does not have an article about " .
"<b><a href='https://en.wikipedia.org/wiki/Special:Search?search=" .
rawurlencode($normalized_name) . "'>" . htmlentities($normalized_name) . "</a></b>. " .
"Maybe you need to capitalise the surname?",
"person_name"
);
} else if ($type === ArticleType::Disambiguation) {
throw new InvalidValueException(
$this->override_message ??
"<b><a href='https://en.wikipedia.org/wiki/" . rawurlencode($normalized_name) . "'>" .
htmlentities($normalized_name) . "</a></b> refers to multiple articles. " .
"<a href='https://en.wikipedia.org/wiki/" . rawurlencode($normalized_name) . "'>Check Wikipedia</a> " .
"to see if your article is listed.",
"person_name"
);
} else if ($type === ArticleType::Other) {
throw new InvalidValueException(
$this->override_message ??
"The Wikipedia article about " .
"<b><a href='https://en.wikipedia.org/wiki/" . rawurlencode($normalized_name) . "'>" .
htmlentities($normalized_name) . "</a></b> is not about a real-world person.",
"person_name"
);
}
if ($status === null)
throw new IllegalStateError("Person page does not have a status.");
return [$normalized_name, $status];
}
}