xref: /webtrees/app/Validator.php (revision fcbce9d892215fd906922a0743351bac0895004e)
18d9c2b68SGreg Roach<?php
28d9c2b68SGreg Roach
38d9c2b68SGreg Roach/**
48d9c2b68SGreg Roach * webtrees: online genealogy
58d9c2b68SGreg Roach * Copyright (C) 2021 webtrees development team
68d9c2b68SGreg Roach * This program is free software: you can redistribute it and/or modify
78d9c2b68SGreg Roach * it under the terms of the GNU General Public License as published by
88d9c2b68SGreg Roach * the Free Software Foundation, either version 3 of the License, or
98d9c2b68SGreg Roach * (at your option) any later version.
108d9c2b68SGreg Roach * This program is distributed in the hope that it will be useful,
118d9c2b68SGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
128d9c2b68SGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
138d9c2b68SGreg Roach * GNU General Public License for more details.
148d9c2b68SGreg Roach * You should have received a copy of the GNU General Public License
158d9c2b68SGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
168d9c2b68SGreg Roach */
178d9c2b68SGreg Roach
188d9c2b68SGreg Roachdeclare(strict_types=1);
198d9c2b68SGreg Roach
208d9c2b68SGreg Roachnamespace Fisharebest\Webtrees;
218d9c2b68SGreg Roach
228d9c2b68SGreg Roachuse Closure;
238d9c2b68SGreg Roachuse LogicException;
248d9c2b68SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
258d9c2b68SGreg Roach
268d9c2b68SGreg Roachuse function array_reduce;
278d9c2b68SGreg Roachuse function ctype_digit;
288d9c2b68SGreg Roachuse function is_array;
298d9c2b68SGreg Roachuse function is_int;
308d9c2b68SGreg Roachuse function is_string;
318d9c2b68SGreg Roachuse function parse_url;
328d9c2b68SGreg Roachuse function str_starts_with;
338d9c2b68SGreg Roach
348d9c2b68SGreg Roach/**
358d9c2b68SGreg Roach * Validate a parameter from an HTTP request
368d9c2b68SGreg Roach */
378d9c2b68SGreg Roachclass Validator
388d9c2b68SGreg Roach{
398d9c2b68SGreg Roach    /** @var array<string|array> */
408d9c2b68SGreg Roach    private array $parameters;
418d9c2b68SGreg Roach
428d9c2b68SGreg Roach    /** @var array<Closure> */
438d9c2b68SGreg Roach    private array $rules = [];
448d9c2b68SGreg Roach
458d9c2b68SGreg Roach    /**
468d9c2b68SGreg Roach     * @param array<string|array> $parameters
478d9c2b68SGreg Roach     */
488d9c2b68SGreg Roach    public function __construct(array $parameters)
498d9c2b68SGreg Roach    {
508d9c2b68SGreg Roach        $this->parameters = $parameters;
518d9c2b68SGreg Roach    }
528d9c2b68SGreg Roach
538d9c2b68SGreg Roach    /**
548d9c2b68SGreg Roach     * @param ServerRequestInterface $request
558d9c2b68SGreg Roach     *
568d9c2b68SGreg Roach     * @return self
578d9c2b68SGreg Roach     */
588d9c2b68SGreg Roach    public static function parsedBody(ServerRequestInterface $request): self
598d9c2b68SGreg Roach    {
608d9c2b68SGreg Roach        return new self((array) $request->getParsedBody());
618d9c2b68SGreg Roach    }
628d9c2b68SGreg Roach
638d9c2b68SGreg Roach    /**
648d9c2b68SGreg Roach     * @param ServerRequestInterface $request
658d9c2b68SGreg Roach     *
668d9c2b68SGreg Roach     * @return self
678d9c2b68SGreg Roach     */
688d9c2b68SGreg Roach    public static function queryParams(ServerRequestInterface $request): self
698d9c2b68SGreg Roach    {
708d9c2b68SGreg Roach        return new self($request->getQueryParams());
718d9c2b68SGreg Roach    }
728d9c2b68SGreg Roach
738d9c2b68SGreg Roach    /**
748d9c2b68SGreg Roach     * @param int $minimum
758d9c2b68SGreg Roach     * @param int $maximum
768d9c2b68SGreg Roach     *
778d9c2b68SGreg Roach     * @return self
788d9c2b68SGreg Roach     */
798d9c2b68SGreg Roach    public function isBetween(int $minimum, int $maximum): self
808d9c2b68SGreg Roach    {
818d9c2b68SGreg Roach        $this->rules[] = static function ($value) use ($minimum, $maximum): ?int {
828d9c2b68SGreg Roach            if (is_int($value)) {
838d9c2b68SGreg Roach                if ($value < $minimum || $value > $maximum) {
848d9c2b68SGreg Roach                    return null;
858d9c2b68SGreg Roach                }
868d9c2b68SGreg Roach
878d9c2b68SGreg Roach                return $value;
888d9c2b68SGreg Roach            }
898d9c2b68SGreg Roach
908d9c2b68SGreg Roach            throw new LogicException('Validator::isBetween() can only be used for integers');
918d9c2b68SGreg Roach        };
928d9c2b68SGreg Roach
938d9c2b68SGreg Roach        return $this;
948d9c2b68SGreg Roach    }
958d9c2b68SGreg Roach
968d9c2b68SGreg Roach    public function localUrl(string $base_url): self
978d9c2b68SGreg Roach    {
988d9c2b68SGreg Roach        $this->rules[] = static function ($value) use ($base_url): ?string {
998d9c2b68SGreg Roach            if (is_string($value)) {
1008d9c2b68SGreg Roach                $value_info    = parse_url($value);
1018d9c2b68SGreg Roach                $base_url_info = parse_url($base_url);
1028d9c2b68SGreg Roach
1038d9c2b68SGreg Roach                if (!is_array($base_url_info)) {
1048d9c2b68SGreg Roach                    throw new LogicException(__METHOD__ . ' needs a valid URL');
1058d9c2b68SGreg Roach                }
1068d9c2b68SGreg Roach
1078d9c2b68SGreg Roach                if (is_array($value_info)) {
1088d9c2b68SGreg Roach                    $scheme_ok = ($value_info['scheme'] ?? 'http') === ($base_url_info['scheme'] ?? 'http');
1098d9c2b68SGreg Roach                    $host_ok   = ($value_info['host'] ?? '') === ($base_url_info['host'] ?? '');
1108d9c2b68SGreg Roach                    $port_ok   = ($value_info['port'] ?? '') === ($base_url_info['port'] ?? '');
1118d9c2b68SGreg Roach                    $user_ok   = ($value_info['user'] ?? '') === ($base_url_info['user'] ?? '');
1128d9c2b68SGreg Roach                    $path_ok   = str_starts_with($value_info['path'] ?? '/', $base_url_info['path'] ?? '/');
1138d9c2b68SGreg Roach
1148d9c2b68SGreg Roach                    if ($scheme_ok && $host_ok && $port_ok && $user_ok && $path_ok) {
1158d9c2b68SGreg Roach                        return $value;
1168d9c2b68SGreg Roach                    }
1178d9c2b68SGreg Roach                }
1188d9c2b68SGreg Roach
1198d9c2b68SGreg Roach                return null;
1208d9c2b68SGreg Roach            }
1218d9c2b68SGreg Roach
1228d9c2b68SGreg Roach            throw new LogicException(__METHOD__ . ' can only be used for strings');
1238d9c2b68SGreg Roach        };
1248d9c2b68SGreg Roach
1258d9c2b68SGreg Roach        return $this;
1268d9c2b68SGreg Roach    }
1278d9c2b68SGreg Roach
1288d9c2b68SGreg Roach    /**
1298d9c2b68SGreg Roach     * @param string $parameter
1308d9c2b68SGreg Roach     *
1318d9c2b68SGreg Roach     * @return array<string>
1328d9c2b68SGreg Roach     */
1338d9c2b68SGreg Roach    public function array(string $parameter): array
1348d9c2b68SGreg Roach    {
1358d9c2b68SGreg Roach        $value = $this->parameters[$parameter] ?? null;
1368d9c2b68SGreg Roach
1378d9c2b68SGreg Roach        if (!is_array($value)) {
1388d9c2b68SGreg Roach            $value = [];
1398d9c2b68SGreg Roach        }
1408d9c2b68SGreg Roach
141*fcbce9d8SGreg Roach        return array_reduce($this->rules, static fn ($value, $rule) => $rule($value), $value);
1428d9c2b68SGreg Roach    }
1438d9c2b68SGreg Roach
1448d9c2b68SGreg Roach    /**
1458d9c2b68SGreg Roach     * @param string $parameter
1468d9c2b68SGreg Roach     *
1478d9c2b68SGreg Roach     * @return int|null
1488d9c2b68SGreg Roach     */
1498d9c2b68SGreg Roach    public function integer(string $parameter): ?int
1508d9c2b68SGreg Roach    {
1518d9c2b68SGreg Roach        $value = $this->parameters[$parameter] ?? null;
1528d9c2b68SGreg Roach
1538d9c2b68SGreg Roach        if (is_string($value) && ctype_digit($value)) {
1548d9c2b68SGreg Roach            $value = (int) $value;
1558d9c2b68SGreg Roach        } else {
1568d9c2b68SGreg Roach            $value = null;
1578d9c2b68SGreg Roach        }
1588d9c2b68SGreg Roach
159*fcbce9d8SGreg Roach        return array_reduce($this->rules, static fn ($value, $rule) => $rule($value), $value);
1608d9c2b68SGreg Roach    }
1618d9c2b68SGreg Roach
1628d9c2b68SGreg Roach    /**
1638d9c2b68SGreg Roach     * @param string $parameter
1648d9c2b68SGreg Roach     *
1658d9c2b68SGreg Roach     * @return string|null
1668d9c2b68SGreg Roach     */
1678d9c2b68SGreg Roach    public function string(string $parameter): ?string
1688d9c2b68SGreg Roach    {
1698d9c2b68SGreg Roach        $value = $this->parameters[$parameter] ?? null;
1708d9c2b68SGreg Roach
1718d9c2b68SGreg Roach        if (!is_string($value)) {
1728d9c2b68SGreg Roach            $value = null;
1738d9c2b68SGreg Roach        }
1748d9c2b68SGreg Roach
1758d9c2b68SGreg Roach        return array_reduce($this->rules, static fn ($value, $rule) => $rule($value), $value);
1768d9c2b68SGreg Roach    }
1778d9c2b68SGreg Roach}
178