death-notifier/src/main/php/Validator.php

234 lines
7.9 KiB
PHP

<?php
namespace php;
/**
* Validates arrays of inputs such as `$_POST` or `$_SESSION` using `Rule`s.
*/
class Validator
{
/**
* Validates whether values in `inputs` match the rules specified in `rule_sets`.
*
* @param array<string, string> $inputs the array of inputs in which to check the values
* @param array<string, Rule[]> $rule_sets maps keys in `inputs` to an array of `Rule`s to be checked
* @return Response|null `null` if all rules are satisfied, or an unsatisfied `Response` otherwise
*/
static function validate_inputs(array $inputs, array $rule_sets): ?Response
{
foreach ($rule_sets as $key => $rules) {
foreach ($rules as $rule) {
$is_valid = $rule->check($inputs, $key);
if ($is_valid !== null)
return $is_valid;
}
}
return null;
}
/**
* Validates that the user is logged in.
*
* @param array<string, string> $session the session to check
* @return Response|null `null` if the user is logged in, or an unsatisfied `Response` otherwise
*/
static function validate_logged_in(array $session): ?Response
{
if (!isset($session["uuid"]))
return Response::unsatisfied("You must be logged in to perform this action.");
return null;
}
/**
* Validates that the user is logged out.
*
* @param array<string, string> $session the session to check
* @return Response|null `null` if the user is logged out, or an unsatisfied `Response` otherwise
*/
static function validate_logged_out(array $session): ?Response
{
if (isset($session["uuid"]))
return Response::unsatisfied("You must be logged out to perform this action.");
return null;
}
/**
* Validates that the array contains the correct token.
*
* @param array<string, string> $token_array the array with key `token`
* @param string $token the expected token
* @return Response|null `null` if the token is correct, or an unsatisfied `Response` otherwise
*/
static function validate_token(array $token_array, string $token): ?Response
{
if (!isset($token_array["token"]) || $token_array["token"] !== $token)
return Response::unsatisfied("Invalid request token. Please refresh the page and try again.");
return null;
}
}
/**
* A rule/constraint/assertion that should hold over an input.
*/
abstract class Rule
{
/**
* @var string|null The message to return if the rule does not apply to some input. If `null`, the rule
* implementation can choose an appropriate message.
*/
public ?string $override_message;
/**
* Instantiates a new rule.
*
* @param string|null $override_message the message to return if the rule does not apply to some input. If `null`,
* the rule implementation can choose an appropriate message
*/
public function __construct(?string $override_message = null)
{
$this->override_message = $override_message;
}
/**
* Checks whether the rule holds for `$inputs[$key]`.
*
* Implementations should never assume that the `$inputs[$key]` is set.
*
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
* @param string $key the key in `inputs` of the input to check
* @return Response|null `null` if the rule holds, or an unsatisfied `Response` otherwise
*/
public abstract function check(array $inputs, string $key): ?Response;
}
/**
* Requires the input to be a valid email address.
*/
class IsEmailRule extends Rule
{
/**
* Checks whether the input is a valid email address.
*
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
* @param string $key the key in `inputs` of the input to check
* @return Response|null `null` if `$inputs[$key]` is an email address, or an unsatisfied `Response` otherwise
*/
public function check(array $inputs, string $key): ?Response
{
return !isset($inputs[$key]) || !filter_var($inputs[$key], FILTER_VALIDATE_EMAIL)
? Response::unsatisfied($this->override_message ?? "Enter a valid email address.", $key)
: null;
}
}
/**
* Requires the input to not be blank.
*/
class IsNotBlankRule extends Rule
{
/**
* Checks whether the input is not blank.
*
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
* @param string $key the key in `inputs` of the input to check
* @return Response|null `null` if `trim($inputs[$key])` is not an empty string, or an unsatisfied `Response`
* otherwise
*/
public function check(array $inputs, string $key): ?Response
{
return !isset($inputs[$key]) || trim($inputs[$key]) === ""
? Response::unsatisfied($this->override_message ?? "Use at least one character.", $key)
: null;
}
}
/**
* Requires the input to be set.
*/
class IsSetRule extends Rule
{
/**
* Checks whether the input is set.
*
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
* @param string $key the key in `inputs` of the input to check
* @return Response|null `null` if `isset($inputs[$key])`, or an unsatisfied `Response` otherwise
*/
public function check(array $inputs, string $key): ?Response
{
return !isset($inputs[$key])
? Response::unsatisfied($this->override_message ?? "Field '" . htmlentities($key) . "' required.", $key)
: null;
}
}
/**
* Requires the input to be of a specific length.
*/
class LengthRule extends Rule
{
/**
* @var int|null The minimum length (inclusive), or `null` if there is no minimum length.
*/
private readonly ?int $min_length;
/**
* @var int|null The maximum length (inclusive), or `null` if there is no maximum length.
*/
private readonly ?int $max_length;
/**
* Instantiates a new rule.
*
* @param int|null $min_length the minimum length (inclusive), or `null` if there is no minimum length
* @param int|null $max_length the maximum length (inclusive), or `null` if there is no maximum length
* @param string|null $override_message the message to return if the rule does not apply to some input. If `null`,
* the rule implementation can choose an appropriate message
*/
public function __construct(?int $min_length = null, ?int $max_length = null, ?string $override_message = null)
{
parent::__construct($override_message);
$this->min_length = $min_length;
$this->max_length = $max_length;
}
/**
* Checks whether the input is of the specified length.
*
* @param array<string, mixed> $inputs the list of inputs in which the value at `key` should be checked
* @param string $key the key in `inputs` of the input to check
* @return Response|null `null` if the input is of the specified length, or an unsatisfied `Response` otherwise
*/
public function check(array $inputs, string $key): ?Response
{
if (!isset($inputs[$key]))
return Response::unsatisfied(
$this->override_message ?? "Missing input '$key'.",
$key
);
else if ($this->min_length !== null && strlen($inputs[$key]) < $this->min_length)
return Response::unsatisfied(
$this->override_message ?? "Use at least $this->min_length character(s).",
$key
);
else if ($this->max_length !== null && strlen($inputs[$key]) > $this->max_length)
return Response::unsatisfied(
$this->override_message ?? "Use at most $this->max_length character(s).",
$key
);
else
return null;
}
}