xref: /webtrees/app/Validator.php (revision 04b3bc82c4274d49542980cd1dca2259779652a2)
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
22b55cbc6bSGreg Roachuse Aura\Router\Route;
238d9c2b68SGreg Roachuse Closure;
24b55cbc6bSGreg Roachuse Fisharebest\Webtrees\Contracts\UserInterface;
2581b729d3SGreg Roachuse Fisharebest\Webtrees\Http\Exceptions\HttpBadRequestException;
268d9c2b68SGreg Roachuse LogicException;
278d9c2b68SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
288d9c2b68SGreg Roach
298d9c2b68SGreg Roachuse function array_reduce;
308d9c2b68SGreg Roachuse function ctype_digit;
318d9c2b68SGreg Roachuse function is_array;
328d9c2b68SGreg Roachuse function is_int;
338d9c2b68SGreg Roachuse function is_string;
348d9c2b68SGreg Roachuse function parse_url;
3581b729d3SGreg Roachuse function preg_match;
368d9c2b68SGreg Roachuse function str_starts_with;
378d9c2b68SGreg Roach
388d9c2b68SGreg Roach/**
398d9c2b68SGreg Roach * Validate a parameter from an HTTP request
408d9c2b68SGreg Roach */
418d9c2b68SGreg Roachclass Validator
428d9c2b68SGreg Roach{
43*04b3bc82SGreg Roach    /** @var array<int|string|Tree|UserInterface|array<int|string>> */
448d9c2b68SGreg Roach    private array $parameters;
458d9c2b68SGreg Roach
468d9c2b68SGreg Roach    /** @var array<Closure> */
478d9c2b68SGreg Roach    private array $rules = [];
488d9c2b68SGreg Roach
498d9c2b68SGreg Roach    /**
50*04b3bc82SGreg Roach     * @param array<int|string|Tree|UserInterface|array<int|string>> $parameters
518d9c2b68SGreg Roach     */
528d9c2b68SGreg Roach    public function __construct(array $parameters)
538d9c2b68SGreg Roach    {
548d9c2b68SGreg Roach        $this->parameters = $parameters;
558d9c2b68SGreg Roach    }
568d9c2b68SGreg Roach
578d9c2b68SGreg Roach    /**
588d9c2b68SGreg Roach     * @param ServerRequestInterface $request
598d9c2b68SGreg Roach     *
608d9c2b68SGreg Roach     * @return self
618d9c2b68SGreg Roach     */
62b55cbc6bSGreg Roach    public static function attributes(ServerRequestInterface $request): self
63b55cbc6bSGreg Roach    {
64b55cbc6bSGreg Roach        return new self($request->getAttributes());
65b55cbc6bSGreg Roach    }
66b55cbc6bSGreg Roach
67b55cbc6bSGreg Roach    /**
68b55cbc6bSGreg Roach     * @param ServerRequestInterface $request
69b55cbc6bSGreg Roach     *
70b55cbc6bSGreg Roach     * @return self
71b55cbc6bSGreg Roach     */
728d9c2b68SGreg Roach    public static function parsedBody(ServerRequestInterface $request): self
738d9c2b68SGreg Roach    {
748d9c2b68SGreg Roach        return new self((array) $request->getParsedBody());
758d9c2b68SGreg Roach    }
768d9c2b68SGreg Roach
778d9c2b68SGreg Roach    /**
788d9c2b68SGreg Roach     * @param ServerRequestInterface $request
798d9c2b68SGreg Roach     *
808d9c2b68SGreg Roach     * @return self
818d9c2b68SGreg Roach     */
828d9c2b68SGreg Roach    public static function queryParams(ServerRequestInterface $request): self
838d9c2b68SGreg Roach    {
848d9c2b68SGreg Roach        return new self($request->getQueryParams());
858d9c2b68SGreg Roach    }
868d9c2b68SGreg Roach
878d9c2b68SGreg Roach    /**
88b55cbc6bSGreg Roach     * @param ServerRequestInterface $request
89b55cbc6bSGreg Roach     *
90b55cbc6bSGreg Roach     * @return self
91b55cbc6bSGreg Roach     */
92b55cbc6bSGreg Roach    public static function serverParams(ServerRequestInterface $request): self
93b55cbc6bSGreg Roach    {
94b55cbc6bSGreg Roach        return new self($request->getServerParams());
95b55cbc6bSGreg Roach    }
96b55cbc6bSGreg Roach
97b55cbc6bSGreg Roach    /**
988d9c2b68SGreg Roach     * @param int $minimum
998d9c2b68SGreg Roach     * @param int $maximum
1008d9c2b68SGreg Roach     *
1018d9c2b68SGreg Roach     * @return self
1028d9c2b68SGreg Roach     */
1038d9c2b68SGreg Roach    public function isBetween(int $minimum, int $maximum): self
1048d9c2b68SGreg Roach    {
1052b1a9a98SGreg Roach        $this->rules[] = static function (?int $value) use ($minimum, $maximum): ?int {
1062b1a9a98SGreg Roach            if (is_int($value) && $value >= $minimum && $value <= $maximum) {
1078d9c2b68SGreg Roach                return $value;
1088d9c2b68SGreg Roach            }
1098d9c2b68SGreg Roach
11081b729d3SGreg Roach            return null;
1118d9c2b68SGreg Roach        };
1128d9c2b68SGreg Roach
1138d9c2b68SGreg Roach        return $this;
1148d9c2b68SGreg Roach    }
1158d9c2b68SGreg Roach
11681b729d3SGreg Roach    /**
1171c6adce8SGreg Roach     * @param array<string> $values
1181c6adce8SGreg Roach     *
1191c6adce8SGreg Roach     * @return $this
1201c6adce8SGreg Roach     */
1211c6adce8SGreg Roach    public function isInArray(array $values): self
1221c6adce8SGreg Roach    {
1231c6adce8SGreg Roach        $this->rules[] = static fn (?string $value): ?string => is_string($value) && in_array($value, $values, true) ? $value : null;
1241c6adce8SGreg Roach
1251c6adce8SGreg Roach        return $this;
1261c6adce8SGreg Roach    }
1271c6adce8SGreg Roach    /**
12881b729d3SGreg Roach     * @param string $base_url
12981b729d3SGreg Roach     *
13081b729d3SGreg Roach     * @return $this
13181b729d3SGreg Roach     */
13281b729d3SGreg Roach    public function isLocalUrl(string $base_url): self
1338d9c2b68SGreg Roach    {
1342b1a9a98SGreg Roach        $this->rules[] = static function (?string $value) use ($base_url): ?string {
1358d9c2b68SGreg Roach            if (is_string($value)) {
1368d9c2b68SGreg Roach                $value_info    = parse_url($value);
1378d9c2b68SGreg Roach                $base_url_info = parse_url($base_url);
1388d9c2b68SGreg Roach
1398d9c2b68SGreg Roach                if (!is_array($base_url_info)) {
1408d9c2b68SGreg Roach                    throw new LogicException(__METHOD__ . ' needs a valid URL');
1418d9c2b68SGreg Roach                }
1428d9c2b68SGreg Roach
1438d9c2b68SGreg Roach                if (is_array($value_info)) {
1448d9c2b68SGreg Roach                    $scheme_ok = ($value_info['scheme'] ?? 'http') === ($base_url_info['scheme'] ?? 'http');
1458d9c2b68SGreg Roach                    $host_ok   = ($value_info['host'] ?? '') === ($base_url_info['host'] ?? '');
1468d9c2b68SGreg Roach                    $port_ok   = ($value_info['port'] ?? '') === ($base_url_info['port'] ?? '');
1478d9c2b68SGreg Roach                    $user_ok   = ($value_info['user'] ?? '') === ($base_url_info['user'] ?? '');
1488d9c2b68SGreg Roach                    $path_ok   = str_starts_with($value_info['path'] ?? '/', $base_url_info['path'] ?? '/');
1498d9c2b68SGreg Roach
1508d9c2b68SGreg Roach                    if ($scheme_ok && $host_ok && $port_ok && $user_ok && $path_ok) {
1518d9c2b68SGreg Roach                        return $value;
1528d9c2b68SGreg Roach                    }
1538d9c2b68SGreg Roach                }
1548d9c2b68SGreg Roach            }
1558d9c2b68SGreg Roach
1562b1a9a98SGreg Roach            return null;
15781b729d3SGreg Roach        };
15881b729d3SGreg Roach
15981b729d3SGreg Roach        return $this;
16081b729d3SGreg Roach    }
16181b729d3SGreg Roach
16281b729d3SGreg Roach    /**
16381b729d3SGreg Roach     * @return $this
16481b729d3SGreg Roach     */
165b55cbc6bSGreg Roach    public function isTag(): self
166b55cbc6bSGreg Roach    {
167b55cbc6bSGreg Roach        $this->rules[] = static function (?string $value): ?string {
168b55cbc6bSGreg Roach            if (is_string($value) && preg_match('/^' . Gedcom::REGEX_TAG . '$/', $value) === 1) {
169b55cbc6bSGreg Roach                return $value;
170b55cbc6bSGreg Roach            }
171b55cbc6bSGreg Roach
172b55cbc6bSGreg Roach            return null;
173b55cbc6bSGreg Roach        };
174b55cbc6bSGreg Roach
175b55cbc6bSGreg Roach        return $this;
176b55cbc6bSGreg Roach    }
177b55cbc6bSGreg Roach
178b55cbc6bSGreg Roach    /**
179b55cbc6bSGreg Roach     * @return $this
180b55cbc6bSGreg Roach     */
18181b729d3SGreg Roach    public function isXref(): self
18281b729d3SGreg Roach    {
1832b1a9a98SGreg Roach        $this->rules[] = static function (?string $value): ?string {
1842b1a9a98SGreg Roach            if (is_string($value) && preg_match('/^' . Gedcom::REGEX_XREF . '$/', $value) === 1) {
18581b729d3SGreg Roach                return $value;
18681b729d3SGreg Roach            }
18781b729d3SGreg Roach
18881b729d3SGreg Roach            return null;
1898d9c2b68SGreg Roach        };
1908d9c2b68SGreg Roach
1918d9c2b68SGreg Roach        return $this;
1928d9c2b68SGreg Roach    }
1938d9c2b68SGreg Roach
1948d9c2b68SGreg Roach    /**
1958d9c2b68SGreg Roach     * @param string $parameter
1968d9c2b68SGreg Roach     *
197f6fdd746SJonathan Jaubart     * @return array<string>|null
1988d9c2b68SGreg Roach     */
199b55cbc6bSGreg Roach    public function optionalArray(string $parameter): ?array
2008d9c2b68SGreg Roach    {
2018d9c2b68SGreg Roach        $value = $this->parameters[$parameter] ?? null;
2028d9c2b68SGreg Roach
2038d9c2b68SGreg Roach        if (!is_array($value)) {
20481b729d3SGreg Roach            $value = null;
2058d9c2b68SGreg Roach        }
2068d9c2b68SGreg Roach
2072b1a9a98SGreg Roach        $callback = static fn (?array $value, Closure $rule): ?array => $rule($value);
2082b1a9a98SGreg Roach
2092b1a9a98SGreg Roach        return array_reduce($this->rules, $callback, $value);
2108d9c2b68SGreg Roach    }
2118d9c2b68SGreg Roach
2128d9c2b68SGreg Roach    /**
2138d9c2b68SGreg Roach     * @param string $parameter
2148d9c2b68SGreg Roach     *
2158d9c2b68SGreg Roach     * @return int|null
2168d9c2b68SGreg Roach     */
217b55cbc6bSGreg Roach    public function optionalInteger(string $parameter): ?int
2188d9c2b68SGreg Roach    {
2198d9c2b68SGreg Roach        $value = $this->parameters[$parameter] ?? null;
2208d9c2b68SGreg Roach
2218d9c2b68SGreg Roach        if (is_string($value) && ctype_digit($value)) {
2228d9c2b68SGreg Roach            $value = (int) $value;
2238d9c2b68SGreg Roach        } else {
2248d9c2b68SGreg Roach            $value = null;
2258d9c2b68SGreg Roach        }
2268d9c2b68SGreg Roach
2272b1a9a98SGreg Roach        $callback = static fn (?int $value, Closure $rule): ?int => $rule($value);
2282b1a9a98SGreg Roach
2292b1a9a98SGreg Roach        return array_reduce($this->rules, $callback, $value);
2308d9c2b68SGreg Roach    }
2318d9c2b68SGreg Roach
2328d9c2b68SGreg Roach    /**
2338d9c2b68SGreg Roach     * @param string $parameter
2348d9c2b68SGreg Roach     *
2358d9c2b68SGreg Roach     * @return string|null
2368d9c2b68SGreg Roach     */
237b55cbc6bSGreg Roach    public function optionalString(string $parameter): ?string
2388d9c2b68SGreg Roach    {
2398d9c2b68SGreg Roach        $value = $this->parameters[$parameter] ?? null;
2408d9c2b68SGreg Roach
2418d9c2b68SGreg Roach        if (!is_string($value)) {
2428d9c2b68SGreg Roach            $value = null;
2438d9c2b68SGreg Roach        }
2448d9c2b68SGreg Roach
2452b1a9a98SGreg Roach        $callback = static fn (?string $value, Closure $rule): ?string => $rule($value);
2462b1a9a98SGreg Roach
2472b1a9a98SGreg Roach        return array_reduce($this->rules, $callback, $value);
2488d9c2b68SGreg Roach    }
24981b729d3SGreg Roach
25081b729d3SGreg Roach    /**
25181b729d3SGreg Roach     * @param string    $parameter
252b55cbc6bSGreg Roach     * @param bool|null $default
253b55cbc6bSGreg Roach     *
254b55cbc6bSGreg Roach     * @return bool
255b55cbc6bSGreg Roach     */
256b55cbc6bSGreg Roach    public function boolean(string $parameter, bool $default = null): bool
257b55cbc6bSGreg Roach    {
258b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
259b55cbc6bSGreg Roach
260b55cbc6bSGreg Roach        if (in_array($value, ['1', true], true)) {
261b55cbc6bSGreg Roach            return true;
262b55cbc6bSGreg Roach        }
263b55cbc6bSGreg Roach
264b55cbc6bSGreg Roach        if (in_array($value, ['0', '', false], true)) {
265b55cbc6bSGreg Roach            return false;
266b55cbc6bSGreg Roach        }
267b55cbc6bSGreg Roach
268b55cbc6bSGreg Roach        if ($default === null) {
269b55cbc6bSGreg Roach            throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
270b55cbc6bSGreg Roach        }
271b55cbc6bSGreg Roach
272b55cbc6bSGreg Roach        return $default;
273b55cbc6bSGreg Roach    }
274b55cbc6bSGreg Roach
275b55cbc6bSGreg Roach    /**
276b55cbc6bSGreg Roach     * @param string $parameter
27781b729d3SGreg Roach     *
27881b729d3SGreg Roach     * @return array<string>
27981b729d3SGreg Roach     */
280b55cbc6bSGreg Roach    public function array(string $parameter): array
28181b729d3SGreg Roach    {
282b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
28381b729d3SGreg Roach
284*04b3bc82SGreg Roach        if (!is_array($value) && $value !== null) {
285*04b3bc82SGreg Roach            throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
286b55cbc6bSGreg Roach        }
287b55cbc6bSGreg Roach
288b55cbc6bSGreg Roach        $callback = static fn (?array $value, Closure $rule): ?array => $rule($value);
289b55cbc6bSGreg Roach
290b55cbc6bSGreg Roach        $value = array_reduce($this->rules, $callback, $value);
291b55cbc6bSGreg Roach        $value ??= [];
292b55cbc6bSGreg Roach
293b55cbc6bSGreg Roach        $check_utf8 = static function($v, $k) use ($parameter) {
294b55cbc6bSGreg Roach            if (is_string($k) && !preg_match('//u', $k) || is_string($v) && !preg_match('//u', $v)) {
2952b1a9a98SGreg Roach                throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
29681b729d3SGreg Roach            }
297b55cbc6bSGreg Roach        };
298b55cbc6bSGreg Roach
299b55cbc6bSGreg Roach        array_walk_recursive($value, $check_utf8);
30081b729d3SGreg Roach
3012b1a9a98SGreg Roach        return $value;
30281b729d3SGreg Roach    }
30381b729d3SGreg Roach
30481b729d3SGreg Roach    /**
30581b729d3SGreg Roach     * @param string   $parameter
306b55cbc6bSGreg Roach     * @param int|null $default
30781b729d3SGreg Roach     *
30881b729d3SGreg Roach     * @return int
30981b729d3SGreg Roach     */
310b55cbc6bSGreg Roach    public function integer(string $parameter, int $default = null): int
31181b729d3SGreg Roach    {
312b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
313b55cbc6bSGreg Roach
314b55cbc6bSGreg Roach        if (is_string($value) && ctype_digit($value)) {
315b55cbc6bSGreg Roach            $value = (int) $value;
316*04b3bc82SGreg Roach        } elseif (!is_int($value)) {
317b55cbc6bSGreg Roach            $value = null;
318b55cbc6bSGreg Roach        }
319b55cbc6bSGreg Roach
320b55cbc6bSGreg Roach        $callback = static fn (?int $value, Closure $rule): ?int => $rule($value);
321b55cbc6bSGreg Roach
322b55cbc6bSGreg Roach        $value = array_reduce($this->rules, $callback, $value);
323b55cbc6bSGreg Roach
324b55cbc6bSGreg Roach        $value ??= $default;
32581b729d3SGreg Roach
3262b1a9a98SGreg Roach        if ($value === null) {
3272b1a9a98SGreg Roach            throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
32881b729d3SGreg Roach        }
32981b729d3SGreg Roach
3302b1a9a98SGreg Roach        return $value;
33181b729d3SGreg Roach    }
33281b729d3SGreg Roach
33381b729d3SGreg Roach    /**
33481b729d3SGreg Roach     * @param string $parameter
33581b729d3SGreg Roach     *
336b55cbc6bSGreg Roach     * @return Route
337b55cbc6bSGreg Roach     */
338b55cbc6bSGreg Roach    public function route(string $parameter = 'route'): Route
339b55cbc6bSGreg Roach    {
340b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
341b55cbc6bSGreg Roach
342b55cbc6bSGreg Roach        if ($value instanceof Route) {
343b55cbc6bSGreg Roach            return $value;
344b55cbc6bSGreg Roach        }
345b55cbc6bSGreg Roach
346b55cbc6bSGreg Roach        throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
347b55cbc6bSGreg Roach    }
348b55cbc6bSGreg Roach
349b55cbc6bSGreg Roach    /**
350b55cbc6bSGreg Roach     * @param string      $parameter
351b55cbc6bSGreg Roach     * @param string|null $default
352b55cbc6bSGreg Roach     *
35381b729d3SGreg Roach     * @return string
35481b729d3SGreg Roach     */
355b55cbc6bSGreg Roach    public function string(string $parameter, string $default = null): string
35681b729d3SGreg Roach    {
357b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
35881b729d3SGreg Roach
359b55cbc6bSGreg Roach        if (!is_string($value)) {
360b55cbc6bSGreg Roach            $value = null;
361b55cbc6bSGreg Roach        }
362b55cbc6bSGreg Roach
363b55cbc6bSGreg Roach        $callback = static fn (?string $value, Closure $rule): ?string => $rule($value);
364b55cbc6bSGreg Roach
365b55cbc6bSGreg Roach        $value =  array_reduce($this->rules, $callback, $value);
366b55cbc6bSGreg Roach        $value ??= $default;
367b55cbc6bSGreg Roach
368b55cbc6bSGreg Roach        if ($value === null || preg_match('//u', $value) !== 1) {
3692b1a9a98SGreg Roach            throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
37081b729d3SGreg Roach        }
37181b729d3SGreg Roach
3722b1a9a98SGreg Roach        return $value;
37381b729d3SGreg Roach    }
374b55cbc6bSGreg Roach
375b55cbc6bSGreg Roach    /**
376b55cbc6bSGreg Roach     * @param string $parameter
377b55cbc6bSGreg Roach     *
378b55cbc6bSGreg Roach     * @return Tree
379b55cbc6bSGreg Roach     */
380b55cbc6bSGreg Roach    public function tree(string $parameter = 'tree'): Tree
381b55cbc6bSGreg Roach    {
382b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
383b55cbc6bSGreg Roach
384b55cbc6bSGreg Roach        if ($value instanceof Tree) {
385b55cbc6bSGreg Roach            return $value;
386b55cbc6bSGreg Roach        }
387b55cbc6bSGreg Roach
388b55cbc6bSGreg Roach        throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
389b55cbc6bSGreg Roach    }
390b55cbc6bSGreg Roach
391b55cbc6bSGreg Roach    /**
392b55cbc6bSGreg Roach     * @param string $parameter
393b55cbc6bSGreg Roach     *
394b55cbc6bSGreg Roach     * @return Tree|null
395b55cbc6bSGreg Roach     */
396b55cbc6bSGreg Roach    public function treeOptional(string $parameter = 'tree'): ?Tree
397b55cbc6bSGreg Roach    {
398b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
399b55cbc6bSGreg Roach
400b55cbc6bSGreg Roach        if ($value === null || $value instanceof Tree) {
401b55cbc6bSGreg Roach            return $value;
402b55cbc6bSGreg Roach        }
403b55cbc6bSGreg Roach
404b55cbc6bSGreg Roach        throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
405b55cbc6bSGreg Roach    }
406b55cbc6bSGreg Roach
407b55cbc6bSGreg Roach    /**
408b55cbc6bSGreg Roach     * @param string $parameter
409b55cbc6bSGreg Roach     *
410b55cbc6bSGreg Roach     * @return UserInterface
411b55cbc6bSGreg Roach     */
412b55cbc6bSGreg Roach    public function user(string $parameter = 'user'): UserInterface
413b55cbc6bSGreg Roach    {
414b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
415b55cbc6bSGreg Roach
416b55cbc6bSGreg Roach        if ($value instanceof UserInterface) {
417b55cbc6bSGreg Roach            return $value;
418b55cbc6bSGreg Roach        }
419b55cbc6bSGreg Roach
420b55cbc6bSGreg Roach        throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
421b55cbc6bSGreg Roach    }
4228d9c2b68SGreg Roach}
423