Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
80.12% |
133 / 166 |
|
59.32% |
35 / 59 |
CRAP | |
0.00% |
0 / 1 |
Form | |
80.12% |
133 / 166 |
|
59.32% |
35 / 59 |
188.97 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
expect | |
89.29% |
25 / 28 |
|
0.00% |
0 / 1 |
8.08 | |||
expectBool | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
requireBool | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expectBoolArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
requireBoolArray | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expectTrue | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
requireTrue | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expectTrueArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
requireTrueArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
expectEmail | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
requireEmail | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expectEmailArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
requireEmailArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
expectFloat | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
requireFloat | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expectFloatArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
requireFloatArray | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expectInt | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
requireInt | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expectIntArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
requireIntArray | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expectIp | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
requireIp | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expectIpArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
requireIpArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
expectRegex | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
requireRegex | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expectRegexArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
requireRegexArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
expectUrl | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
requireUrl | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expectUrlArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
requireUrlArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
expectString | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
requireString | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expectStringArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
requireStringArray | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
expectAnything | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
requireAnything | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
expectAnythingArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
requireAnythingArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
expectInArray | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
requireInArray | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
expectInArrayArray | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
requireInArrayArray | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
expectDateTime | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
6.03 | |||
requireDateTime | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
validate | |
91.89% |
34 / 37 |
|
0.00% |
0 / 1 |
21.24 | |||
customValidationHook | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
get | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
3.58 | |||
getValues | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getErrors | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasErrors | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
urlEncode | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
qsMerge | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
qsRemove | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
required | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
wantArray | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * @section LICENSE |
4 | * This file is part of Wikimedia Slim application library |
5 | * |
6 | * Wikimedia Slim application library is free software: you can |
7 | * redistribute it and/or modify it under the terms of the GNU General Public |
8 | * License as published by the Free Software Foundation, either version 3 of |
9 | * the License, or (at your option) any later version. |
10 | * |
11 | * Wikimedia Slim application library is distributed in the hope that it |
12 | * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty |
13 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License along |
17 | * with Wikimedia Grants Review application. If not, see |
18 | * <http://www.gnu.org/licenses/>. |
19 | * |
20 | * @file |
21 | * @copyright © 2015 Bryan Davis, Wikimedia Foundation and contributors. |
22 | */ |
23 | |
24 | namespace Wikimedia\Slimapp; |
25 | |
26 | use DateTime; |
27 | use Exception; |
28 | use InvalidArgumentException; |
29 | use Psr\Log\LoggerInterface; |
30 | use Psr\Log\NullLogger; |
31 | use const FILTER_CALLBACK; |
32 | use const FILTER_REQUIRE_ARRAY; |
33 | use const FILTER_UNSAFE_RAW; |
34 | use const FILTER_VALIDATE_BOOLEAN; |
35 | use const FILTER_VALIDATE_EMAIL; |
36 | use const FILTER_VALIDATE_FLOAT; |
37 | use const FILTER_VALIDATE_INT; |
38 | use const FILTER_VALIDATE_IP; |
39 | use const FILTER_VALIDATE_REGEXP; |
40 | use const FILTER_VALIDATE_URL; |
41 | |
42 | /** |
43 | * Collect and validate user input. |
44 | * |
45 | * Wraps PHP's built-in filter_var_array function to collect GET or POST input |
46 | * as a collection of sanitized data. |
47 | * |
48 | * @author Bryan Davis <bd808@wikimedia.org> |
49 | * @copyright © 2015 Bryan Davis, Wikimedia Foundation and contributors. |
50 | */ |
51 | class Form { |
52 | |
53 | /** |
54 | * @var LoggerInterface |
55 | */ |
56 | protected $logger; |
57 | |
58 | /** |
59 | * Input parameters to expect. |
60 | * @var array |
61 | */ |
62 | protected $params = []; |
63 | |
64 | /** |
65 | * Values received after filtering. |
66 | * @var array |
67 | */ |
68 | protected $values = []; |
69 | |
70 | /** |
71 | * Fields with errors. |
72 | * @var array |
73 | */ |
74 | protected $errors = []; |
75 | |
76 | /** |
77 | * @param LoggerInterface $logger Log channel |
78 | */ |
79 | public function __construct( $logger = null ) { |
80 | $this->logger = $logger ?: new NullLogger(); |
81 | } |
82 | |
83 | /** |
84 | * Add an input expectation. |
85 | * |
86 | * Allowed options: |
87 | * - default: Default value for missing input |
88 | * - flags: Flags to pass to filter_var_array |
89 | * - required: Is this input required? |
90 | * - validate: Callable to perform additional validation |
91 | * - callback: Callable for \FILTER_CALLBACK validation |
92 | * |
93 | * @param string $name Parameter to expect |
94 | * @param int $filter Validation filter(s) to apply |
95 | * @param array $options Validation options |
96 | * @return Form Self, for message chaining |
97 | */ |
98 | public function expect( $name, $filter, $options = null ) { |
99 | $options = ( is_array( $options ) ) ? $options : []; |
100 | $flags = null; |
101 | $required = false; |
102 | $validate = null; |
103 | |
104 | if ( isset( $options['flags'] ) ) { |
105 | $flags = $options['flags']; |
106 | unset( $options['flags'] ); |
107 | } |
108 | |
109 | if ( isset( $options['required'] ) ) { |
110 | $required = $options['required']; |
111 | unset( $options['required'] ); |
112 | } |
113 | |
114 | if ( isset( $options['validate'] ) ) { |
115 | $validate = $options['validate']; |
116 | unset( $options['validate'] ); |
117 | } |
118 | |
119 | if ( $filter === FILTER_CALLBACK ) { |
120 | if ( !isset( $options['callback'] ) || |
121 | !is_callable( $options['callback'] ) |
122 | ) { |
123 | throw new InvalidArgumentException( |
124 | 'FILTER_CALLBACK requires a valid callback.' |
125 | ); |
126 | } |
127 | $options = $options['callback']; |
128 | } |
129 | |
130 | $this->params[$name] = [ |
131 | 'filter' => $filter, |
132 | 'flags' => $flags, |
133 | 'options' => $options, |
134 | 'required' => $required, |
135 | 'validate' => $validate, |
136 | ]; |
137 | |
138 | return $this; |
139 | } |
140 | |
141 | /** |
142 | * @param string $name Parameter to expect |
143 | * @param array $options Additional options |
144 | * @return Form Self, for message chaining |
145 | */ |
146 | public function expectBool( $name, $options = null ) { |
147 | $options = ( is_array( $options ) ) ? $options : []; |
148 | if ( !isset( $options['default'] ) ) { |
149 | $options['default'] = false; |
150 | } |
151 | return $this->expect( $name, FILTER_VALIDATE_BOOLEAN, $options ); |
152 | } |
153 | |
154 | /** |
155 | * @param string $name Parameter to require |
156 | * @param array $options Additional options |
157 | * @return Form Self, for message chaining |
158 | */ |
159 | public function requireBool( $name, $options = null ) { |
160 | return $this->expectBool( $name, self::required( $options ) ); |
161 | } |
162 | |
163 | /** |
164 | * @param string $name Parameter to expect |
165 | * @param array $options Additional options |
166 | * @return Form Self, for message chaining |
167 | */ |
168 | public function expectBoolArray( $name, $options = null ) { |
169 | return $this->expectBool( $name, self::wantArray( $options ) ); |
170 | } |
171 | |
172 | /** |
173 | * @param string $name Parameter to require |
174 | * @param array $options Additional options |
175 | * @return Form Self, for message chaining |
176 | */ |
177 | public function requireBoolArray( $name, $options = null ) { |
178 | return $this->requireBool( $name, self::wantArray( $options ) ); |
179 | } |
180 | |
181 | /** |
182 | * @param string $name Parameter to expect |
183 | * @param array $options Additional options |
184 | * @return Form Self, for message chaining |
185 | */ |
186 | public function expectTrue( $name, $options = null ) { |
187 | $options = ( is_array( $options ) ) ? $options : []; |
188 | $options['validate'] = static function ( $v ) { |
189 | return (bool)$v; |
190 | }; |
191 | return $this->expectBool( $name, $options ); |
192 | } |
193 | |
194 | /** |
195 | * @param string $name Parameter to require |
196 | * @param array $options Additional options |
197 | * @return Form Self, for message chaining |
198 | */ |
199 | public function requireTrue( $name, $options = null ) { |
200 | return $this->expectTrue( $name, self::required( $options ) ); |
201 | } |
202 | |
203 | /** |
204 | * @param string $name Parameter to expect |
205 | * @param array $options Additional options |
206 | * @return Form Self, for message chaining |
207 | */ |
208 | public function expectTrueArray( $name, $options = null ) { |
209 | return $this->expectTrue( $name, self::wantArray( $options ) ); |
210 | } |
211 | |
212 | /** |
213 | * @param string $name Parameter to require |
214 | * @param array $options Additional options |
215 | * @return Form Self, for message chaining |
216 | */ |
217 | public function requireTrueArray( $name, $options = null ) { |
218 | return $this->requireTrue( $name, self::wantArray( $options ) ); |
219 | } |
220 | |
221 | /** |
222 | * @param string $name Parameter to expect |
223 | * @param array $options Additional options |
224 | * @return Form Self, for message chaining |
225 | */ |
226 | public function expectEmail( $name, $options = null ) { |
227 | return $this->expect( $name, FILTER_VALIDATE_EMAIL, $options ); |
228 | } |
229 | |
230 | /** |
231 | * @param string $name Parameter to require |
232 | * @param array $options Additional options |
233 | * @return Form Self, for message chaining |
234 | */ |
235 | public function requireEmail( $name, $options = null ) { |
236 | return $this->expectEmail( $name, self::required( $options ) ); |
237 | } |
238 | |
239 | /** |
240 | * @param string $name Parameter to expect |
241 | * @param array $options Additional options |
242 | * @return Form Self, for message chaining |
243 | */ |
244 | public function expectEmailArray( $name, $options = null ) { |
245 | return $this->expectEmail( $name, self::wantArray( $options ) ); |
246 | } |
247 | |
248 | /** |
249 | * @param string $name Parameter to require |
250 | * @param array $options Additional options |
251 | * @return Form Self, for message chaining |
252 | */ |
253 | public function requireEmailArray( $name, $options = null ) { |
254 | return $this->requireEmail( $name, self::wantArray( $options ) ); |
255 | } |
256 | |
257 | /** |
258 | * @param string $name Parameter to expect |
259 | * @param array $options Additional options |
260 | * @return Form Self, for message chaining |
261 | */ |
262 | public function expectFloat( $name, $options = null ) { |
263 | return $this->expect( $name, FILTER_VALIDATE_FLOAT, $options ); |
264 | } |
265 | |
266 | /** |
267 | * @param string $name Parameter to require |
268 | * @param array $options Additional options |
269 | * @return Form Self, for message chaining |
270 | */ |
271 | public function requireFloat( $name, $options = null ) { |
272 | return $this->expectFloat( $name, self::required( $options ) ); |
273 | } |
274 | |
275 | /** |
276 | * @param string $name Parameter to expect |
277 | * @param array $options Additional options |
278 | * @return Form Self, for message chaining |
279 | */ |
280 | public function expectFloatArray( $name, $options = null ) { |
281 | return $this->expectFloat( $name, self::wantArray( $options ) ); |
282 | } |
283 | |
284 | /** |
285 | * @param string $name Parameter to require |
286 | * @param array $options Additional options |
287 | * @return Form Self, for message chaining |
288 | */ |
289 | public function requireFloatArray( $name, $options = null ) { |
290 | return $this->requireFloat( $name, self::wantArray( $options ) ); |
291 | } |
292 | |
293 | /** |
294 | * @param string $name Parameter to expect |
295 | * @param array $options Additional options |
296 | * @return Form Self, for message chaining |
297 | */ |
298 | public function expectInt( $name, $options = null ) { |
299 | return $this->expect( $name, FILTER_VALIDATE_INT, $options ); |
300 | } |
301 | |
302 | /** |
303 | * @param string $name Parameter to require |
304 | * @param array $options Additional options |
305 | * @return Form Self, for message chaining |
306 | */ |
307 | public function requireInt( $name, $options = null ) { |
308 | return $this->expectInt( $name, self::required( $options ) ); |
309 | } |
310 | |
311 | /** |
312 | * @param string $name Parameter to expect |
313 | * @param array $options Additional options |
314 | * @return Form Self, for message chaining |
315 | */ |
316 | public function expectIntArray( $name, $options = null ) { |
317 | return $this->expectInt( $name, self::wantArray( $options ) ); |
318 | } |
319 | |
320 | /** |
321 | * @param string $name Parameter to require |
322 | * @param array $options Additional options |
323 | * @return Form Self, for message chaining |
324 | */ |
325 | public function requireIntArray( $name, $options = null ) { |
326 | return $this->requireInt( $name, self::wantArray( $options ) ); |
327 | } |
328 | |
329 | /** |
330 | * @param string $name Parameter to expect |
331 | * @param array $options Additional options |
332 | * @return Form Self, for message chaining |
333 | */ |
334 | public function expectIp( $name, $options = null ) { |
335 | return $this->expect( $name, FILTER_VALIDATE_IP, $options ); |
336 | } |
337 | |
338 | /** |
339 | * @param string $name Parameter to require |
340 | * @param array $options Additional options |
341 | * @return Form Self, for message chaining |
342 | */ |
343 | public function requireIp( $name, $options = null ) { |
344 | return $this->expectIp( $name, self::required( $options ) ); |
345 | } |
346 | |
347 | /** |
348 | * @param string $name Parameter to expect |
349 | * @param array $options Additional options |
350 | * @return Form Self, for message chaining |
351 | */ |
352 | public function expectIpArray( $name, $options = null ) { |
353 | return $this->expectIp( $name, self::wantArray( $options ) ); |
354 | } |
355 | |
356 | /** |
357 | * @param string $name Parameter to require |
358 | * @param array $options Additional options |
359 | * @return Form Self, for message chaining |
360 | */ |
361 | public function requireIpArray( $name, $options = null ) { |
362 | return $this->requireIp( $name, self::wantArray( $options ) ); |
363 | } |
364 | |
365 | /** |
366 | * @param string $name Parameter to expect |
367 | * @param string $re Regular expression |
368 | * @param array $options Additional options |
369 | * @return Form Self, for message chaining |
370 | */ |
371 | public function expectRegex( $name, $re, $options = null ) { |
372 | $options = ( is_array( $options ) ) ? $options : []; |
373 | $options['regexp'] = $re; |
374 | return $this->expect( $name, FILTER_VALIDATE_REGEXP, $options ); |
375 | } |
376 | |
377 | /** |
378 | * @param string $name Parameter to require |
379 | * @param string $re Regular expression |
380 | * @param array $options Additional options |
381 | * @return Form Self, for message chaining |
382 | */ |
383 | public function requireRegex( $name, $re, $options = null ) { |
384 | return $this->expectRegex( $name, $re, self::required( $options ) ); |
385 | } |
386 | |
387 | /** |
388 | * @param string $name Parameter to expect |
389 | * @param string $re Regular expression |
390 | * @param array $options Additional options |
391 | * @return Form Self, for message chaining |
392 | */ |
393 | public function expectRegexArray( $name, $re, $options = null ) { |
394 | return $this->expectRegex( $name, $re, self::wantArray( $options ) ); |
395 | } |
396 | |
397 | /** |
398 | * @param string $name Parameter to require |
399 | * @param string $re Regular expression |
400 | * @param array $options Additional options |
401 | * @return Form Self, for message chaining |
402 | */ |
403 | public function requireRegexArray( $name, $re, $options = null ) { |
404 | return $this->requireRegex( $name, $re, self::wantArray( $options ) ); |
405 | } |
406 | |
407 | /** |
408 | * @param string $name Parameter to expect |
409 | * @param array $options Additional options |
410 | * @return Form Self, for message chaining |
411 | */ |
412 | public function expectUrl( $name, $options = null ) { |
413 | return $this->expect( $name, FILTER_VALIDATE_URL, $options ); |
414 | } |
415 | |
416 | /** |
417 | * @param string $name Parameter to require |
418 | * @param array $options Additional options |
419 | * @return Form Self, for message chaining |
420 | */ |
421 | public function requireUrl( $name, $options = null ) { |
422 | return $this->expectUrl( $name, self::required( $options ) ); |
423 | } |
424 | |
425 | /** |
426 | * @param string $name Parameter to expect |
427 | * @param array $options Additional options |
428 | * @return Form Self, for message chaining |
429 | */ |
430 | public function expectUrlArray( $name, $options = null ) { |
431 | return $this->expectUrl( $name, self::wantArray( $options ) ); |
432 | } |
433 | |
434 | /** |
435 | * @param string $name Parameter to require |
436 | * @param array $options Additional options |
437 | * @return Form Self, for message chaining |
438 | */ |
439 | public function requireUrlArray( $name, $options = null ) { |
440 | return $this->requireUrl( $name, self::wantArray( $options ) ); |
441 | } |
442 | |
443 | /** |
444 | * @param string $name Parameter to expect |
445 | * @param array $options Additional options |
446 | * @return Form Self, for message chaining |
447 | */ |
448 | public function expectString( $name, $options = null ) { |
449 | return $this->expectRegex( $name, '/^.+$/s', $options ); |
450 | } |
451 | |
452 | /** |
453 | * @param string $name Parameter to require |
454 | * @param array $options Additional options |
455 | * @return Form Self, for message chaining |
456 | */ |
457 | public function requireString( $name, $options = null ) { |
458 | return $this->expectString( $name, self::required( $options ) ); |
459 | } |
460 | |
461 | /** |
462 | * @param string $name Parameter to expect |
463 | * @param array $options Additional options |
464 | * @return Form Self, for message chaining |
465 | */ |
466 | public function expectStringArray( $name, $options = null ) { |
467 | return $this->expectString( $name, self::wantArray( $options ) ); |
468 | } |
469 | |
470 | /** |
471 | * @param string $name Parameter to require |
472 | * @param array $options Additional options |
473 | * @return Form Self, for message chaining |
474 | */ |
475 | public function requireStringArray( $name, $options = null ) { |
476 | return $this->requireString( $name, self::wantArray( $options ) ); |
477 | } |
478 | |
479 | /** |
480 | * @param string $name Parameter to expect |
481 | * @param array $options Additional options |
482 | * @return Form Self, for message chaining |
483 | */ |
484 | public function expectAnything( $name, $options = null ) { |
485 | return $this->expect( $name, FILTER_UNSAFE_RAW, $options ); |
486 | } |
487 | |
488 | /** |
489 | * @param string $name Parameter to require |
490 | * @param array $options Additional options |
491 | * @return Form Self, for message chaining |
492 | */ |
493 | public function requireAnything( $name, $options = null ) { |
494 | return $this->expectAnything( $name, self::required( $options ) ); |
495 | } |
496 | |
497 | /** |
498 | * @param string $name Parameter to expect |
499 | * @param array $options Additional options |
500 | * @return Form Self, for message chaining |
501 | */ |
502 | public function expectAnythingArray( $name, $options = null ) { |
503 | return $this->expectAnything( $name, self::wantArray( $options ) ); |
504 | } |
505 | |
506 | /** |
507 | * @param string $name Parameter to require |
508 | * @param array $options Additional options |
509 | * @return Form Self, for message chaining |
510 | */ |
511 | public function requireAnythingArray( $name, $options = null ) { |
512 | return $this->requireAnything( $name, self::wantArray( $options ) ); |
513 | } |
514 | |
515 | /** |
516 | * @param string $name Parameter to expect |
517 | * @param array $valids Valid values |
518 | * @param array $options Additional options |
519 | * @return Form Self, for message chaining |
520 | */ |
521 | public function expectInArray( $name, $valids, $options = null ) { |
522 | $options = ( is_array( $options ) ) ? $options : []; |
523 | $required = $options['required'] ?? false; |
524 | $options['validate'] = static function ( $val ) use ( $valids, $required ) { |
525 | return ( !$required && empty( $val ) ) || in_array( $val, $valids ); |
526 | }; |
527 | return $this->expectAnything( $name, $options ); |
528 | } |
529 | |
530 | /** |
531 | * @param string $name Parameter to require |
532 | * @param array $valids Valid values |
533 | * @param array $options Additional options |
534 | * @return Form Self, for message chaining |
535 | */ |
536 | public function requireInArray( $name, $valids, $options = null ) { |
537 | return $this->expectInArray( $name, $valids, |
538 | self::required( $options ) |
539 | ); |
540 | } |
541 | |
542 | /** |
543 | * @param string $name Parameter to expect |
544 | * @param array $valids Valid values |
545 | * @param array $options Additional options |
546 | * @return Form Self, for message chaining |
547 | */ |
548 | public function expectInArrayArray( $name, $valids, $options = null ) { |
549 | return $this->expectInArray( |
550 | $name, $valids, self::wantArray( $options ) |
551 | ); |
552 | } |
553 | |
554 | /** |
555 | * @param string $name Parameter to require |
556 | * @param array $valids Valid values |
557 | * @param array $options Additional options |
558 | * @return Form Self, for message chaining |
559 | */ |
560 | public function requireInArrayArray( $name, $valids, $options = null ) { |
561 | return $this->requireInArray( |
562 | $name, $valids, self::wantArray( $options ) |
563 | ); |
564 | } |
565 | |
566 | /** |
567 | * Add an input expectation for a DateTime object. |
568 | * |
569 | * @param string $name Parameter to expect |
570 | * @param string $format Expected date/time format |
571 | * @param array $options Additional options |
572 | * @return Form Self, for message chaining |
573 | * @see DateTime::createFromFormat |
574 | */ |
575 | public function expectDateTime( $name, $format, $options = null ) { |
576 | $options = ( is_array( $options ) ) ? $options : []; |
577 | $options['callback'] = static function ( $value ) use ( $format ) { |
578 | try { |
579 | $date = DateTime::createFromFormat( $format, $value ); |
580 | $formatErrors = DateTime::getLastErrors(); |
581 | if ( $formatErrors === false || |
582 | ( $formatErrors['error_count'] == 0 && $formatErrors['warning_count'] == 0 ) |
583 | ) { |
584 | return $date; |
585 | } |
586 | } catch ( Exception $ignored ) { |
587 | // no-op |
588 | } |
589 | return false; |
590 | }; |
591 | return $this->expect( $name, FILTER_CALLBACK, $options ); |
592 | } |
593 | |
594 | /** |
595 | * @param string $name Parameter to require |
596 | * @param string $format Expected date/time format |
597 | * @param array $options Additional options |
598 | * @return Form Self, for message chaining |
599 | */ |
600 | public function requireDateTime( $name, $format, $options = null ) { |
601 | return $this->expectDateTime( $name, $format, |
602 | self::required( $options ) |
603 | ); |
604 | } |
605 | |
606 | /** |
607 | * Validate the provided input data using this form's expectations. |
608 | * |
609 | * @param array $vars Input to validate (default $_POST) |
610 | * @return bool True if input is valid, false otherwise |
611 | */ |
612 | public function validate( $vars = null ) { |
613 | $vars = $vars ?: $_POST; |
614 | $this->values = []; |
615 | $this->errors = []; |
616 | $arrayInvalids = []; |
617 | |
618 | $cleaned = filter_var_array( $vars, $this->params ); |
619 | |
620 | foreach ( $this->params as $name => $opt ) { |
621 | $clean = isset( $vars[$name] ) ? $cleaned[$name] : null; |
622 | |
623 | if ( $clean === false && |
624 | $opt['filter'] !== FILTER_VALIDATE_BOOLEAN |
625 | ) { |
626 | $this->values[$name] = null; |
627 | |
628 | } elseif ( is_array( $clean ) && |
629 | ( $opt['flags'] & FILTER_REQUIRE_ARRAY ) && |
630 | $opt['filter'] !== FILTER_VALIDATE_BOOLEAN |
631 | ) { |
632 | // Strip invalid value markers from input array |
633 | $this->values[$name] = []; |
634 | foreach ( $clean as $key => $value ) { |
635 | if ( $opt['filter'] !== FILTER_VALIDATE_BOOLEAN && |
636 | $value !== false |
637 | ) { |
638 | $this->values[$name][$key] = $value; |
639 | |
640 | } elseif ( $opt['filter'] === FILTER_VALIDATE_BOOLEAN && |
641 | $value !== null |
642 | ) { |
643 | $this->values[$name][$key] = $value; |
644 | |
645 | } else { |
646 | // Keep track of invalid keys in case input was |
647 | // required |
648 | if ( !isset( $arrayInvalids[$name] ) ) { |
649 | $arrayInvalids[$name] = []; |
650 | } |
651 | $arrayInvalids[$name][] = "{$name}[{$key}]"; |
652 | } |
653 | } |
654 | |
655 | } else { |
656 | $this->values[$name] = $clean; |
657 | } |
658 | |
659 | if ( $opt['required'] && $this->values[$name] === null ) { |
660 | $this->errors[] = $name; |
661 | |
662 | } elseif ( $opt['required'] && isset( $arrayInvalids[$name] ) ) { |
663 | $this->errors = array_merge( |
664 | $this->errors, $arrayInvalids[$name] |
665 | ); |
666 | |
667 | } elseif ( is_callable( $opt['validate'] ) && |
668 | call_user_func( $opt['validate'], $this->values[$name] ) === false |
669 | ) { |
670 | $this->errors[] = $name; |
671 | $this->values[$name] = null; |
672 | } |
673 | } |
674 | |
675 | $this->customValidationHook(); |
676 | |
677 | return count( $this->errors ) === 0; |
678 | } |
679 | |
680 | /** |
681 | * Stub method that can be extended by subclasses to add additional |
682 | * validation logic. |
683 | */ |
684 | protected function customValidationHook() { |
685 | } |
686 | |
687 | /** |
688 | * @param string $name Parameter name |
689 | * @return mixed Parameter value |
690 | */ |
691 | public function get( $name ) { |
692 | if ( isset( $this->values[$name] ) ) { |
693 | return $this->values[$name]; |
694 | |
695 | } elseif ( isset( $this->params[$name]['options']['default'] ) ) { |
696 | return $this->params[$name]['options']['default']; |
697 | |
698 | } else { |
699 | return null; |
700 | } |
701 | } |
702 | |
703 | /** |
704 | * @return array Form values |
705 | */ |
706 | public function getValues() { |
707 | return $this->values; |
708 | } |
709 | |
710 | /** |
711 | * @return array Form errors |
712 | */ |
713 | public function getErrors() { |
714 | return $this->errors; |
715 | } |
716 | |
717 | /** |
718 | * @return bool True if form has errors, false otherwise. |
719 | */ |
720 | public function hasErrors() { |
721 | return count( $this->errors ) !== 0; |
722 | } |
723 | |
724 | /** |
725 | * Make a URL-encoded string from a key=>value array |
726 | * @param array $parms Parameter array |
727 | * @return string URL-encoded message body |
728 | */ |
729 | public static function urlEncode( $parms ) { |
730 | $payload = []; |
731 | |
732 | foreach ( $parms as $key => $value ) { |
733 | if ( is_array( $value ) ) { |
734 | foreach ( $value as $item ) { |
735 | $payload[] = urlencode( $key ) . '=' . urlencode( $item ); |
736 | } |
737 | } else { |
738 | $payload[] = urlencode( $key ) . '=' . urlencode( $value ); |
739 | } |
740 | } |
741 | |
742 | return implode( '&', $payload ); |
743 | } |
744 | |
745 | /** |
746 | * Merge parameters into current query string. |
747 | * @param array $params Parameter array |
748 | * @return string URL-encoded message body |
749 | */ |
750 | public static function qsMerge( $params = [] ) { |
751 | return self::urlEncode( array_merge( $_GET, $params ) ); |
752 | } |
753 | |
754 | /** |
755 | * Remove parameters from current query string. |
756 | * @param array $params Parameters to remove |
757 | * @return string URL-encoded message body |
758 | */ |
759 | public static function qsRemove( $params = [] ) { |
760 | return self::urlEncode( array_diff_key( $_GET, array_flip( $params ) ) ); |
761 | } |
762 | |
763 | /** |
764 | * Ensure that the given options collection contains a 'required' key. |
765 | * |
766 | * @param array $options |
767 | * @return array |
768 | */ |
769 | protected static function required( $options ) { |
770 | return array_merge( [ 'required' => true ], (array)$options ); |
771 | } |
772 | |
773 | /** |
774 | * Ensure that the given options collection contains a 'flags' key that |
775 | * requires the input to be an array. |
776 | * |
777 | * @param array $options |
778 | * @return array |
779 | */ |
780 | protected static function wantArray( $options ) { |
781 | $options = array_merge( [ 'flags' => 0 ], (array)$options ); |
782 | $options['flags'] |= FILTER_REQUIRE_ARRAY; |
783 | return $options; |
784 | } |
785 | } |