PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.11.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.11.0
2.11.0 2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 All 78 releases
fluent-community / vendor / wpfluent / framework / src / WPFluent / Validator / ValidatesAttributes.php

ValidatesAttributes.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.11.0, at vendor/wpfluent/framework/src/WPFluent/Validator/ValidatesAttributes.php

903 lines 22.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\Framework\Validator;
4
5 use Countable;
6 use Exception;
7 use ValueError;
8 use DateTimeInterface;
9 use InvalidArgumentException;
10 use FluentCommunity\Framework\Support\Str;
11 use FluentCommunity\Framework\Support\Arr;
12 use FluentCommunity\Framework\Support\Helper;
13 use FluentCommunity\Framework\Support\DateTime;
14 use FluentCommunity\Framework\Validator\Contracts\File;
15
16 trait ValidatesAttributes
17 {
18 use ValidateDatabaseRulesTrait;
19
20 /**
21 * Require a certain number of parameters to be present.
22 *
23 * @param int $count
24 * @param array $parameters
25 * @param string $rule
26 *
27 * @return void
28 *
29 * @throws \InvalidArgumentException
30 */
31 protected function requireParameterCount($count, $parameters, $rule)
32 {
33 if (count($parameters) < $count) {
34 throw new InvalidArgumentException(
35 "Validation rule $rule requires at least $count parameters."
36 );
37 }
38 }
39
40 /**
41 * Get the size of an attribute.
42 *
43 * @param string $attribute
44 * @param mixed $value
45 *
46 * @return mixed
47 */
48 protected function getSize($attribute, $value)
49 {
50 // This method will determine if the attribute is a number, string, or file and
51 // return the proper size accordingly. If it is a number, then number itself
52 // is the size. If it is a file, we take kilobytes, and for a string the
53 // entire length of the string will be considered the attribute size.
54 $type = $this->deduceType($value, $attribute);
55
56 switch ($type) {
57 case 'numeric':
58 return $value;
59 case 'array':
60 return count($value);
61 case 'file':
62 return $value->getSize() / 1024;
63 default:
64 return mb_strlen($value);
65 }
66 }
67
68 /**
69 * Deduce the value type of an attribute.
70 *
71 * @param $value
72 *
73 * @return string
74 */
75 protected function deduceType($value, $attribute = null)
76 {
77 if (is_numeric($value)) {
78 return $this->guessType($value, $attribute);
79 } elseif (is_array($value)) {
80 return 'array';
81 } elseif ($value instanceof File) {
82 return 'file';
83 }
84
85 return 'string';
86 }
87
88 /**
89 * Guess the real type by examining rules.
90 *
91 * @param mixed $value
92 * @param string|null $attribute
93 * @return string
94 */
95 protected function guessType($value, $attribute)
96 {
97 if ($attribute && in_array('string', $this->rules[$attribute])) {
98 return 'string';
99 }
100
101 return 'numeric';
102 }
103
104 /**
105 * Check if parameter should be converted to boolean.
106 *
107 * @param string $parameter
108 * @return bool
109 */
110 protected function shouldConvertToBoolean($parameter)
111 {
112 return in_array('boolean', Arr::get($this->rules, $parameter, []));
113 }
114
115 /**
116 * Convert the given values to boolean if they are string "true" / "false".
117 *
118 * @param array $values
119 *
120 * @return array
121 */
122 protected function convertValuesToBoolean($values)
123 {
124 return array_map(function ($value) {
125 if ($value === 'true') {
126 return true;
127 } elseif ($value === 'false') {
128 return false;
129 }
130
131 return $value;
132 }, $values);
133 }
134
135 /**
136 * Convert the given values to null if they are string "null".
137 *
138 * @param array $values
139 * @return array
140 */
141 protected function convertValuesToNull($values)
142 {
143 return array_map(function ($value) {
144 return Str::lower($value) === 'null' ? null : $value;
145 }, $values);
146 }
147
148 /**
149 * Validate that a required attribute exists.
150 *
151 * @param string $attribute
152 * @param mixed $value
153 *
154 * @return bool
155 */
156 protected function validateRequired($attribute, $value)
157 {
158 if (is_null($value)) {
159 return false;
160 } elseif (is_string($value) && trim($value) === '') {
161 return false;
162 } elseif ((is_array($value) || $value instanceof Countable) && count($value) < 1) {
163 return false;
164 } elseif ($value instanceof File) {
165 return (string) $value->getPath() != '';
166 }
167
168 return true;
169 }
170
171 /**
172 * Validate that an attribute exists when another attribute has a given value.
173 *
174 * @param string $attribute
175 * @param mixed $value
176 * @param mixed $parameters
177 *
178 * @return bool
179 */
180 protected function validateRequiredIf($attribute, $value, $parameters)
181 {
182 $this->requireParameterCount(2, $parameters, 'required_if');
183
184 if (preg_match('/\.\d\./', $attribute, $matches)) {
185 $parameters[0] = str_replace(['.*.'], $matches, $parameters[0]);
186 }
187
188 $other = Arr::get($this->data, $parameters[0]);
189
190 $values = array_slice($parameters, 1);
191
192 if (is_bool($other)) {
193 $values = $this->convertValuesToBoolean($values);
194 }
195
196 if (in_array($other, $values)) {
197 return $this->validateRequired($attribute, $value);
198 }
199
200 return true;
201 }
202
203 /**
204 * Validate that an attribute is required only if **any** of the
205 * other specified fields are present.
206 *
207 * @param string $attribute
208 * @param mixed $value
209 * @param array $parameters
210 * @return bool
211 */
212 protected function validateRequiredWith($attribute, $value, $parameters)
213 {
214 $this->requireParameterCount(1, $parameters, 'required_with');
215
216 foreach ($parameters as $field) {
217 if ($this->isPresent($field) && !$this->isPresent($attribute)) {
218 return $this->validateRequired($attribute, $value);
219 }
220 }
221
222 return true;
223 }
224
225 /**
226 * Validate that an attribute is required only if **all** of the
227 * other specified fields are present.
228 *
229 * @param string $attribute
230 * @param mixed $value
231 * @param array $parameters
232 * @return bool
233 */
234 protected function validateRequiredWithAll($attribute, $value, $parameters)
235 {
236 $this->requireParameterCount(1, $parameters, 'required_with_all');
237
238 $allPresent = true;
239
240 foreach ($parameters as $field) {
241 if (!$this->isPresent($field)) {
242 $allPresent = false;
243 break;
244 }
245 }
246
247 if ($allPresent && !$this->isPresent($attribute)) {
248 return $this->validateRequired($attribute, $value);
249 }
250
251 return true;
252 }
253
254 /**
255 * Validate that an attribute is required only if **any** of the
256 * other specified fields are **not present**.
257 *
258 * @param string $attribute
259 * @param mixed $value
260 * @param array $parameters
261 * @return bool
262 */
263 protected function validateRequiredWithout($attribute, $value, $parameters)
264 {
265 $this->requireParameterCount(1, $parameters, 'required_without');
266
267 foreach ($parameters as $field) {
268 if (!$this->isPresent($field) && !$this->isPresent($attribute)) {
269 return $this->validateRequired($attribute, $value);
270 }
271 }
272
273 return true;
274 }
275
276 /**
277 * Validate that an attribute is required only if **all** of the
278 * other specified fields are **not present**.
279 *
280 * @param string $attribute
281 * @param mixed $value
282 * @param array $parameters
283 * @return bool
284 */
285 protected function validateRequiredWithoutAll($attribute, $value, $parameters)
286 {
287 $this->requireParameterCount(1, $parameters, 'required_without_all');
288
289 $allMissing = true;
290
291 foreach ($parameters as $field) {
292 if ($this->isPresent($field)) {
293 $allMissing = false;
294 break;
295 }
296 }
297
298 if ($allMissing && !$this->isPresent($attribute)) {
299 return $this->validateRequired($attribute, $value);
300 }
301
302 return true;
303 }
304
305 /**
306 * Determine if the given attribute is present.
307 *
308 * @param string $attribute
309 * @return boolean
310 */
311 protected function isPresent($attribute)
312 {
313 $attribute = str_replace(['[', ']'], ['.', ''], $attribute);
314
315 if (!Arr::has($this->data, $attribute)) {
316 return false;
317 }
318
319 $value = Arr::get($this->data, $attribute);
320
321 return !is_null($value) && $value !== '';
322 }
323
324 /**
325 * Validate that an attribute is greater than value.
326 *
327 * @param string $attribute
328 * @param mixed $value
329 * @param array $parameters
330 * @return bool
331 */
332 protected function validateGt($attribute, $value, $parameters)
333 {
334 $this->requireParameterCount(1, $parameters, 'gt');
335
336 if (!is_numeric($value) || !is_numeric($parameters[0])) {
337 return false;
338 }
339
340 return $value > $parameters[0];
341 }
342
343 /**
344 * Validate that an attribute is greater than or equal to value.
345 *
346 * @param string $attribute
347 * @param mixed $value
348 * @param array $parameters
349 * @return bool
350 */
351 protected function validateGte($attribute, $value, $parameters)
352 {
353 $this->requireParameterCount(1, $parameters, 'gte');
354
355 if (!is_numeric($value) || !is_numeric($parameters[0])) {
356 return false;
357 }
358
359 return $value >= $parameters[0];
360 }
361
362 /**
363 * Validate that an attribute is less than value.
364 *
365 * @param string $attribute
366 * @param mixed $value
367 * @param array $parameters
368 * @return bool
369 */
370 protected function validateLt($attribute, $value, $parameters)
371 {
372 $this->requireParameterCount(1, $parameters, 'lt');
373
374 if (!is_numeric($value) || !is_numeric($parameters[0])) {
375 return false;
376 }
377
378 return $value < $parameters[0];
379 }
380
381 /**
382 * Validate that an attribute is less than or equal to value.
383 *
384 * @param string $attribute
385 * @param mixed $value
386 * @param array $parameters
387 * @return bool
388 */
389 protected function validateLte($attribute, $value, $parameters)
390 {
391 $this->requireParameterCount(1, $parameters, 'lte');
392
393 if (!is_numeric($value) || !is_numeric($parameters[0])) {
394 return false;
395 }
396
397 return $value <= $parameters[0];
398 }
399
400 /**
401 * Validate that an attribute is a valid e-mail address.
402 *
403 * @param string $attribute
404 * @param mixed $value
405 *
406 * @return bool
407 */
408 protected function validateEmail($attribute, $value)
409 {
410 return ! ! is_email($value);
411 }
412
413 /**
414 * Validate the size of an attribute.
415 *
416 * @param string $attribute
417 * @param mixed $value
418 * @param array $parameters
419 *
420 * @return bool
421 */
422 protected function validateSize($attribute, $value, $parameters)
423 {
424 $this->requireParameterCount(1, $parameters, 'size');
425
426 return $this->getSize($attribute, $value) == $parameters[0];
427 }
428
429 /**
430 * Validate the size of an attribute is greater than a minimum value.
431 *
432 * @param string $attribute
433 * @param mixed $value
434 * @param array $parameters
435 *
436 * @return bool
437 */
438 protected function validateMin($attribute, $value, $parameters)
439 {
440 $this->requireParameterCount(1, $parameters, 'min');
441
442 return $this->getSize($attribute, $value) >= $parameters[0];
443 }
444
445 /**
446 * Validate the size of an attribute is less than a maximum value.
447 *
448 * @param string $attribute
449 * @param mixed $value
450 * @param array $parameters
451 *
452 * @return bool
453 */
454 protected function validateMax($attribute, $value, $parameters)
455 {
456 $this->requireParameterCount(1, $parameters, 'max');
457
458 return $this->getSize($attribute, $value) <= $parameters[0];
459 }
460
461 /**
462 * Validate that two attributes match.
463 *
464 * @param string $attribute
465 * @param mixed $value
466 * @param array $parameters
467 *
468 * @return bool
469 */
470 protected function validateSame($attribute, $value, $parameters)
471 {
472 $this->requireParameterCount(1, $parameters, 'same');
473
474 $other = @$this->data[$parameters[0]];
475
476 return $value === $other;
477 }
478
479 /**
480 * Validate that an attribute is a valid URL.
481 *
482 * @param string $attribute
483 * @param mixed $value
484 *
485 * @return bool
486 */
487 protected function validateUrl($attribute, $value)
488 {
489 return (bool) Str::isUrl($value);
490 }
491
492 /**
493 * Validate that an attribute is a string.
494 *
495 * @param string $attribute
496 * @param mixed $value
497 * @return bool
498 */
499 protected function validateString($attribute, $value)
500 {
501 return is_string($value);
502 }
503
504 /**
505 * Validate that an attribute is a integer.
506 *
507 * @param string $attribute
508 * @param mixed $value
509 * @return bool
510 */
511 protected function validateInt($attribute, $value)
512 {
513 return $this->validateInteger($attribute, $value);
514 }
515
516 /**
517 * Validate that an attribute is a integer.
518 *
519 * @param string $attribute
520 * @param mixed $value
521 * @return bool
522 */
523 protected function validateInteger($attribute, $value)
524 {
525 return is_int($value);
526 }
527
528 /**
529 * Validate that an attribute has a given number of decimal places.
530 *
531 * @param string $attribute
532 * @param mixed $value
533 * @param array<int, int|string> $parameters
534 * @return bool
535 */
536 protected function validateDecimal($attribute, $value, $parameters)
537 {
538 $this->requireParameterCount(1, $parameters, 'decimal');
539
540 if (!$this->validateNumeric($attribute, $value)) {
541 return false;
542 }
543
544 $matches = [];
545
546 if (preg_match('/^[+-]?\d*\.?(\d*)$/', $value, $matches) !== 1) {
547 return false;
548 }
549
550 $decimals = strlen(end($matches));
551
552 if (!isset($parameters[1])) {
553 return $decimals == $parameters[0];
554 }
555
556 return $decimals >= $parameters[0] && $decimals <= $parameters[1];
557 }
558
559 /**
560 * Validate that an attribute is numeric.
561 *
562 * @param string $attribute
563 * @param mixed $value
564 *
565 * @return bool
566 */
567 protected function validateNumeric($attribute, $value)
568 {
569 return is_numeric($value);
570 }
571
572 /**
573 * Validate that an attribute is a valid date.
574 *
575 * @param string $attribute
576 * @param mixed $value
577 * @return bool
578 */
579 protected function validateDate($attribute, $value)
580 {
581 if ($value instanceof DateTimeInterface) {
582 return true;
583 }
584
585 try {
586 if ((!is_string($value) && !is_numeric($value)) || strtotime($value) === false) {
587 return false;
588 }
589 } catch (Exception $e) {
590 return false;
591 }
592
593 $date = date_parse($value);
594
595 return checkdate($date['month'], $date['day'], $date['year']);
596 }
597
598 /**
599 * Validate that an attribute matches a date format.
600 *
601 * @param string $attribute
602 * @param mixed $value
603 * @param array<int, int|string> $parameters
604 * @return bool
605 */
606 protected function validateDateFormat($attribute, $value, $parameters)
607 {
608 $this->requireParameterCount(1, $parameters, 'date_format');
609
610 if (!is_string($value) && !is_numeric($value)) {
611 return false;
612 }
613
614 foreach ($parameters as $format) {
615 try {
616 $date = DateTime::createFromFormat('!'.$format, $value);
617
618 if ($date && $date->format($format) == $value) {
619 return true;
620 }
621 } catch (ValueError $e) {
622 return false;
623 }
624 }
625
626 return false;
627 }
628
629 /**
630 * Validate that an attribute is alphabetic.
631 *
632 * @param string $attribute
633 * @param mixed $value
634 *
635 * @return bool
636 */
637 protected function validateAlpha($attribute, $value)
638 {
639 return is_string($value) && preg_match('/^[\pL\pM]+$/u', $value);
640 }
641
642 /**
643 * Validate that an attribute is alphanum.
644 *
645 * @param string $attribute
646 * @param mixed $value
647 *
648 * @return bool
649 */
650 protected function validateAlphanum($attribute, $value)
651 {
652 if (! is_string($value) && ! is_numeric($value)) {
653 return false;
654 }
655
656 return preg_match('/^[\pL\pM\pN]+$/u', $value) > 0;
657 }
658
659 /**
660 * Validate that an attribute is alphanum and -_.
661 *
662 * @param string $attribute
663 * @param mixed $value
664 *
665 * @return bool
666 */
667 protected function validateAlphadash($attribute, $value)
668 {
669 if (! is_string($value) && ! is_numeric($value)) {
670 return false;
671 }
672
673 return preg_match('/^[\pL\pM\pN_-]+$/u', $value) > 0;
674 }
675
676 /**
677 * Validate the guessed extension of a file upload is in a set of file extensions.
678 *
679 * @param string $attribute
680 * @param mixed $value
681 * @param array $parameters
682 *
683 * @return bool
684 */
685 protected function validateMimes($attribute, $value, $parameters)
686 {
687 if (! $this->isValidFileInstance($value)) {
688 return false;
689 }
690
691 if ($this->shouldBlockPhpUpload($value, $parameters)) {
692 return false;
693 }
694
695 /**
696 * @var $value \FluentCommunity\Framework\Validator\Contracts\File
697 */
698 // wp_check_filetype() matches case-insensitively but returns the
699 // extension in the filename's own case ("clip.MOV" => "MOV"), so a
700 // case-sensitive compare rejects valid uploads. Fold both sides.
701 // Casts guard the false (no match) and [null] (bare "mimes:" via
702 // str_getcsv) cases, and avoid strtolower(null) on PHP 8.1+.
703 $extension = strtolower((string) $value->guessExtension());
704
705 $allowed = array_map(function ($parameter) {
706 return strtolower((string) $parameter);
707 }, $parameters);
708
709 return $value->getPath() != '' && '' !== $extension && in_array(
710 $extension, $allowed, true
711 );
712 }
713
714 /**
715 * Validate the MIME type of a file upload attribute is in a set of MIME types.
716 *
717 * @param string $attribute
718 * @param mixed $value
719 * @param array $parameters
720 * @return bool
721 */
722 protected function validateMimetypes($attribute, $value, $parameters)
723 {
724 if (! $this->isValidFileInstance($value)) {
725 return false;
726 }
727
728 if ($this->shouldBlockPhpUpload($value, $parameters)) {
729 return false;
730 }
731
732 return $value->getPath() != '' && (
733 in_array($value->getMimeType(), $parameters) ||
734 in_array(explode('/', $value->getMimeType())[0].'/*', $parameters)
735 );
736 }
737
738 /**
739 * Check that the given value is a valid file instance.
740 *
741 * @param mixed $value
742 *
743 * @return bool
744 */
745 protected function isValidFileInstance($value)
746 {
747 return $value instanceof File && $value->isValid();
748 }
749
750 /**
751 * Check if PHP uploads are explicitly allowed.
752 *
753 * @param mixed $value
754 * @param array $parameters
755 *
756 * @return bool
757 */
758 protected function shouldBlockPhpUpload($value, $parameters)
759 {
760 if (in_array('php', $parameters)) {
761 return false;
762 }
763
764 /**
765 * @var $value \FluentCommunity\Framework\Validator\Contracts\File
766 */
767 return strtolower($value->getClientOriginalExtension()) === 'php';
768 }
769
770 /**
771 * Validate that an attribute exists even if not filled.
772 *
773 * @param string $attribute
774 * @param mixed $value
775 *
776 * @return bool
777 */
778 protected function validatePresent($attribute, $value)
779 {
780 $attribute = str_replace(['[', ']'], ['.', ''], $attribute);
781
782 return Arr::has($this->data, $attribute);
783 }
784
785 /**
786 * Validate that an attribute has a given number of digits.
787 *
788 * @param string $attribute
789 * @param mixed $value
790 * @param array $parameters
791 *
792 * @return bool
793 */
794 protected function validateDigits($attribute, $value, $parameters)
795 {
796 $this->requireParameterCount(1, $parameters, 'digits');
797
798 return $this->validateNumeric($attribute, $value)
799 && strlen((string) $value) == $parameters[0];
800 }
801
802 /**
803 * Validate that an attribute is an array.
804 *
805 * @param string $attribute
806 * @param mixed $value
807 * @param array $parameters
808 * @return bool
809 */
810 protected function validateArray($attribute, $value, $parameters = [])
811 {
812 if (! is_array($value)) {
813 return false;
814 }
815
816 if (empty($parameters)) {
817 return true;
818 }
819
820 return empty(array_diff_key($value, array_fill_keys($parameters, '')));
821 }
822
823 /**
824 * Validate that an array has all of the given keys.
825 *
826 * @param string $attribute
827 * @param mixed $value
828 * @param array<int, int|string> $parameters
829 * @return bool
830 */
831 protected function validateRequiredArrayKeys($attribute, $value, $parameters)
832 {
833 $this->requireParameterCount(1, $parameters, 'required_array_keys');
834
835 if (!is_array($value)) {
836 return false;
837 }
838
839 foreach ($parameters as $key) {
840 if (!Arr::exists($value, $key)) {
841 return false;
842 }
843 }
844
845 return true;
846 }
847
848 /**
849 * Validate that an attribute passes a regular expression check.
850 *
851 * @param string $attribute
852 * @param mixed $value
853 * @param array<int, int|string> $parameters
854 * @return bool
855 */
856 protected function validateRegex($attribute, $value, $parameters)
857 {
858 if (! is_string($value) && ! is_numeric($value)) {
859 return false;
860 }
861
862 $this->requireParameterCount(1, $parameters, 'regex');
863
864 return preg_match($parameters[0], $value) > 0;
865 }
866
867 /**
868 * Validate an attribute is contained within a list of values.
869 *
870 * @param string $attribute
871 * @param mixed $value
872 * @param array $parameters
873 * @return bool
874 */
875 protected function validateIn($attribute, $value, $parameters)
876 {
877 if (is_array($value) && $this->hasRule($attribute, 'Array')) {
878 foreach ($value as $element) {
879 if (is_array($element)) {
880 return false;
881 }
882 }
883
884 return count(array_diff($value, $parameters)) === 0;
885 }
886
887 return !is_array($value) && in_array((string) $value, $parameters);
888 }
889
890 /**
891 * Validate an attribute is not contained within a list of values.
892 *
893 * @param string $attribute
894 * @param mixed $value
895 * @param array $parameters
896 * @return bool
897 */
898 protected function validateNotIn($attribute, $value, $parameters)
899 {
900 return !$this->validateIn($attribute, $value, $parameters);
901 }
902 }
903