xref: /webtrees/app/Validator.php (revision 1ff45046fabc22237b5d0d8e489c96f031fc598d)
18d9c2b68SGreg Roach<?php
28d9c2b68SGreg Roach
38d9c2b68SGreg Roach/**
48d9c2b68SGreg Roach * webtrees: online genealogy
5d11be702SGreg Roach * Copyright (C) 2023 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 Psr\Http\Message\ServerRequestInterface;
278d9c2b68SGreg Roach
288d9c2b68SGreg Roachuse function array_reduce;
29748dbe15SGreg Roachuse function array_walk_recursive;
308d9c2b68SGreg Roachuse function ctype_digit;
31d2d056daSGreg Roachuse function in_array;
328d9c2b68SGreg Roachuse function is_array;
338d9c2b68SGreg Roachuse function is_int;
348d9c2b68SGreg Roachuse function is_string;
358d9c2b68SGreg Roachuse function parse_url;
3681b729d3SGreg Roachuse function preg_match;
378d9c2b68SGreg Roachuse function str_starts_with;
3865625b93SGreg Roachuse function substr;
398d9c2b68SGreg Roach
408d9c2b68SGreg Roach/**
418d9c2b68SGreg Roach * Validate a parameter from an HTTP request
428d9c2b68SGreg Roach */
438d9c2b68SGreg Roachclass Validator
448d9c2b68SGreg Roach{
4504b3bc82SGreg Roach    /** @var array<int|string|Tree|UserInterface|array<int|string>> */
468d9c2b68SGreg Roach    private array $parameters;
478d9c2b68SGreg Roach
48f507cef9SGreg Roach    private ServerRequestInterface $request;
49f507cef9SGreg Roach
508d9c2b68SGreg Roach    /** @var array<Closure> */
518d9c2b68SGreg Roach    private array $rules = [];
528d9c2b68SGreg Roach
538d9c2b68SGreg Roach    /**
5404b3bc82SGreg Roach     * @param array<int|string|Tree|UserInterface|array<int|string>> $parameters
55f507cef9SGreg Roach     * @param ServerRequestInterface                                 $request
56a5f003cfSGreg Roach     * @param string                                                 $encoding
578d9c2b68SGreg Roach     */
58a5f003cfSGreg Roach    private function __construct(array $parameters, ServerRequestInterface $request, string $encoding)
598d9c2b68SGreg Roach    {
60a5f003cfSGreg Roach        if ($encoding === 'UTF-8') {
61748dbe15SGreg Roach            // All keys and values must be valid UTF-8
62748dbe15SGreg Roach            $check_utf8 = static function ($value, $key): void {
63748dbe15SGreg Roach                if (is_string($key) && preg_match('//u', $key) !== 1) {
64748dbe15SGreg Roach                    throw new HttpBadRequestException('Invalid UTF-8 characters in request');
65748dbe15SGreg Roach                }
66748dbe15SGreg Roach                if (is_string($value) && preg_match('//u', $value) !== 1) {
67748dbe15SGreg Roach                    throw new HttpBadRequestException('Invalid UTF-8 characters in request');
68748dbe15SGreg Roach                }
69748dbe15SGreg Roach            };
70748dbe15SGreg Roach
71748dbe15SGreg Roach            array_walk_recursive($parameters, $check_utf8);
72a5f003cfSGreg Roach        }
73748dbe15SGreg Roach
748d9c2b68SGreg Roach        $this->parameters = $parameters;
75f507cef9SGreg Roach        $this->request    = $request;
768d9c2b68SGreg Roach    }
778d9c2b68SGreg Roach
788d9c2b68SGreg Roach    /**
798d9c2b68SGreg Roach     * @param ServerRequestInterface $request
808d9c2b68SGreg Roach     *
818d9c2b68SGreg Roach     * @return self
828d9c2b68SGreg Roach     */
83b55cbc6bSGreg Roach    public static function attributes(ServerRequestInterface $request): self
84b55cbc6bSGreg Roach    {
85a5f003cfSGreg Roach        return new self($request->getAttributes(), $request, 'UTF-8');
86b55cbc6bSGreg Roach    }
87b55cbc6bSGreg Roach
88b55cbc6bSGreg Roach    /**
89b55cbc6bSGreg Roach     * @param ServerRequestInterface $request
90b55cbc6bSGreg Roach     *
91b55cbc6bSGreg Roach     * @return self
92b55cbc6bSGreg Roach     */
938d9c2b68SGreg Roach    public static function parsedBody(ServerRequestInterface $request): self
948d9c2b68SGreg Roach    {
95a5f003cfSGreg Roach        return new self((array) $request->getParsedBody(), $request, 'UTF-8');
968d9c2b68SGreg Roach    }
978d9c2b68SGreg Roach
988d9c2b68SGreg Roach    /**
998d9c2b68SGreg Roach     * @param ServerRequestInterface $request
1008d9c2b68SGreg Roach     *
1018d9c2b68SGreg Roach     * @return self
1028d9c2b68SGreg Roach     */
1038d9c2b68SGreg Roach    public static function queryParams(ServerRequestInterface $request): self
1048d9c2b68SGreg Roach    {
105a5f003cfSGreg Roach        return new self($request->getQueryParams(), $request, 'UTF-8');
1068d9c2b68SGreg Roach    }
1078d9c2b68SGreg Roach
1088d9c2b68SGreg Roach    /**
109b55cbc6bSGreg Roach     * @param ServerRequestInterface $request
110b55cbc6bSGreg Roach     *
111b55cbc6bSGreg Roach     * @return self
112b55cbc6bSGreg Roach     */
113b55cbc6bSGreg Roach    public static function serverParams(ServerRequestInterface $request): self
114b55cbc6bSGreg Roach    {
115a5f003cfSGreg Roach        // Headers should be ASCII.
116a5f003cfSGreg Roach        // However, we cannot enforce this as some servers add GEOIP headers with non-ASCII placenames.
117a5f003cfSGreg Roach        return new self($request->getServerParams(), $request, 'ASCII');
118b55cbc6bSGreg Roach    }
119b55cbc6bSGreg Roach
120b55cbc6bSGreg Roach    /**
1218d9c2b68SGreg Roach     * @param int $minimum
1228d9c2b68SGreg Roach     * @param int $maximum
1238d9c2b68SGreg Roach     *
1248d9c2b68SGreg Roach     * @return self
1258d9c2b68SGreg Roach     */
1268d9c2b68SGreg Roach    public function isBetween(int $minimum, int $maximum): self
1278d9c2b68SGreg Roach    {
128*1ff45046SGreg Roach        $this->rules[] = static function (int|null $value) use ($minimum, $maximum): int|null {
1292b1a9a98SGreg Roach            if (is_int($value) && $value >= $minimum && $value <= $maximum) {
1308d9c2b68SGreg Roach                return $value;
1318d9c2b68SGreg Roach            }
1328d9c2b68SGreg Roach
13381b729d3SGreg Roach            return null;
1348d9c2b68SGreg Roach        };
1358d9c2b68SGreg Roach
1368d9c2b68SGreg Roach        return $this;
1378d9c2b68SGreg Roach    }
1388d9c2b68SGreg Roach
13981b729d3SGreg Roach    /**
1400acf1b4bSGreg Roach     * @param array<int|string,int|string> $values
1411c6adce8SGreg Roach     *
1426612c384SGreg Roach     * @return self
1431c6adce8SGreg Roach     */
1441c6adce8SGreg Roach    public function isInArray(array $values): self
1451c6adce8SGreg Roach    {
146ebe785f4SGreg Roach        $this->rules[] = static fn (int|string|null $value): int|string|null => in_array($value, $values, true) ? $value : null;
1471c6adce8SGreg Roach
1481c6adce8SGreg Roach        return $this;
1491c6adce8SGreg Roach    }
150c3bff7b4SGreg Roach
151c3bff7b4SGreg Roach    /**
1520acf1b4bSGreg Roach     * @param array<int|string,int|string> $values
153158900c2SGreg Roach     *
1546612c384SGreg Roach     * @return self
155158900c2SGreg Roach     */
156158900c2SGreg Roach    public function isInArrayKeys(array $values): self
157158900c2SGreg Roach    {
158158900c2SGreg Roach        return $this->isInArray(array_keys($values));
159158900c2SGreg Roach    }
160158900c2SGreg Roach
161158900c2SGreg Roach    /**
1626612c384SGreg Roach     * @return self
163c3bff7b4SGreg Roach     */
164c3bff7b4SGreg Roach    public function isNotEmpty(): self
165c3bff7b4SGreg Roach    {
166*1ff45046SGreg Roach        $this->rules[] = static fn (string|null $value): string|null => $value !== null && $value !== '' ? $value : null;
167c3bff7b4SGreg Roach
168c3bff7b4SGreg Roach        return $this;
169c3bff7b4SGreg Roach    }
170c3bff7b4SGreg Roach
1711c6adce8SGreg Roach    /**
1726612c384SGreg Roach     * @return self
17381b729d3SGreg Roach     */
174f507cef9SGreg Roach    public function isLocalUrl(): self
1758d9c2b68SGreg Roach    {
176f507cef9SGreg Roach        $base_url = $this->request->getAttribute('base_url', '');
177f507cef9SGreg Roach
178*1ff45046SGreg Roach        $this->rules[] = static function (string|null $value) use ($base_url): string|null {
179c3bff7b4SGreg Roach            if ($value !== null) {
1808d9c2b68SGreg Roach                $value_info    = parse_url($value);
1818d9c2b68SGreg Roach                $base_url_info = parse_url($base_url);
1828d9c2b68SGreg Roach
183f507cef9SGreg Roach                if (is_array($value_info) && is_array($base_url_info)) {
1848d9c2b68SGreg Roach                    $scheme_ok = ($value_info['scheme'] ?? 'http') === ($base_url_info['scheme'] ?? 'http');
1858d9c2b68SGreg Roach                    $host_ok   = ($value_info['host'] ?? '') === ($base_url_info['host'] ?? '');
1868d9c2b68SGreg Roach                    $port_ok   = ($value_info['port'] ?? '') === ($base_url_info['port'] ?? '');
1878d9c2b68SGreg Roach                    $user_ok   = ($value_info['user'] ?? '') === ($base_url_info['user'] ?? '');
1888d9c2b68SGreg Roach                    $path_ok   = str_starts_with($value_info['path'] ?? '/', $base_url_info['path'] ?? '/');
1898d9c2b68SGreg Roach
1908d9c2b68SGreg Roach                    if ($scheme_ok && $host_ok && $port_ok && $user_ok && $path_ok) {
1918d9c2b68SGreg Roach                        return $value;
1928d9c2b68SGreg Roach                    }
1938d9c2b68SGreg Roach                }
1948d9c2b68SGreg Roach            }
1958d9c2b68SGreg Roach
1962b1a9a98SGreg Roach            return null;
19781b729d3SGreg Roach        };
19881b729d3SGreg Roach
19981b729d3SGreg Roach        return $this;
20081b729d3SGreg Roach    }
20181b729d3SGreg Roach
20281b729d3SGreg Roach    /**
2036612c384SGreg Roach     * @return self
20481b729d3SGreg Roach     */
205b55cbc6bSGreg Roach    public function isTag(): self
206b55cbc6bSGreg Roach    {
207*1ff45046SGreg Roach        $this->rules[] = static function (string|null $value): string|null {
208c3bff7b4SGreg Roach            if ($value !== null && preg_match('/^' . Gedcom::REGEX_TAG . '$/', $value) === 1) {
209b55cbc6bSGreg Roach                return $value;
210b55cbc6bSGreg Roach            }
211b55cbc6bSGreg Roach
212b55cbc6bSGreg Roach            return null;
213b55cbc6bSGreg Roach        };
214b55cbc6bSGreg Roach
215b55cbc6bSGreg Roach        return $this;
216b55cbc6bSGreg Roach    }
217b55cbc6bSGreg Roach
218b55cbc6bSGreg Roach    /**
2196612c384SGreg Roach     * @return self
220b55cbc6bSGreg Roach     */
22181b729d3SGreg Roach    public function isXref(): self
22281b729d3SGreg Roach    {
223748dbe15SGreg Roach        $this->rules[] = static function ($value) {
224748dbe15SGreg Roach            if (is_string($value) && preg_match('/^' . Gedcom::REGEX_XREF . '$/', $value) === 1) {
22581b729d3SGreg Roach                return $value;
22681b729d3SGreg Roach            }
22781b729d3SGreg Roach
228748dbe15SGreg Roach            if (is_array($value)) {
229a5f003cfSGreg Roach                foreach ($value as $v) {
230a5f003cfSGreg Roach                    if (!is_string($v) || preg_match('/^' . Gedcom::REGEX_XREF . '$/', $v) !== 1) {
231a5f003cfSGreg Roach                        return null;
232a5f003cfSGreg Roach                    }
233a5f003cfSGreg Roach                }
234a5f003cfSGreg Roach
235a5f003cfSGreg Roach                return $value;
236748dbe15SGreg Roach            }
237748dbe15SGreg Roach
23881b729d3SGreg Roach            return null;
2398d9c2b68SGreg Roach        };
2408d9c2b68SGreg Roach
2418d9c2b68SGreg Roach        return $this;
2428d9c2b68SGreg Roach    }
2438d9c2b68SGreg Roach
2448d9c2b68SGreg Roach    /**
2458d9c2b68SGreg Roach     * @param string    $parameter
246b55cbc6bSGreg Roach     * @param bool|null $default
247b55cbc6bSGreg Roach     *
248b55cbc6bSGreg Roach     * @return bool
249b55cbc6bSGreg Roach     */
2502c6f1bd5SGreg Roach    public function boolean(string $parameter, bool|null $default = null): bool
251b55cbc6bSGreg Roach    {
252b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
253b55cbc6bSGreg Roach
25446b31fc1SGreg Roach        if (in_array($value, ['1', 'on', true], true)) {
255b55cbc6bSGreg Roach            return true;
256b55cbc6bSGreg Roach        }
257b55cbc6bSGreg Roach
258b55cbc6bSGreg Roach        if (in_array($value, ['0', '', false], true)) {
259b55cbc6bSGreg Roach            return false;
260b55cbc6bSGreg Roach        }
261b55cbc6bSGreg Roach
262b55cbc6bSGreg Roach        if ($default === null) {
263b55cbc6bSGreg Roach            throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
264b55cbc6bSGreg Roach        }
265b55cbc6bSGreg Roach
266b55cbc6bSGreg Roach        return $default;
267b55cbc6bSGreg Roach    }
268b55cbc6bSGreg Roach
269b55cbc6bSGreg Roach    /**
270b55cbc6bSGreg Roach     * @param string $parameter
27181b729d3SGreg Roach     *
27281b729d3SGreg Roach     * @return array<string>
27381b729d3SGreg Roach     */
274b55cbc6bSGreg Roach    public function array(string $parameter): array
27581b729d3SGreg Roach    {
276b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
27781b729d3SGreg Roach
27804b3bc82SGreg Roach        if (!is_array($value) && $value !== null) {
27904b3bc82SGreg Roach            throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
280b55cbc6bSGreg Roach        }
281b55cbc6bSGreg Roach
282*1ff45046SGreg Roach        $callback = static fn (array|null $value, Closure $rule): array|null => $rule($value);
283b55cbc6bSGreg Roach
284748dbe15SGreg Roach        return array_reduce($this->rules, $callback, $value) ?? [];
28581b729d3SGreg Roach    }
28681b729d3SGreg Roach
28781b729d3SGreg Roach    /**
28881b729d3SGreg Roach     * @param string   $parameter
289770da671SGreg Roach     * @param float|null $default
290770da671SGreg Roach     *
291770da671SGreg Roach     * @return float
292770da671SGreg Roach     */
2932c6f1bd5SGreg Roach    public function float(string $parameter, float|null $default = null): float
294770da671SGreg Roach    {
295770da671SGreg Roach        $value = $this->parameters[$parameter] ?? null;
296770da671SGreg Roach
297770da671SGreg Roach        if (is_numeric($value)) {
298770da671SGreg Roach            $value = (float) $value;
299770da671SGreg Roach        } else {
300770da671SGreg Roach            $value = null;
301770da671SGreg Roach        }
302770da671SGreg Roach
303*1ff45046SGreg Roach        $callback = static fn (?float $value, Closure $rule): float|null => $rule($value);
304770da671SGreg Roach
305770da671SGreg Roach        $value = array_reduce($this->rules, $callback, $value) ?? $default;
306770da671SGreg Roach
307770da671SGreg Roach        if ($value === null) {
308770da671SGreg Roach            throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
309770da671SGreg Roach        }
310770da671SGreg Roach
311770da671SGreg Roach        return $value;
312770da671SGreg Roach    }
313770da671SGreg Roach
314770da671SGreg Roach    /**
315770da671SGreg Roach     * @param string   $parameter
316b55cbc6bSGreg Roach     * @param int|null $default
31781b729d3SGreg Roach     *
31881b729d3SGreg Roach     * @return int
31981b729d3SGreg Roach     */
3202c6f1bd5SGreg Roach    public function integer(string $parameter, int|null $default = null): int
32181b729d3SGreg Roach    {
322b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
323b55cbc6bSGreg Roach
32465625b93SGreg Roach        if (is_string($value)) {
32565625b93SGreg Roach            if (ctype_digit($value)) {
326b55cbc6bSGreg Roach                $value = (int) $value;
32765625b93SGreg Roach            } elseif (str_starts_with($value, '-') && ctype_digit(substr($value, 1))) {
32865625b93SGreg Roach                $value = (int) $value;
32965625b93SGreg Roach            }
33065625b93SGreg Roach        }
33165625b93SGreg Roach
33265625b93SGreg Roach        if (!is_int($value)) {
333b55cbc6bSGreg Roach            $value = null;
334b55cbc6bSGreg Roach        }
335b55cbc6bSGreg Roach
336*1ff45046SGreg Roach        $callback = static fn (int|null $value, Closure $rule): int|null => $rule($value);
337b55cbc6bSGreg Roach
338748dbe15SGreg Roach        $value = array_reduce($this->rules, $callback, $value) ?? $default;
33981b729d3SGreg Roach
3402b1a9a98SGreg Roach        if ($value === null) {
3412b1a9a98SGreg Roach            throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
34281b729d3SGreg Roach        }
34381b729d3SGreg Roach
3442b1a9a98SGreg Roach        return $value;
34581b729d3SGreg Roach    }
34681b729d3SGreg Roach
34781b729d3SGreg Roach    /**
34881b729d3SGreg Roach     * @param string $parameter
34981b729d3SGreg Roach     *
350b55cbc6bSGreg Roach     * @return Route
351b55cbc6bSGreg Roach     */
352b55cbc6bSGreg Roach    public function route(string $parameter = 'route'): Route
353b55cbc6bSGreg Roach    {
354b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
355b55cbc6bSGreg Roach
356b55cbc6bSGreg Roach        if ($value instanceof Route) {
357b55cbc6bSGreg Roach            return $value;
358b55cbc6bSGreg Roach        }
359b55cbc6bSGreg Roach
360b55cbc6bSGreg Roach        throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
361b55cbc6bSGreg Roach    }
362b55cbc6bSGreg Roach
363b55cbc6bSGreg Roach    /**
364b55cbc6bSGreg Roach     * @param string      $parameter
365b55cbc6bSGreg Roach     * @param string|null $default
366b55cbc6bSGreg Roach     *
36781b729d3SGreg Roach     * @return string
36881b729d3SGreg Roach     */
3692c6f1bd5SGreg Roach    public function string(string $parameter, string|null $default = null): string
37081b729d3SGreg Roach    {
371b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
37281b729d3SGreg Roach
373b55cbc6bSGreg Roach        if (!is_string($value)) {
374b55cbc6bSGreg Roach            $value = null;
375b55cbc6bSGreg Roach        }
376b55cbc6bSGreg Roach
377*1ff45046SGreg Roach        $callback = static fn (string|null $value, Closure $rule): string|null => $rule($value);
378b55cbc6bSGreg Roach
379748dbe15SGreg Roach        $value =  array_reduce($this->rules, $callback, $value) ?? $default;
380b55cbc6bSGreg Roach
381748dbe15SGreg Roach        if ($value === null) {
3822b1a9a98SGreg Roach            throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
38381b729d3SGreg Roach        }
38481b729d3SGreg Roach
3852b1a9a98SGreg Roach        return $value;
38681b729d3SGreg Roach    }
387b55cbc6bSGreg Roach
388b55cbc6bSGreg Roach    /**
389b55cbc6bSGreg Roach     * @param string $parameter
390b55cbc6bSGreg Roach     *
391b55cbc6bSGreg Roach     * @return Tree
392b55cbc6bSGreg Roach     */
393b55cbc6bSGreg Roach    public function tree(string $parameter = 'tree'): Tree
394b55cbc6bSGreg Roach    {
395b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
396b55cbc6bSGreg Roach
397b55cbc6bSGreg Roach        if ($value instanceof Tree) {
398b55cbc6bSGreg Roach            return $value;
399b55cbc6bSGreg Roach        }
400b55cbc6bSGreg Roach
401b55cbc6bSGreg Roach        throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
402b55cbc6bSGreg Roach    }
403b55cbc6bSGreg Roach
404*1ff45046SGreg Roach    public function treeOptional(string $parameter = 'tree'): Tree|null
405b55cbc6bSGreg Roach    {
406b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
407b55cbc6bSGreg Roach
408b55cbc6bSGreg Roach        if ($value === null || $value instanceof Tree) {
409b55cbc6bSGreg Roach            return $value;
410b55cbc6bSGreg Roach        }
411b55cbc6bSGreg Roach
412b55cbc6bSGreg Roach        throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
413b55cbc6bSGreg Roach    }
414b55cbc6bSGreg Roach
415b55cbc6bSGreg Roach    public function user(string $parameter = 'user'): UserInterface
416b55cbc6bSGreg Roach    {
417b55cbc6bSGreg Roach        $value = $this->parameters[$parameter] ?? null;
418b55cbc6bSGreg Roach
419b55cbc6bSGreg Roach        if ($value instanceof UserInterface) {
420b55cbc6bSGreg Roach            return $value;
421b55cbc6bSGreg Roach        }
422b55cbc6bSGreg Roach
423b55cbc6bSGreg Roach        throw new HttpBadRequestException(I18N::translate('The parameter “%s” is missing.', $parameter));
424b55cbc6bSGreg Roach    }
4258d9c2b68SGreg Roach}
426