Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Revision 399 | Zur aktuellen Revision | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Validation\Concerns;
4
 
5
use DateTime;
6
use DateTimeInterface;
7
use Egulias\EmailValidator\EmailValidator;
8
use Egulias\EmailValidator\Validation\DNSCheckValidation;
9
use Egulias\EmailValidator\Validation\Extra\SpoofCheckValidation;
10
use Egulias\EmailValidator\Validation\MultipleValidationWithAnd;
11
use Egulias\EmailValidator\Validation\NoRFCWarningsValidation;
12
use Egulias\EmailValidator\Validation\RFCValidation;
13
use Exception;
14
use Illuminate\Database\Eloquent\Model;
15
use Illuminate\Support\Arr;
16
use Illuminate\Support\Facades\Date;
17
use Illuminate\Support\Str;
18
use Illuminate\Validation\Rules\Exists;
19
use Illuminate\Validation\Rules\Unique;
20
use Illuminate\Validation\ValidationData;
21
use InvalidArgumentException;
22
use Symfony\Component\HttpFoundation\File\File;
23
use Symfony\Component\HttpFoundation\File\UploadedFile;
24
 
25
trait ValidatesAttributes
26
{
27
    /**
28
     * Validate that an attribute was "accepted".
29
     *
30
     * This validation rule implies the attribute is "required".
31
     *
32
     * @param  string  $attribute
33
     * @param  mixed  $value
34
     * @return bool
35
     */
36
    public function validateAccepted($attribute, $value)
37
    {
38
        $acceptable = ['yes', 'on', '1', 1, true, 'true'];
39
 
40
        return $this->validateRequired($attribute, $value) && in_array($value, $acceptable, true);
41
    }
42
 
43
    /**
44
     * Validate that an attribute was "accepted" when another attribute has a given value.
45
     *
46
     * @param  string  $attribute
47
     * @param  mixed  $value
48
     * @param  mixed  $parameters
49
     * @return bool
50
     */
51
    public function validateAcceptedIf($attribute, $value, $parameters)
52
    {
53
        $acceptable = ['yes', 'on', '1', 1, true, 'true'];
54
 
55
        $this->requireParameterCount(2, $parameters, 'accepted_if');
56
 
57
        [$values, $other] = $this->parseDependentRuleParameters($parameters);
58
 
59
        if (in_array($other, $values, is_bool($other) || is_null($other))) {
60
            return $this->validateRequired($attribute, $value) && in_array($value, $acceptable, true);
61
        }
62
 
63
        return true;
64
    }
65
 
66
    /**
67
     * Validate that an attribute was "declined".
68
     *
69
     * This validation rule implies the attribute is "required".
70
     *
71
     * @param  string  $attribute
72
     * @param  mixed  $value
73
     * @return bool
74
     */
75
    public function validateDeclined($attribute, $value)
76
    {
77
        $acceptable = ['no', 'off', '0', 0, false, 'false'];
78
 
79
        return $this->validateRequired($attribute, $value) && in_array($value, $acceptable, true);
80
    }
81
 
82
    /**
83
     * Validate that an attribute was "declined" when another attribute has a given value.
84
     *
85
     * @param  string  $attribute
86
     * @param  mixed  $value
87
     * @param  mixed  $parameters
88
     * @return bool
89
     */
90
    public function validateDeclinedIf($attribute, $value, $parameters)
91
    {
92
        $acceptable = ['no', 'off', '0', 0, false, 'false'];
93
 
94
        $this->requireParameterCount(2, $parameters, 'declined_if');
95
 
96
        [$values, $other] = $this->parseDependentRuleParameters($parameters);
97
 
98
        if (in_array($other, $values, is_bool($other) || is_null($other))) {
99
            return $this->validateRequired($attribute, $value) && in_array($value, $acceptable, true);
100
        }
101
 
102
        return true;
103
    }
104
 
105
    /**
106
     * Validate that an attribute is an active URL.
107
     *
108
     * @param  string  $attribute
109
     * @param  mixed  $value
110
     * @return bool
111
     */
112
    public function validateActiveUrl($attribute, $value)
113
    {
114
        if (! is_string($value)) {
115
            return false;
116
        }
117
 
118
        if ($url = parse_url($value, PHP_URL_HOST)) {
119
            try {
120
                $records = $this->getDnsRecords($url.'.', DNS_A | DNS_AAAA);
121
 
122
                if (is_array($records) && count($records) > 0) {
123
                    return true;
124
                }
125
            } catch (Exception $e) {
126
                return false;
127
            }
128
        }
129
 
130
        return false;
131
    }
132
 
133
    /**
134
     * Get the DNS records for the given hostname.
135
     *
136
     * @param  string  $hostname
137
     * @param  int  $type
138
     * @return array|false
139
     */
140
    protected function getDnsRecords($hostname, $type)
141
    {
142
        return dns_get_record($hostname, $type);
143
    }
144
 
145
    /**
146
     * Validate that an attribute is 7 bit ASCII.
147
     *
148
     * @param  string  $attribute
149
     * @param  mixed  $value
150
     * @return bool
151
     */
152
    public function validateAscii($attribute, $value)
153
    {
154
        return Str::isAscii($value);
155
    }
156
 
157
    /**
158
     * "Break" on first validation fail.
159
     *
160
     * Always returns true, just lets us put "bail" in rules.
161
     *
162
     * @return bool
163
     */
164
    public function validateBail()
165
    {
166
        return true;
167
    }
168
 
169
    /**
170
     * Validate the date is before a given date.
171
     *
172
     * @param  string  $attribute
173
     * @param  mixed  $value
174
     * @param  array<int, int|string>  $parameters
175
     * @return bool
176
     */
177
    public function validateBefore($attribute, $value, $parameters)
178
    {
179
        $this->requireParameterCount(1, $parameters, 'before');
180
 
181
        return $this->compareDates($attribute, $value, $parameters, '<');
182
    }
183
 
184
    /**
185
     * Validate the date is before or equal a given date.
186
     *
187
     * @param  string  $attribute
188
     * @param  mixed  $value
189
     * @param  array<int, int|string>  $parameters
190
     * @return bool
191
     */
192
    public function validateBeforeOrEqual($attribute, $value, $parameters)
193
    {
194
        $this->requireParameterCount(1, $parameters, 'before_or_equal');
195
 
196
        return $this->compareDates($attribute, $value, $parameters, '<=');
197
    }
198
 
199
    /**
200
     * Validate the date is after a given date.
201
     *
202
     * @param  string  $attribute
203
     * @param  mixed  $value
204
     * @param  array<int, int|string>  $parameters
205
     * @return bool
206
     */
207
    public function validateAfter($attribute, $value, $parameters)
208
    {
209
        $this->requireParameterCount(1, $parameters, 'after');
210
 
211
        return $this->compareDates($attribute, $value, $parameters, '>');
212
    }
213
 
214
    /**
215
     * Validate the date is equal or after a given date.
216
     *
217
     * @param  string  $attribute
218
     * @param  mixed  $value
219
     * @param  array<int, int|string>  $parameters
220
     * @return bool
221
     */
222
    public function validateAfterOrEqual($attribute, $value, $parameters)
223
    {
224
        $this->requireParameterCount(1, $parameters, 'after_or_equal');
225
 
226
        return $this->compareDates($attribute, $value, $parameters, '>=');
227
    }
228
 
229
    /**
230
     * Compare a given date against another using an operator.
231
     *
232
     * @param  string  $attribute
233
     * @param  mixed  $value
234
     * @param  array<int, int|string>  $parameters
235
     * @param  string  $operator
236
     * @return bool
237
     */
238
    protected function compareDates($attribute, $value, $parameters, $operator)
239
    {
240
        if (! is_string($value) && ! is_numeric($value) && ! $value instanceof DateTimeInterface) {
241
            return false;
242
        }
243
 
244
        if ($format = $this->getDateFormat($attribute)) {
245
            return $this->checkDateTimeOrder($format, $value, $parameters[0], $operator);
246
        }
247
 
248
        if (! $date = $this->getDateTimestamp($parameters[0])) {
249
            $date = $this->getDateTimestamp($this->getValue($parameters[0]));
250
        }
251
 
252
        return $this->compare($this->getDateTimestamp($value), $date, $operator);
253
    }
254
 
255
    /**
256
     * Get the date format for an attribute if it has one.
257
     *
258
     * @param  string  $attribute
259
     * @return string|null
260
     */
261
    protected function getDateFormat($attribute)
262
    {
263
        if ($result = $this->getRule($attribute, 'DateFormat')) {
264
            return $result[1][0];
265
        }
266
    }
267
 
268
    /**
269
     * Get the date timestamp.
270
     *
271
     * @param  mixed  $value
272
     * @return int
273
     */
274
    protected function getDateTimestamp($value)
275
    {
276
        $date = is_null($value) ? null : $this->getDateTime($value);
277
 
278
        return $date ? $date->getTimestamp() : null;
279
    }
280
 
281
    /**
282
     * Given two date/time strings, check that one is after the other.
283
     *
284
     * @param  string  $format
285
     * @param  string  $first
286
     * @param  string  $second
287
     * @param  string  $operator
288
     * @return bool
289
     */
290
    protected function checkDateTimeOrder($format, $first, $second, $operator)
291
    {
292
        $firstDate = $this->getDateTimeWithOptionalFormat($format, $first);
293
 
294
        if (! $secondDate = $this->getDateTimeWithOptionalFormat($format, $second)) {
295
            if (is_null($second = $this->getValue($second))) {
296
                return true;
297
            }
298
 
299
            $secondDate = $this->getDateTimeWithOptionalFormat($format, $second);
300
        }
301
 
302
        return ($firstDate && $secondDate) && $this->compare($firstDate, $secondDate, $operator);
303
    }
304
 
305
    /**
306
     * Get a DateTime instance from a string.
307
     *
308
     * @param  string  $format
309
     * @param  string  $value
310
     * @return \DateTime|null
311
     */
312
    protected function getDateTimeWithOptionalFormat($format, $value)
313
    {
314
        if ($date = DateTime::createFromFormat('!'.$format, $value)) {
315
            return $date;
316
        }
317
 
318
        return $this->getDateTime($value);
319
    }
320
 
321
    /**
322
     * Get a DateTime instance from a string with no format.
323
     *
324
     * @param  string  $value
325
     * @return \DateTime|null
326
     */
327
    protected function getDateTime($value)
328
    {
329
        try {
330
            return @Date::parse($value) ?: null;
331
        } catch (Exception $e) {
332
            //
333
        }
334
    }
335
 
336
    /**
337
     * Validate that an attribute contains only alphabetic characters.
338
     *
339
     * @param  string  $attribute
340
     * @param  mixed  $value
341
     * @return bool
342
     */
343
    public function validateAlpha($attribute, $value)
344
    {
345
        return is_string($value) && preg_match('/^[\pL\pM]+$/u', $value);
346
    }
347
 
348
    /**
349
     * Validate that an attribute contains only alpha-numeric characters, dashes, and underscores.
350
     *
351
     * @param  string  $attribute
352
     * @param  mixed  $value
353
     * @return bool
354
     */
355
    public function validateAlphaDash($attribute, $value)
356
    {
357
        if (! is_string($value) && ! is_numeric($value)) {
358
            return false;
359
        }
360
 
361
        return preg_match('/^[\pL\pM\pN_-]+$/u', $value) > 0;
362
    }
363
 
364
    /**
365
     * Validate that an attribute contains only alpha-numeric characters.
366
     *
367
     * @param  string  $attribute
368
     * @param  mixed  $value
369
     * @return bool
370
     */
371
    public function validateAlphaNum($attribute, $value)
372
    {
373
        if (! is_string($value) && ! is_numeric($value)) {
374
            return false;
375
        }
376
 
377
        return preg_match('/^[\pL\pM\pN]+$/u', $value) > 0;
378
    }
379
 
380
    /**
381
     * Validate that an attribute is an array.
382
     *
383
     * @param  string  $attribute
384
     * @param  mixed  $value
385
     * @param  array<int, int|string>  $parameters
386
     * @return bool
387
     */
388
    public function validateArray($attribute, $value, $parameters = [])
389
    {
390
        if (! is_array($value)) {
391
            return false;
392
        }
393
 
394
        if (empty($parameters)) {
395
            return true;
396
        }
397
 
398
        return empty(array_diff_key($value, array_fill_keys($parameters, '')));
399
    }
400
 
401
    /**
402
     * Validate that an array has all of the given keys.
403
     *
404
     * @param  string  $attribute
405
     * @param  mixed  $value
406
     * @param  array<int, int|string>  $parameters
407
     * @return bool
408
     */
409
    public function validateRequiredArrayKeys($attribute, $value, $parameters)
410
    {
411
        if (! is_array($value)) {
412
            return false;
413
        }
414
 
415
        foreach ($parameters as $param) {
416
            if (! Arr::exists($value, $param)) {
417
                return false;
418
            }
419
        }
420
 
421
        return true;
422
    }
423
 
424
    /**
425
     * Validate the size of an attribute is between a set of values.
426
     *
427
     * @param  string  $attribute
428
     * @param  mixed  $value
429
     * @param  array<int, int|string>  $parameters
430
     * @return bool
431
     */
432
    public function validateBetween($attribute, $value, $parameters)
433
    {
434
        $this->requireParameterCount(2, $parameters, 'between');
435
 
436
        $size = $this->getSize($attribute, $value);
437
 
438
        return $size >= $parameters[0] && $size <= $parameters[1];
439
    }
440
 
441
    /**
442
     * Validate that an attribute is a boolean.
443
     *
444
     * @param  string  $attribute
445
     * @param  mixed  $value
446
     * @return bool
447
     */
448
    public function validateBoolean($attribute, $value)
449
    {
450
        $acceptable = [true, false, 0, 1, '0', '1'];
451
 
452
        return in_array($value, $acceptable, true);
453
    }
454
 
455
    /**
456
     * Validate that an attribute has a matching confirmation.
457
     *
458
     * @param  string  $attribute
459
     * @param  mixed  $value
460
     * @return bool
461
     */
462
    public function validateConfirmed($attribute, $value)
463
    {
464
        return $this->validateSame($attribute, $value, [$attribute.'_confirmation']);
465
    }
466
 
467
    /**
468
     * Validate that the password of the currently authenticated user matches the given value.
469
     *
470
     * @param  string  $attribute
471
     * @param  mixed  $value
472
     * @param  array<int, int|string>  $parameters
473
     * @return bool
474
     */
475
    protected function validateCurrentPassword($attribute, $value, $parameters)
476
    {
477
        $auth = $this->container->make('auth');
478
        $hasher = $this->container->make('hash');
479
 
480
        $guard = $auth->guard(Arr::first($parameters));
481
 
482
        if ($guard->guest()) {
483
            return false;
484
        }
485
 
486
        return $hasher->check($value, $guard->user()->getAuthPassword());
487
    }
488
 
489
    /**
490
     * Validate that an attribute is a valid date.
491
     *
492
     * @param  string  $attribute
493
     * @param  mixed  $value
494
     * @return bool
495
     */
496
    public function validateDate($attribute, $value)
497
    {
498
        if ($value instanceof DateTimeInterface) {
499
            return true;
500
        }
501
 
502
        try {
503
            if ((! is_string($value) && ! is_numeric($value)) || strtotime($value) === false) {
504
                return false;
505
            }
506
        } catch (Exception $e) {
507
            return false;
508
        }
509
 
510
        $date = date_parse($value);
511
 
512
        return checkdate($date['month'], $date['day'], $date['year']);
513
    }
514
 
515
    /**
516
     * Validate that an attribute matches a date format.
517
     *
518
     * @param  string  $attribute
519
     * @param  mixed  $value
520
     * @param  array<int, int|string>  $parameters
521
     * @return bool
522
     */
523
    public function validateDateFormat($attribute, $value, $parameters)
524
    {
525
        $this->requireParameterCount(1, $parameters, 'date_format');
526
 
527
        if (! is_string($value) && ! is_numeric($value)) {
528
            return false;
529
        }
530
 
531
        foreach ($parameters as $format) {
532
            $date = DateTime::createFromFormat('!'.$format, $value);
533
 
534
            if ($date && $date->format($format) == $value) {
535
                return true;
536
            }
537
        }
538
 
539
        return false;
540
    }
541
 
542
    /**
543
     * Validate that an attribute is equal to another date.
544
     *
545
     * @param  string  $attribute
546
     * @param  mixed  $value
547
     * @param  array<int, int|string>  $parameters
548
     * @return bool
549
     */
550
    public function validateDateEquals($attribute, $value, $parameters)
551
    {
552
        $this->requireParameterCount(1, $parameters, 'date_equals');
553
 
554
        return $this->compareDates($attribute, $value, $parameters, '=');
555
    }
556
 
557
    /**
558
     * Validate that an attribute has a given number of decimal places.
559
     *
560
     * @param  string  $attribute
561
     * @param  mixed  $value
562
     * @param  array<int, int|string>  $parameters
563
     * @return bool
564
     */
565
    public function validateDecimal($attribute, $value, $parameters)
566
    {
567
        if (! $this->validateNumeric($attribute, $value)) {
568
            return false;
569
        }
570
 
571
        $this->requireParameterCount(1, $parameters, 'decimal');
572
 
573
        $matches = [];
574
 
575
        preg_match('/^[+-]?\d*.(\d*)$/', $value, $matches);
576
 
577
        $decimals = strlen(end($matches));
578
 
579
        if (! isset($parameters[1])) {
580
            return $decimals == $parameters[0];
581
        }
582
 
583
        return $decimals >= $parameters[0] &&
584
               $decimals <= $parameters[1];
585
    }
586
 
587
    /**
588
     * Validate that an attribute is different from another attribute.
589
     *
590
     * @param  string  $attribute
591
     * @param  mixed  $value
592
     * @param  array<int, int|string>  $parameters
593
     * @return bool
594
     */
595
    public function validateDifferent($attribute, $value, $parameters)
596
    {
597
        $this->requireParameterCount(1, $parameters, 'different');
598
 
599
        foreach ($parameters as $parameter) {
600
            if (Arr::has($this->data, $parameter)) {
601
                $other = Arr::get($this->data, $parameter);
602
 
603
                if ($value === $other) {
604
                    return false;
605
                }
606
            }
607
        }
608
 
609
        return true;
610
    }
611
 
612
    /**
613
     * Validate that an attribute has a given number of digits.
614
     *
615
     * @param  string  $attribute
616
     * @param  mixed  $value
617
     * @param  array<int, int|string>  $parameters
618
     * @return bool
619
     */
620
    public function validateDigits($attribute, $value, $parameters)
621
    {
622
        $this->requireParameterCount(1, $parameters, 'digits');
623
 
624
        return ! preg_match('/[^0-9]/', $value)
625
                    && strlen((string) $value) == $parameters[0];
626
    }
627
 
628
    /**
629
     * Validate that an attribute is between a given number of digits.
630
     *
631
     * @param  string  $attribute
632
     * @param  mixed  $value
633
     * @param  array<int, int|string>  $parameters
634
     * @return bool
635
     */
636
    public function validateDigitsBetween($attribute, $value, $parameters)
637
    {
638
        $this->requireParameterCount(2, $parameters, 'digits_between');
639
 
640
        $length = strlen((string) $value);
641
 
642
        return ! preg_match('/[^0-9]/', $value)
643
                    && $length >= $parameters[0] && $length <= $parameters[1];
644
    }
645
 
646
    /**
647
     * Validate the dimensions of an image matches the given values.
648
     *
649
     * @param  string  $attribute
650
     * @param  mixed  $value
651
     * @param  array<int, int|string>  $parameters
652
     * @return bool
653
     */
654
    public function validateDimensions($attribute, $value, $parameters)
655
    {
656
        if ($this->isValidFileInstance($value) && in_array($value->getMimeType(), ['image/svg+xml', 'image/svg'])) {
657
            return true;
658
        }
659
 
660
        if (! $this->isValidFileInstance($value) || ! $sizeDetails = @getimagesize($value->getRealPath())) {
661
            return false;
662
        }
663
 
664
        $this->requireParameterCount(1, $parameters, 'dimensions');
665
 
666
        [$width, $height] = $sizeDetails;
667
 
668
        $parameters = $this->parseNamedParameters($parameters);
669
 
670
        if ($this->failsBasicDimensionChecks($parameters, $width, $height) ||
671
            $this->failsRatioCheck($parameters, $width, $height)) {
672
            return false;
673
        }
674
 
675
        return true;
676
    }
677
 
678
    /**
679
     * Test if the given width and height fail any conditions.
680
     *
681
     * @param  array<string,string>  $parameters
682
     * @param  int  $width
683
     * @param  int  $height
684
     * @return bool
685
     */
686
    protected function failsBasicDimensionChecks($parameters, $width, $height)
687
    {
688
        return (isset($parameters['width']) && $parameters['width'] != $width) ||
689
               (isset($parameters['min_width']) && $parameters['min_width'] > $width) ||
690
               (isset($parameters['max_width']) && $parameters['max_width'] < $width) ||
691
               (isset($parameters['height']) && $parameters['height'] != $height) ||
692
               (isset($parameters['min_height']) && $parameters['min_height'] > $height) ||
693
               (isset($parameters['max_height']) && $parameters['max_height'] < $height);
694
    }
695
 
696
    /**
697
     * Determine if the given parameters fail a dimension ratio check.
698
     *
699
     * @param  array<string,string>  $parameters
700
     * @param  int  $width
701
     * @param  int  $height
702
     * @return bool
703
     */
704
    protected function failsRatioCheck($parameters, $width, $height)
705
    {
706
        if (! isset($parameters['ratio'])) {
707
            return false;
708
        }
709
 
710
        [$numerator, $denominator] = array_replace(
711
            [1, 1], array_filter(sscanf($parameters['ratio'], '%f/%d'))
712
        );
713
 
714
        $precision = 1 / (max($width, $height) + 1);
715
 
716
        return abs($numerator / $denominator - $width / $height) > $precision;
717
    }
718
 
719
    /**
720
     * Validate an attribute is unique among other values.
721
     *
722
     * @param  string  $attribute
723
     * @param  mixed  $value
724
     * @param  array<int, int|string>  $parameters
725
     * @return bool
726
     */
727
    public function validateDistinct($attribute, $value, $parameters)
728
    {
729
        $data = Arr::except($this->getDistinctValues($attribute), $attribute);
730
 
731
        if (in_array('ignore_case', $parameters)) {
732
            return empty(preg_grep('/^'.preg_quote($value, '/').'$/iu', $data));
733
        }
734
 
735
        return ! in_array($value, array_values($data), in_array('strict', $parameters));
736
    }
737
 
738
    /**
739
     * Get the values to distinct between.
740
     *
741
     * @param  string  $attribute
742
     * @return array
743
     */
744
    protected function getDistinctValues($attribute)
745
    {
746
        $attributeName = $this->getPrimaryAttribute($attribute);
747
 
748
        if (! property_exists($this, 'distinctValues')) {
749
            return $this->extractDistinctValues($attributeName);
750
        }
751
 
752
        if (! array_key_exists($attributeName, $this->distinctValues)) {
753
            $this->distinctValues[$attributeName] = $this->extractDistinctValues($attributeName);
754
        }
755
 
756
        return $this->distinctValues[$attributeName];
757
    }
758
 
759
    /**
760
     * Extract the distinct values from the data.
761
     *
762
     * @param  string  $attribute
763
     * @return array
764
     */
765
    protected function extractDistinctValues($attribute)
766
    {
767
        $attributeData = ValidationData::extractDataFromPath(
768
            ValidationData::getLeadingExplicitAttributePath($attribute), $this->data
769
        );
770
 
771
        $pattern = str_replace('\*', '[^.]+', preg_quote($attribute, '#'));
772
 
773
        return Arr::where(Arr::dot($attributeData), function ($value, $key) use ($pattern) {
774
            return (bool) preg_match('#^'.$pattern.'\z#u', $key);
775
        });
776
    }
777
 
778
    /**
779
     * Validate that an attribute is a valid e-mail address.
780
     *
781
     * @param  string  $attribute
782
     * @param  mixed  $value
783
     * @param  array<int, int|string>  $parameters
784
     * @return bool
785
     */
786
    public function validateEmail($attribute, $value, $parameters)
787
    {
788
        if (! is_string($value) && ! (is_object($value) && method_exists($value, '__toString'))) {
789
            return false;
790
        }
791
 
792
        $validations = collect($parameters)
793
            ->unique()
794
            ->map(fn ($validation) => match (true) {
795
                $validation === 'strict' => new NoRFCWarningsValidation(),
796
                $validation === 'dns' => new DNSCheckValidation(),
797
                $validation === 'spoof' => new SpoofCheckValidation(),
798
                $validation === 'filter' => new FilterEmailValidation(),
799
                $validation === 'filter_unicode' => FilterEmailValidation::unicode(),
800
                is_string($validation) && class_exists($validation) => $this->container->make($validation),
801
                default => new RFCValidation(),
802
            })
803
            ->values()
804
            ->all() ?: [new RFCValidation];
805
 
806
        return (new EmailValidator)->isValid($value, new MultipleValidationWithAnd($validations));
807
    }
808
 
809
    /**
810
     * Validate the existence of an attribute value in a database table.
811
     *
812
     * @param  string  $attribute
813
     * @param  mixed  $value
814
     * @param  array<int, int|string>  $parameters
815
     * @return bool
816
     */
817
    public function validateExists($attribute, $value, $parameters)
818
    {
819
        $this->requireParameterCount(1, $parameters, 'exists');
820
 
821
        [$connection, $table] = $this->parseTable($parameters[0]);
822
 
823
        // The second parameter position holds the name of the column that should be
824
        // verified as existing. If this parameter is not specified we will guess
825
        // that the columns being "verified" shares the given attribute's name.
826
        $column = $this->getQueryColumn($parameters, $attribute);
827
 
828
        $expected = is_array($value) ? count(array_unique($value)) : 1;
829
 
830
        return $this->getExistCount(
831
            $connection, $table, $column, $value, $parameters
832
        ) >= $expected;
833
    }
834
 
835
    /**
836
     * Get the number of records that exist in storage.
837
     *
838
     * @param  mixed  $connection
839
     * @param  string  $table
840
     * @param  string  $column
841
     * @param  mixed  $value
842
     * @param  array<int, int|string>  $parameters
843
     * @return int
844
     */
845
    protected function getExistCount($connection, $table, $column, $value, $parameters)
846
    {
847
        $verifier = $this->getPresenceVerifier($connection);
848
 
849
        $extra = $this->getExtraConditions(
850
            array_values(array_slice($parameters, 2))
851
        );
852
 
853
        if ($this->currentRule instanceof Exists) {
854
            $extra = array_merge($extra, $this->currentRule->queryCallbacks());
855
        }
856
 
857
        return is_array($value)
858
                ? $verifier->getMultiCount($table, $column, $value, $extra)
859
                : $verifier->getCount($table, $column, $value, null, null, $extra);
860
    }
861
 
862
    /**
863
     * Validate the uniqueness of an attribute value on a given database table.
864
     *
865
     * If a database column is not specified, the attribute will be used.
866
     *
867
     * @param  string  $attribute
868
     * @param  mixed  $value
869
     * @param  array<int, int|string>  $parameters
870
     * @return bool
871
     */
872
    public function validateUnique($attribute, $value, $parameters)
873
    {
874
        $this->requireParameterCount(1, $parameters, 'unique');
875
 
876
        [$connection, $table, $idColumn] = $this->parseTable($parameters[0]);
877
 
878
        // The second parameter position holds the name of the column that needs to
879
        // be verified as unique. If this parameter isn't specified we will just
880
        // assume that this column to be verified shares the attribute's name.
881
        $column = $this->getQueryColumn($parameters, $attribute);
882
 
883
        $id = null;
884
 
885
        if (isset($parameters[2])) {
886
            [$idColumn, $id] = $this->getUniqueIds($idColumn, $parameters);
887
 
888
            if (! is_null($id)) {
889
                $id = stripslashes($id);
890
            }
891
        }
892
 
893
        // The presence verifier is responsible for counting rows within this store
894
        // mechanism which might be a relational database or any other permanent
895
        // data store like Redis, etc. We will use it to determine uniqueness.
896
        $verifier = $this->getPresenceVerifier($connection);
897
 
898
        $extra = $this->getUniqueExtra($parameters);
899
 
900
        if ($this->currentRule instanceof Unique) {
901
            $extra = array_merge($extra, $this->currentRule->queryCallbacks());
902
        }
903
 
904
        return $verifier->getCount(
905
            $table, $column, $value, $id, $idColumn, $extra
906
        ) == 0;
907
    }
908
 
909
    /**
910
     * Get the excluded ID column and value for the unique rule.
911
     *
912
     * @param  string|null  $idColumn
913
     * @param  array<int, int|string>  $parameters
914
     * @return array
915
     */
916
    protected function getUniqueIds($idColumn, $parameters)
917
    {
918
        $idColumn ??= $parameters[3] ?? 'id';
919
 
920
        return [$idColumn, $this->prepareUniqueId($parameters[2])];
921
    }
922
 
923
    /**
924
     * Prepare the given ID for querying.
925
     *
926
     * @param  mixed  $id
927
     * @return int
928
     */
929
    protected function prepareUniqueId($id)
930
    {
931
        if (preg_match('/\[(.*)\]/', $id, $matches)) {
932
            $id = $this->getValue($matches[1]);
933
        }
934
 
935
        if (strtolower($id) === 'null') {
936
            $id = null;
937
        }
938
 
939
        if (filter_var($id, FILTER_VALIDATE_INT) !== false) {
940
            $id = (int) $id;
941
        }
942
 
943
        return $id;
944
    }
945
 
946
    /**
947
     * Get the extra conditions for a unique rule.
948
     *
949
     * @param  array<int, int|string>  $parameters
950
     * @return array
951
     */
952
    protected function getUniqueExtra($parameters)
953
    {
954
        if (isset($parameters[4])) {
955
            return $this->getExtraConditions(array_slice($parameters, 4));
956
        }
957
 
958
        return [];
959
    }
960
 
961
    /**
962
     * Parse the connection / table for the unique / exists rules.
963
     *
964
     * @param  string  $table
965
     * @return array
966
     */
967
    public function parseTable($table)
968
    {
969
        [$connection, $table] = str_contains($table, '.') ? explode('.', $table, 2) : [null, $table];
970
 
971
        if (str_contains($table, '\\') && class_exists($table) && is_a($table, Model::class, true)) {
972
            $model = new $table;
973
 
974
            $table = $model->getTable();
975
            $connection ??= $model->getConnectionName();
976
 
977
            if (str_contains($table, '.') && Str::startsWith($table, $connection)) {
978
                $connection = null;
979
            }
980
 
981
            $idColumn = $model->getKeyName();
982
        }
983
 
984
        return [$connection, $table, $idColumn ?? null];
985
    }
986
 
987
    /**
988
     * Get the column name for an exists / unique query.
989
     *
990
     * @param  array<int, int|string>  $parameters
991
     * @param  string  $attribute
992
     * @return bool
993
     */
994
    public function getQueryColumn($parameters, $attribute)
995
    {
996
        return isset($parameters[1]) && $parameters[1] !== 'NULL'
997
                    ? $parameters[1] : $this->guessColumnForQuery($attribute);
998
    }
999
 
1000
    /**
1001
     * Guess the database column from the given attribute name.
1002
     *
1003
     * @param  string  $attribute
1004
     * @return string
1005
     */
1006
    public function guessColumnForQuery($attribute)
1007
    {
1008
        if (in_array($attribute, Arr::collapse($this->implicitAttributes))
1009
                && ! is_numeric($last = last(explode('.', $attribute)))) {
1010
            return $last;
1011
        }
1012
 
1013
        return $attribute;
1014
    }
1015
 
1016
    /**
1017
     * Get the extra conditions for a unique / exists rule.
1018
     *
1019
     * @param  array  $segments
1020
     * @return array
1021
     */
1022
    protected function getExtraConditions(array $segments)
1023
    {
1024
        $extra = [];
1025
 
1026
        $count = count($segments);
1027
 
1028
        for ($i = 0; $i < $count; $i += 2) {
1029
            $extra[$segments[$i]] = $segments[$i + 1];
1030
        }
1031
 
1032
        return $extra;
1033
    }
1034
 
1035
    /**
1036
     * Validate the given value is a valid file.
1037
     *
1038
     * @param  string  $attribute
1039
     * @param  mixed  $value
1040
     * @return bool
1041
     */
1042
    public function validateFile($attribute, $value)
1043
    {
1044
        return $this->isValidFileInstance($value);
1045
    }
1046
 
1047
    /**
1048
     * Validate the given attribute is filled if it is present.
1049
     *
1050
     * @param  string  $attribute
1051
     * @param  mixed  $value
1052
     * @return bool
1053
     */
1054
    public function validateFilled($attribute, $value)
1055
    {
1056
        if (Arr::has($this->data, $attribute)) {
1057
            return $this->validateRequired($attribute, $value);
1058
        }
1059
 
1060
        return true;
1061
    }
1062
 
1063
    /**
1064
     * Validate that an attribute is greater than another attribute.
1065
     *
1066
     * @param  string  $attribute
1067
     * @param  mixed  $value
1068
     * @param  array<int, int|string>  $parameters
1069
     * @return bool
1070
     */
1071
    public function validateGt($attribute, $value, $parameters)
1072
    {
1073
        $this->requireParameterCount(1, $parameters, 'gt');
1074
 
1075
        $comparedToValue = $this->getValue($parameters[0]);
1076
 
1077
        $this->shouldBeNumeric($attribute, 'Gt');
1078
 
1079
        if (is_null($comparedToValue) && (is_numeric($value) && is_numeric($parameters[0]))) {
1080
            return $this->getSize($attribute, $value) > $parameters[0];
1081
        }
1082
 
1083
        if (is_numeric($parameters[0])) {
1084
            return false;
1085
        }
1086
 
1087
        if ($this->hasRule($attribute, $this->numericRules) && is_numeric($value) && is_numeric($comparedToValue)) {
1088
            return $value > $comparedToValue;
1089
        }
1090
 
1091
        if (! $this->isSameType($value, $comparedToValue)) {
1092
            return false;
1093
        }
1094
 
1095
        return $this->getSize($attribute, $value) > $this->getSize($attribute, $comparedToValue);
1096
    }
1097
 
1098
    /**
1099
     * Validate that an attribute is less than another attribute.
1100
     *
1101
     * @param  string  $attribute
1102
     * @param  mixed  $value
1103
     * @param  array<int, int|string>  $parameters
1104
     * @return bool
1105
     */
1106
    public function validateLt($attribute, $value, $parameters)
1107
    {
1108
        $this->requireParameterCount(1, $parameters, 'lt');
1109
 
1110
        $comparedToValue = $this->getValue($parameters[0]);
1111
 
1112
        $this->shouldBeNumeric($attribute, 'Lt');
1113
 
1114
        if (is_null($comparedToValue) && (is_numeric($value) && is_numeric($parameters[0]))) {
1115
            return $this->getSize($attribute, $value) < $parameters[0];
1116
        }
1117
 
1118
        if (is_numeric($parameters[0])) {
1119
            return false;
1120
        }
1121
 
1122
        if ($this->hasRule($attribute, $this->numericRules) && is_numeric($value) && is_numeric($comparedToValue)) {
1123
            return $value < $comparedToValue;
1124
        }
1125
 
1126
        if (! $this->isSameType($value, $comparedToValue)) {
1127
            return false;
1128
        }
1129
 
1130
        return $this->getSize($attribute, $value) < $this->getSize($attribute, $comparedToValue);
1131
    }
1132
 
1133
    /**
1134
     * Validate that an attribute is greater than or equal another attribute.
1135
     *
1136
     * @param  string  $attribute
1137
     * @param  mixed  $value
1138
     * @param  array<int, int|string>  $parameters
1139
     * @return bool
1140
     */
1141
    public function validateGte($attribute, $value, $parameters)
1142
    {
1143
        $this->requireParameterCount(1, $parameters, 'gte');
1144
 
1145
        $comparedToValue = $this->getValue($parameters[0]);
1146
 
1147
        $this->shouldBeNumeric($attribute, 'Gte');
1148
 
1149
        if (is_null($comparedToValue) && (is_numeric($value) && is_numeric($parameters[0]))) {
1150
            return $this->getSize($attribute, $value) >= $parameters[0];
1151
        }
1152
 
1153
        if (is_numeric($parameters[0])) {
1154
            return false;
1155
        }
1156
 
1157
        if ($this->hasRule($attribute, $this->numericRules) && is_numeric($value) && is_numeric($comparedToValue)) {
1158
            return $value >= $comparedToValue;
1159
        }
1160
 
1161
        if (! $this->isSameType($value, $comparedToValue)) {
1162
            return false;
1163
        }
1164
 
1165
        return $this->getSize($attribute, $value) >= $this->getSize($attribute, $comparedToValue);
1166
    }
1167
 
1168
    /**
1169
     * Validate that an attribute is less than or equal another attribute.
1170
     *
1171
     * @param  string  $attribute
1172
     * @param  mixed  $value
1173
     * @param  array<int, int|string>  $parameters
1174
     * @return bool
1175
     */
1176
    public function validateLte($attribute, $value, $parameters)
1177
    {
1178
        $this->requireParameterCount(1, $parameters, 'lte');
1179
 
1180
        $comparedToValue = $this->getValue($parameters[0]);
1181
 
1182
        $this->shouldBeNumeric($attribute, 'Lte');
1183
 
1184
        if (is_null($comparedToValue) && (is_numeric($value) && is_numeric($parameters[0]))) {
1185
            return $this->getSize($attribute, $value) <= $parameters[0];
1186
        }
1187
 
1188
        if (is_numeric($parameters[0])) {
1189
            return false;
1190
        }
1191
 
1192
        if ($this->hasRule($attribute, $this->numericRules) && is_numeric($value) && is_numeric($comparedToValue)) {
1193
            return $value <= $comparedToValue;
1194
        }
1195
 
1196
        if (! $this->isSameType($value, $comparedToValue)) {
1197
            return false;
1198
        }
1199
 
1200
        return $this->getSize($attribute, $value) <= $this->getSize($attribute, $comparedToValue);
1201
    }
1202
 
1203
    /**
1204
     * Validate that an attribute is lowercase.
1205
     *
1206
     * @param  string  $attribute
1207
     * @param  mixed  $value
1208
     * @param  array<int, int|string>  $parameters
1209
     * @return bool
1210
     */
1211
    public function validateLowercase($attribute, $value, $parameters)
1212
    {
1213
        return Str::lower($value) === $value;
1214
    }
1215
 
1216
    /**
1217
     * Validate that an attribute is uppercase.
1218
     *
1219
     * @param  string  $attribute
1220
     * @param  mixed  $value
1221
     * @param  array<int, int|string>  $parameters
1222
     * @return bool
1223
     */
1224
    public function validateUppercase($attribute, $value, $parameters)
1225
    {
1226
        return Str::upper($value) === $value;
1227
    }
1228
 
1229
    /**
1230
     * Validate the MIME type of a file is an image MIME type.
1231
     *
1232
     * @param  string  $attribute
1233
     * @param  mixed  $value
1234
     * @return bool
1235
     */
1236
    public function validateImage($attribute, $value)
1237
    {
1238
        return $this->validateMimes($attribute, $value, ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp']);
1239
    }
1240
 
1241
    /**
1242
     * Validate an attribute is contained within a list of values.
1243
     *
1244
     * @param  string  $attribute
1245
     * @param  mixed  $value
1246
     * @param  array<int, int|string>  $parameters
1247
     * @return bool
1248
     */
1249
    public function validateIn($attribute, $value, $parameters)
1250
    {
1251
        if (is_array($value) && $this->hasRule($attribute, 'Array')) {
1252
            foreach ($value as $element) {
1253
                if (is_array($element)) {
1254
                    return false;
1255
                }
1256
            }
1257
 
1258
            return count(array_diff($value, $parameters)) === 0;
1259
        }
1260
 
1261
        return ! is_array($value) && in_array((string) $value, $parameters);
1262
    }
1263
 
1264
    /**
1265
     * Validate that the values of an attribute are in another attribute.
1266
     *
1267
     * @param  string  $attribute
1268
     * @param  mixed  $value
1269
     * @param  array<int, int|string>  $parameters
1270
     * @return bool
1271
     */
1272
    public function validateInArray($attribute, $value, $parameters)
1273
    {
1274
        $this->requireParameterCount(1, $parameters, 'in_array');
1275
 
1276
        $explicitPath = ValidationData::getLeadingExplicitAttributePath($parameters[0]);
1277
 
1278
        $attributeData = ValidationData::extractDataFromPath($explicitPath, $this->data);
1279
 
1280
        $otherValues = Arr::where(Arr::dot($attributeData), function ($value, $key) use ($parameters) {
1281
            return Str::is($parameters[0], $key);
1282
        });
1283
 
1284
        return in_array($value, $otherValues);
1285
    }
1286
 
1287
    /**
1288
     * Validate that an attribute is an integer.
1289
     *
1290
     * @param  string  $attribute
1291
     * @param  mixed  $value
1292
     * @return bool
1293
     */
1294
    public function validateInteger($attribute, $value)
1295
    {
1296
        return filter_var($value, FILTER_VALIDATE_INT) !== false;
1297
    }
1298
 
1299
    /**
1300
     * Validate that an attribute is a valid IP.
1301
     *
1302
     * @param  string  $attribute
1303
     * @param  mixed  $value
1304
     * @return bool
1305
     */
1306
    public function validateIp($attribute, $value)
1307
    {
1308
        return filter_var($value, FILTER_VALIDATE_IP) !== false;
1309
    }
1310
 
1311
    /**
1312
     * Validate that an attribute is a valid IPv4.
1313
     *
1314
     * @param  string  $attribute
1315
     * @param  mixed  $value
1316
     * @return bool
1317
     */
1318
    public function validateIpv4($attribute, $value)
1319
    {
1320
        return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
1321
    }
1322
 
1323
    /**
1324
     * Validate that an attribute is a valid IPv6.
1325
     *
1326
     * @param  string  $attribute
1327
     * @param  mixed  $value
1328
     * @return bool
1329
     */
1330
    public function validateIpv6($attribute, $value)
1331
    {
1332
        return filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
1333
    }
1334
 
1335
    /**
1336
     * Validate that an attribute is a valid MAC address.
1337
     *
1338
     * @param  string  $attribute
1339
     * @param  mixed  $value
1340
     * @return bool
1341
     */
1342
    public function validateMacAddress($attribute, $value)
1343
    {
1344
        return filter_var($value, FILTER_VALIDATE_MAC) !== false;
1345
    }
1346
 
1347
    /**
1348
     * Validate the attribute is a valid JSON string.
1349
     *
1350
     * @param  string  $attribute
1351
     * @param  mixed  $value
1352
     * @return bool
1353
     */
1354
    public function validateJson($attribute, $value)
1355
    {
1356
        if (is_array($value)) {
1357
            return false;
1358
        }
1359
 
1360
        if (! is_scalar($value) && ! is_null($value) && ! method_exists($value, '__toString')) {
1361
            return false;
1362
        }
1363
 
1364
        json_decode($value);
1365
 
1366
        return json_last_error() === JSON_ERROR_NONE;
1367
    }
1368
 
1369
    /**
1370
     * Validate the size of an attribute is less than a maximum value.
1371
     *
1372
     * @param  string  $attribute
1373
     * @param  mixed  $value
1374
     * @param  array<int, int|string>  $parameters
1375
     * @return bool
1376
     */
1377
    public function validateMax($attribute, $value, $parameters)
1378
    {
1379
        $this->requireParameterCount(1, $parameters, 'max');
1380
 
1381
        if ($value instanceof UploadedFile && ! $value->isValid()) {
1382
            return false;
1383
        }
1384
 
1385
        return $this->getSize($attribute, $value) <= $parameters[0];
1386
    }
1387
 
1388
    /**
1389
     * Validate that an attribute has a maximum number of digits.
1390
     *
1391
     * @param  string  $attribute
1392
     * @param  mixed  $value
1393
     * @param  array<int, int|string>  $parameters
1394
     * @return bool
1395
     */
1396
    public function validateMaxDigits($attribute, $value, $parameters)
1397
    {
1398
        $this->requireParameterCount(1, $parameters, 'max');
1399
 
1400
        $length = strlen((string) $value);
1401
 
1402
        return ! preg_match('/[^0-9]/', $value) && $length <= $parameters[0];
1403
    }
1404
 
1405
    /**
1406
     * Validate the guessed extension of a file upload is in a set of file extensions.
1407
     *
1408
     * @param  string  $attribute
1409
     * @param  mixed  $value
1410
     * @param  array<int, int|string>  $parameters
1411
     * @return bool
1412
     */
1413
    public function validateMimes($attribute, $value, $parameters)
1414
    {
1415
        if (! $this->isValidFileInstance($value)) {
1416
            return false;
1417
        }
1418
 
1419
        if ($this->shouldBlockPhpUpload($value, $parameters)) {
1420
            return false;
1421
        }
1422
 
1423
        if (in_array('jpg', $parameters) || in_array('jpeg', $parameters)) {
1424
            $parameters = array_unique(array_merge($parameters, ['jpg', 'jpeg']));
1425
        }
1426
 
1427
        return $value->getPath() !== '' && in_array($value->guessExtension(), $parameters);
1428
    }
1429
 
1430
    /**
1431
     * Validate the MIME type of a file upload attribute is in a set of MIME types.
1432
     *
1433
     * @param  string  $attribute
1434
     * @param  mixed  $value
1435
     * @param  array<int, int|string>  $parameters
1436
     * @return bool
1437
     */
1438
    public function validateMimetypes($attribute, $value, $parameters)
1439
    {
1440
        if (! $this->isValidFileInstance($value)) {
1441
            return false;
1442
        }
1443
 
1444
        if ($this->shouldBlockPhpUpload($value, $parameters)) {
1445
            return false;
1446
        }
1447
 
1448
        return $value->getPath() !== '' &&
1449
                (in_array($value->getMimeType(), $parameters) ||
1450
                 in_array(explode('/', $value->getMimeType())[0].'/*', $parameters));
1451
    }
1452
 
1453
    /**
1454
     * Check if PHP uploads are explicitly allowed.
1455
     *
1456
     * @param  mixed  $value
1457
     * @param  array<int, int|string>  $parameters
1458
     * @return bool
1459
     */
1460
    protected function shouldBlockPhpUpload($value, $parameters)
1461
    {
1462
        if (in_array('php', $parameters)) {
1463
            return false;
1464
        }
1465
 
1466
        $phpExtensions = [
1467
            'php', 'php3', 'php4', 'php5', 'php7', 'php8', 'phtml', 'phar',
1468
        ];
1469
 
1470
        return ($value instanceof UploadedFile)
1471
           ? in_array(trim(strtolower($value->getClientOriginalExtension())), $phpExtensions)
1472
           : in_array(trim(strtolower($value->getExtension())), $phpExtensions);
1473
    }
1474
 
1475
    /**
1476
     * Validate the size of an attribute is greater than a minimum value.
1477
     *
1478
     * @param  string  $attribute
1479
     * @param  mixed  $value
1480
     * @param  array<int, int|string>  $parameters
1481
     * @return bool
1482
     */
1483
    public function validateMin($attribute, $value, $parameters)
1484
    {
1485
        $this->requireParameterCount(1, $parameters, 'min');
1486
 
1487
        return $this->getSize($attribute, $value) >= $parameters[0];
1488
    }
1489
 
1490
    /**
1491
     * Validate that an attribute has a minimum number of digits.
1492
     *
1493
     * @param  string  $attribute
1494
     * @param  mixed  $value
1495
     * @param  array<int, int|string>  $parameters
1496
     * @return bool
1497
     */
1498
    public function validateMinDigits($attribute, $value, $parameters)
1499
    {
1500
        $this->requireParameterCount(1, $parameters, 'min');
1501
 
1502
        $length = strlen((string) $value);
1503
 
1504
        return ! preg_match('/[^0-9]/', $value) && $length >= $parameters[0];
1505
    }
1506
 
1507
    /**
1508
     * Validate the value of an attribute is a multiple of a given value.
1509
     *
1510
     * @param  string  $attribute
1511
     * @param  mixed  $value
1512
     * @param  array<int, int|string>  $parameters
1513
     * @return bool
1514
     */
1515
    public function validateMultipleOf($attribute, $value, $parameters)
1516
    {
1517
        $this->requireParameterCount(1, $parameters, 'multiple_of');
1518
 
1519
        if (! $this->validateNumeric($attribute, $value) || ! $this->validateNumeric($attribute, $parameters[0])) {
1520
            return false;
1521
        }
1522
 
1523
        if ((float) $parameters[0] === 0.0) {
1524
            return false;
1525
        }
1526
 
1527
        return bcmod($value, $parameters[0], 16) === '0.0000000000000000';
1528
    }
1529
 
1530
    /**
1531
     * "Indicate" validation should pass if value is null.
1532
     *
1533
     * Always returns true, just lets us put "nullable" in rules.
1534
     *
1535
     * @return bool
1536
     */
1537
    public function validateNullable()
1538
    {
1539
        return true;
1540
    }
1541
 
1542
    /**
1543
     * Validate an attribute is not contained within a list of values.
1544
     *
1545
     * @param  string  $attribute
1546
     * @param  mixed  $value
1547
     * @param  array<int, int|string>  $parameters
1548
     * @return bool
1549
     */
1550
    public function validateNotIn($attribute, $value, $parameters)
1551
    {
1552
        return ! $this->validateIn($attribute, $value, $parameters);
1553
    }
1554
 
1555
    /**
1556
     * Validate that an attribute is numeric.
1557
     *
1558
     * @param  string  $attribute
1559
     * @param  mixed  $value
1560
     * @return bool
1561
     */
1562
    public function validateNumeric($attribute, $value)
1563
    {
1564
        return is_numeric($value);
1565
    }
1566
 
1567
    /**
1568
     * Validate that an attribute exists even if not filled.
1569
     *
1570
     * @param  string  $attribute
1571
     * @param  mixed  $value
1572
     * @return bool
1573
     */
1574
    public function validatePresent($attribute, $value)
1575
    {
1576
        return Arr::has($this->data, $attribute);
1577
    }
1578
 
1579
    /**
1580
     * Validate that an attribute passes a regular expression check.
1581
     *
1582
     * @param  string  $attribute
1583
     * @param  mixed  $value
1584
     * @param  array<int, int|string>  $parameters
1585
     * @return bool
1586
     */
1587
    public function validateRegex($attribute, $value, $parameters)
1588
    {
1589
        if (! is_string($value) && ! is_numeric($value)) {
1590
            return false;
1591
        }
1592
 
1593
        $this->requireParameterCount(1, $parameters, 'regex');
1594
 
1595
        return preg_match($parameters[0], $value) > 0;
1596
    }
1597
 
1598
    /**
1599
     * Validate that an attribute does not pass a regular expression check.
1600
     *
1601
     * @param  string  $attribute
1602
     * @param  mixed  $value
1603
     * @param  array<int, int|string>  $parameters
1604
     * @return bool
1605
     */
1606
    public function validateNotRegex($attribute, $value, $parameters)
1607
    {
1608
        if (! is_string($value) && ! is_numeric($value)) {
1609
            return false;
1610
        }
1611
 
1612
        $this->requireParameterCount(1, $parameters, 'not_regex');
1613
 
1614
        return preg_match($parameters[0], $value) < 1;
1615
    }
1616
 
1617
    /**
1618
     * Validate that a required attribute exists.
1619
     *
1620
     * @param  string  $attribute
1621
     * @param  mixed  $value
1622
     * @return bool
1623
     */
1624
    public function validateRequired($attribute, $value)
1625
    {
1626
        if (is_null($value)) {
1627
            return false;
1628
        } elseif (is_string($value) && trim($value) === '') {
1629
            return false;
1630
        } elseif (is_countable($value) && count($value) < 1) {
1631
            return false;
1632
        } elseif ($value instanceof File) {
1633
            return (string) $value->getPath() !== '';
1634
        }
1635
 
1636
        return true;
1637
    }
1638
 
1639
    /**
1640
     * Validate that an attribute exists when another attribute has a given value.
1641
     *
1642
     * @param  string  $attribute
1643
     * @param  mixed  $value
1644
     * @param  mixed  $parameters
1645
     * @return bool
1646
     */
1647
    public function validateRequiredIf($attribute, $value, $parameters)
1648
    {
1649
        $this->requireParameterCount(2, $parameters, 'required_if');
1650
 
1651
        if (! Arr::has($this->data, $parameters[0])) {
1652
            return true;
1653
        }
1654
 
1655
        [$values, $other] = $this->parseDependentRuleParameters($parameters);
1656
 
1657
        if (in_array($other, $values, is_bool($other) || is_null($other))) {
1658
            return $this->validateRequired($attribute, $value);
1659
        }
1660
 
1661
        return true;
1662
    }
1663
 
1664
    /**
1665
     * Validate that an attribute exists when another attribute was "accepted".
1666
     *
1667
     * @param  string  $attribute
1668
     * @param  mixed  $value
1669
     * @param  mixed  $parameters
1670
     * @return bool
1671
     */
1672
    public function validateRequiredIfAccepted($attribute, $value, $parameters)
1673
    {
1674
        $this->requireParameterCount(1, $parameters, 'required_if_accepted');
1675
 
1676
        if ($this->validateAccepted($parameters[0], $this->getValue($parameters[0]))) {
1677
            return $this->validateRequired($attribute, $value);
1678
        }
1679
 
1680
        return true;
1681
    }
1682
 
1683
    /**
150 lars 1684
     * Validate that an attribute does not exist or is an empty string.
148 lars 1685
     *
1686
     * @param  string  $attribute
1687
     * @param  mixed  $value
1688
     * @return bool
1689
     */
1690
    public function validateProhibited($attribute, $value)
1691
    {
1692
        return false;
1693
    }
1694
 
1695
    /**
1696
     * Validate that an attribute does not exist when another attribute has a given value.
1697
     *
1698
     * @param  string  $attribute
1699
     * @param  mixed  $value
1700
     * @param  mixed  $parameters
1701
     * @return bool
1702
     */
1703
    public function validateProhibitedIf($attribute, $value, $parameters)
1704
    {
1705
        $this->requireParameterCount(2, $parameters, 'prohibited_if');
1706
 
1707
        [$values, $other] = $this->parseDependentRuleParameters($parameters);
1708
 
1709
        if (in_array($other, $values, is_bool($other) || is_null($other))) {
1710
            return ! $this->validateRequired($attribute, $value);
1711
        }
1712
 
1713
        return true;
1714
    }
1715
 
1716
    /**
1717
     * Validate that an attribute does not exist unless another attribute has a given value.
1718
     *
1719
     * @param  string  $attribute
1720
     * @param  mixed  $value
1721
     * @param  mixed  $parameters
1722
     * @return bool
1723
     */
1724
    public function validateProhibitedUnless($attribute, $value, $parameters)
1725
    {
1726
        $this->requireParameterCount(2, $parameters, 'prohibited_unless');
1727
 
1728
        [$values, $other] = $this->parseDependentRuleParameters($parameters);
1729
 
1730
        if (! in_array($other, $values, is_bool($other) || is_null($other))) {
1731
            return ! $this->validateRequired($attribute, $value);
1732
        }
1733
 
1734
        return true;
1735
    }
1736
 
1737
    /**
1738
     * Validate that other attributes do not exist when this attribute exists.
1739
     *
1740
     * @param  string  $attribute
1741
     * @param  mixed  $value
1742
     * @param  mixed  $parameters
1743
     * @return bool
1744
     */
1745
    public function validateProhibits($attribute, $value, $parameters)
1746
    {
1747
        return ! Arr::hasAny($this->data, $parameters);
1748
    }
1749
 
1750
    /**
1751
     * Indicate that an attribute is excluded.
1752
     *
1753
     * @return bool
1754
     */
1755
    public function validateExclude()
1756
    {
1757
        return false;
1758
    }
1759
 
1760
    /**
1761
     * Indicate that an attribute should be excluded when another attribute has a given value.
1762
     *
1763
     * @param  string  $attribute
1764
     * @param  mixed  $value
1765
     * @param  mixed  $parameters
1766
     * @return bool
1767
     */
1768
    public function validateExcludeIf($attribute, $value, $parameters)
1769
    {
1770
        $this->requireParameterCount(2, $parameters, 'exclude_if');
1771
 
1772
        if (! Arr::has($this->data, $parameters[0])) {
1773
            return true;
1774
        }
1775
 
1776
        [$values, $other] = $this->parseDependentRuleParameters($parameters);
1777
 
1778
        return ! in_array($other, $values, is_bool($other) || is_null($other));
1779
    }
1780
 
1781
    /**
1782
     * Indicate that an attribute should be excluded when another attribute does not have a given value.
1783
     *
1784
     * @param  string  $attribute
1785
     * @param  mixed  $value
1786
     * @param  mixed  $parameters
1787
     * @return bool
1788
     */
1789
    public function validateExcludeUnless($attribute, $value, $parameters)
1790
    {
1791
        $this->requireParameterCount(2, $parameters, 'exclude_unless');
1792
 
1793
        [$values, $other] = $this->parseDependentRuleParameters($parameters);
1794
 
1795
        return in_array($other, $values, is_bool($other) || is_null($other));
1796
    }
1797
 
1798
    /**
1799
     * Validate that an attribute exists when another attribute does not have a given value.
1800
     *
1801
     * @param  string  $attribute
1802
     * @param  mixed  $value
1803
     * @param  mixed  $parameters
1804
     * @return bool
1805
     */
1806
    public function validateRequiredUnless($attribute, $value, $parameters)
1807
    {
1808
        $this->requireParameterCount(2, $parameters, 'required_unless');
1809
 
1810
        [$values, $other] = $this->parseDependentRuleParameters($parameters);
1811
 
1812
        if (! in_array($other, $values, is_bool($other) || is_null($other))) {
1813
            return $this->validateRequired($attribute, $value);
1814
        }
1815
 
1816
        return true;
1817
    }
1818
 
1819
    /**
1820
     * Indicate that an attribute should be excluded when another attribute presents.
1821
     *
1822
     * @param  string  $attribute
1823
     * @param  mixed  $value
1824
     * @param  mixed  $parameters
1825
     * @return bool
1826
     */
1827
    public function validateExcludeWith($attribute, $value, $parameters)
1828
    {
1829
        $this->requireParameterCount(1, $parameters, 'exclude_with');
1830
 
1831
        if (! Arr::has($this->data, $parameters[0])) {
1832
            return true;
1833
        }
1834
 
1835
        return false;
1836
    }
1837
 
1838
    /**
1839
     * Indicate that an attribute should be excluded when another attribute is missing.
1840
     *
1841
     * @param  string  $attribute
1842
     * @param  mixed  $value
1843
     * @param  mixed  $parameters
1844
     * @return bool
1845
     */
1846
    public function validateExcludeWithout($attribute, $value, $parameters)
1847
    {
1848
        $this->requireParameterCount(1, $parameters, 'exclude_without');
1849
 
1850
        if ($this->anyFailingRequired($parameters)) {
1851
            return false;
1852
        }
1853
 
1854
        return true;
1855
    }
1856
 
1857
    /**
1858
     * Prepare the values and the other value for validation.
1859
     *
1860
     * @param  array<int, int|string>  $parameters
1861
     * @return array
1862
     */
1863
    public function parseDependentRuleParameters($parameters)
1864
    {
1865
        $other = Arr::get($this->data, $parameters[0]);
1866
 
1867
        $values = array_slice($parameters, 1);
1868
 
1869
        if ($this->shouldConvertToBoolean($parameters[0]) || is_bool($other)) {
1870
            $values = $this->convertValuesToBoolean($values);
1871
        }
1872
 
1873
        if (is_null($other)) {
1874
            $values = $this->convertValuesToNull($values);
1875
        }
1876
 
1877
        return [$values, $other];
1878
    }
1879
 
1880
    /**
1881
     * Check if parameter should be converted to boolean.
1882
     *
1883
     * @param  string  $parameter
1884
     * @return bool
1885
     */
1886
    protected function shouldConvertToBoolean($parameter)
1887
    {
1888
        return in_array('boolean', Arr::get($this->rules, $parameter, []));
1889
    }
1890
 
1891
    /**
1892
     * Convert the given values to boolean if they are string "true" / "false".
1893
     *
1894
     * @param  array  $values
1895
     * @return array
1896
     */
1897
    protected function convertValuesToBoolean($values)
1898
    {
1899
        return array_map(function ($value) {
1900
            if ($value === 'true') {
1901
                return true;
1902
            } elseif ($value === 'false') {
1903
                return false;
1904
            }
1905
 
1906
            return $value;
1907
        }, $values);
1908
    }
1909
 
1910
    /**
1911
     * Convert the given values to null if they are string "null".
1912
     *
1913
     * @param  array  $values
1914
     * @return array
1915
     */
1916
    protected function convertValuesToNull($values)
1917
    {
1918
        return array_map(function ($value) {
1919
            return Str::lower($value) === 'null' ? null : $value;
1920
        }, $values);
1921
    }
1922
 
1923
    /**
1924
     * Validate that an attribute exists when any other attribute exists.
1925
     *
1926
     * @param  string  $attribute
1927
     * @param  mixed  $value
1928
     * @param  mixed  $parameters
1929
     * @return bool
1930
     */
1931
    public function validateRequiredWith($attribute, $value, $parameters)
1932
    {
1933
        if (! $this->allFailingRequired($parameters)) {
1934
            return $this->validateRequired($attribute, $value);
1935
        }
1936
 
1937
        return true;
1938
    }
1939
 
1940
    /**
1941
     * Validate that an attribute exists when all other attributes exist.
1942
     *
1943
     * @param  string  $attribute
1944
     * @param  mixed  $value
1945
     * @param  mixed  $parameters
1946
     * @return bool
1947
     */
1948
    public function validateRequiredWithAll($attribute, $value, $parameters)
1949
    {
1950
        if (! $this->anyFailingRequired($parameters)) {
1951
            return $this->validateRequired($attribute, $value);
1952
        }
1953
 
1954
        return true;
1955
    }
1956
 
1957
    /**
1958
     * Validate that an attribute exists when another attribute does not.
1959
     *
1960
     * @param  string  $attribute
1961
     * @param  mixed  $value
1962
     * @param  mixed  $parameters
1963
     * @return bool
1964
     */
1965
    public function validateRequiredWithout($attribute, $value, $parameters)
1966
    {
1967
        if ($this->anyFailingRequired($parameters)) {
1968
            return $this->validateRequired($attribute, $value);
1969
        }
1970
 
1971
        return true;
1972
    }
1973
 
1974
    /**
1975
     * Validate that an attribute exists when all other attributes do not.
1976
     *
1977
     * @param  string  $attribute
1978
     * @param  mixed  $value
1979
     * @param  mixed  $parameters
1980
     * @return bool
1981
     */
1982
    public function validateRequiredWithoutAll($attribute, $value, $parameters)
1983
    {
1984
        if ($this->allFailingRequired($parameters)) {
1985
            return $this->validateRequired($attribute, $value);
1986
        }
1987
 
1988
        return true;
1989
    }
1990
 
1991
    /**
1992
     * Determine if any of the given attributes fail the required test.
1993
     *
1994
     * @param  array  $attributes
1995
     * @return bool
1996
     */
1997
    protected function anyFailingRequired(array $attributes)
1998
    {
1999
        foreach ($attributes as $key) {
2000
            if (! $this->validateRequired($key, $this->getValue($key))) {
2001
                return true;
2002
            }
2003
        }
2004
 
2005
        return false;
2006
    }
2007
 
2008
    /**
2009
     * Determine if all of the given attributes fail the required test.
2010
     *
2011
     * @param  array  $attributes
2012
     * @return bool
2013
     */
2014
    protected function allFailingRequired(array $attributes)
2015
    {
2016
        foreach ($attributes as $key) {
2017
            if ($this->validateRequired($key, $this->getValue($key))) {
2018
                return false;
2019
            }
2020
        }
2021
 
2022
        return true;
2023
    }
2024
 
2025
    /**
2026
     * Validate that two attributes match.
2027
     *
2028
     * @param  string  $attribute
2029
     * @param  mixed  $value
2030
     * @param  array<int, int|string>  $parameters
2031
     * @return bool
2032
     */
2033
    public function validateSame($attribute, $value, $parameters)
2034
    {
2035
        $this->requireParameterCount(1, $parameters, 'same');
2036
 
2037
        $other = Arr::get($this->data, $parameters[0]);
2038
 
2039
        return $value === $other;
2040
    }
2041
 
2042
    /**
2043
     * Validate the size of an attribute.
2044
     *
2045
     * @param  string  $attribute
2046
     * @param  mixed  $value
2047
     * @param  array<int, int|string>  $parameters
2048
     * @return bool
2049
     */
2050
    public function validateSize($attribute, $value, $parameters)
2051
    {
2052
        $this->requireParameterCount(1, $parameters, 'size');
2053
 
2054
        return $this->getSize($attribute, $value) == $parameters[0];
2055
    }
2056
 
2057
    /**
2058
     * "Validate" optional attributes.
2059
     *
2060
     * Always returns true, just lets us put sometimes in rules.
2061
     *
2062
     * @return bool
2063
     */
2064
    public function validateSometimes()
2065
    {
2066
        return true;
2067
    }
2068
 
2069
    /**
2070
     * Validate the attribute starts with a given substring.
2071
     *
2072
     * @param  string  $attribute
2073
     * @param  mixed  $value
2074
     * @param  array<int, int|string>  $parameters
2075
     * @return bool
2076
     */
2077
    public function validateStartsWith($attribute, $value, $parameters)
2078
    {
2079
        return Str::startsWith($value, $parameters);
2080
    }
2081
 
2082
    /**
2083
     * Validate the attribute does not start with a given substring.
2084
     *
2085
     * @param  string  $attribute
2086
     * @param  mixed  $value
2087
     * @param  array<int, int|string>  $parameters
2088
     * @return bool
2089
     */
2090
    public function validateDoesntStartWith($attribute, $value, $parameters)
2091
    {
2092
        return ! Str::startsWith($value, $parameters);
2093
    }
2094
 
2095
    /**
2096
     * Validate the attribute ends with a given substring.
2097
     *
2098
     * @param  string  $attribute
2099
     * @param  mixed  $value
2100
     * @param  array<int, int|string>  $parameters
2101
     * @return bool
2102
     */
2103
    public function validateEndsWith($attribute, $value, $parameters)
2104
    {
2105
        return Str::endsWith($value, $parameters);
2106
    }
2107
 
2108
    /**
2109
     * Validate the attribute does not end with a given substring.
2110
     *
2111
     * @param  string  $attribute
2112
     * @param  mixed  $value
2113
     * @param  array<int, int|string>  $parameters
2114
     * @return bool
2115
     */
2116
    public function validateDoesntEndWith($attribute, $value, $parameters)
2117
    {
2118
        return ! Str::endsWith($value, $parameters);
2119
    }
2120
 
2121
    /**
2122
     * Validate that an attribute is a string.
2123
     *
2124
     * @param  string  $attribute
2125
     * @param  mixed  $value
2126
     * @return bool
2127
     */
2128
    public function validateString($attribute, $value)
2129
    {
2130
        return is_string($value);
2131
    }
2132
 
2133
    /**
2134
     * Validate that an attribute is a valid timezone.
2135
     *
2136
     * @param  string  $attribute
2137
     * @param  mixed  $value
2138
     * @return bool
2139
     */
2140
    public function validateTimezone($attribute, $value)
2141
    {
2142
        return in_array($value, timezone_identifiers_list(), true);
2143
    }
2144
 
2145
    /**
2146
     * Validate that an attribute is a valid URL.
2147
     *
2148
     * @param  string  $attribute
2149
     * @param  mixed  $value
2150
     * @return bool
2151
     */
2152
    public function validateUrl($attribute, $value)
2153
    {
2154
        if (! is_string($value)) {
2155
            return false;
2156
        }
2157
 
2158
        /*
2159
         * This pattern is derived from Symfony\Component\Validator\Constraints\UrlValidator (5.0.7).
2160
         *
2161
         * (c) Fabien Potencier <fabien@symfony.com> http://symfony.com
2162
         */
2163
        $pattern = '~^
2164
            (aaa|aaas|about|acap|acct|acd|acr|adiumxtra|adt|afp|afs|aim|amss|android|appdata|apt|ark|attachment|aw|barion|beshare|bitcoin|bitcoincash|blob|bolo|browserext|calculator|callto|cap|cast|casts|chrome|chrome-extension|cid|coap|coap\+tcp|coap\+ws|coaps|coaps\+tcp|coaps\+ws|com-eventbrite-attendee|content|conti|crid|cvs|dab|data|dav|diaspora|dict|did|dis|dlna-playcontainer|dlna-playsingle|dns|dntp|dpp|drm|drop|dtn|dvb|ed2k|elsi|example|facetime|fax|feed|feedready|file|filesystem|finger|first-run-pen-experience|fish|fm|ftp|fuchsia-pkg|geo|gg|git|gizmoproject|go|gopher|graph|gtalk|h323|ham|hcap|hcp|http|https|hxxp|hxxps|hydrazone|iax|icap|icon|im|imap|info|iotdisco|ipn|ipp|ipps|irc|irc6|ircs|iris|iris\.beep|iris\.lwz|iris\.xpc|iris\.xpcs|isostore|itms|jabber|jar|jms|keyparc|lastfm|ldap|ldaps|leaptofrogans|lorawan|lvlt|magnet|mailserver|mailto|maps|market|message|mid|mms|modem|mongodb|moz|ms-access|ms-browser-extension|ms-calculator|ms-drive-to|ms-enrollment|ms-excel|ms-eyecontrolspeech|ms-gamebarservices|ms-gamingoverlay|ms-getoffice|ms-help|ms-infopath|ms-inputapp|ms-lockscreencomponent-config|ms-media-stream-id|ms-mixedrealitycapture|ms-mobileplans|ms-officeapp|ms-people|ms-project|ms-powerpoint|ms-publisher|ms-restoretabcompanion|ms-screenclip|ms-screensketch|ms-search|ms-search-repair|ms-secondary-screen-controller|ms-secondary-screen-setup|ms-settings|ms-settings-airplanemode|ms-settings-bluetooth|ms-settings-camera|ms-settings-cellular|ms-settings-cloudstorage|ms-settings-connectabledevices|ms-settings-displays-topology|ms-settings-emailandaccounts|ms-settings-language|ms-settings-location|ms-settings-lock|ms-settings-nfctransactions|ms-settings-notifications|ms-settings-power|ms-settings-privacy|ms-settings-proximity|ms-settings-screenrotation|ms-settings-wifi|ms-settings-workplace|ms-spd|ms-sttoverlay|ms-transit-to|ms-useractivityset|ms-virtualtouchpad|ms-visio|ms-walk-to|ms-whiteboard|ms-whiteboard-cmd|ms-word|msnim|msrp|msrps|mss|mtqp|mumble|mupdate|mvn|news|nfs|ni|nih|nntp|notes|ocf|oid|onenote|onenote-cmd|opaquelocktoken|openpgp4fpr|pack|palm|paparazzi|payto|pkcs11|platform|pop|pres|prospero|proxy|pwid|psyc|pttp|qb|query|redis|rediss|reload|res|resource|rmi|rsync|rtmfp|rtmp|rtsp|rtsps|rtspu|s3|secondlife|service|session|sftp|sgn|shttp|sieve|simpleledger|sip|sips|skype|smb|sms|smtp|snews|snmp|soap\.beep|soap\.beeps|soldat|spiffe|spotify|ssh|steam|stun|stuns|submit|svn|tag|teamspeak|tel|teliaeid|telnet|tftp|tg|things|thismessage|tip|tn3270|tool|ts3server|turn|turns|tv|udp|unreal|urn|ut2004|v-event|vemmi|ventrilo|videotex|vnc|view-source|wais|webcal|wpid|ws|wss|wtai|wyciwyg|xcon|xcon-userid|xfire|xmlrpc\.beep|xmlrpc\.beeps|xmpp|xri|ymsgr|z39\.50|z39\.50r|z39\.50s)://                                 # protocol
2165
            (((?:[\_\.\pL\pN-]|%[0-9A-Fa-f]{2})+:)?((?:[\_\.\pL\pN-]|%[0-9A-Fa-f]{2})+)@)?  # basic auth
2166
            (
2167
                ([\pL\pN\pS\-\_\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name
2168
                    |                                                 # or
2169
                \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}                    # an IP address
2170
                    |                                                 # or
2171
                \[
2172
                    (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::))))
2173
                \]  # an IPv6 address
2174
            )
2175
            (:[0-9]+)?                              # a port (optional)
2176
            (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})* )*          # a path
2177
            (?:\? (?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%[0-9A-Fa-f]{2})* )?   # a query (optional)
2178
            (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})* )?       # a fragment (optional)
2179
        $~ixu';
2180
 
2181
        return preg_match($pattern, $value) > 0;
2182
    }
2183
 
2184
    /**
2185
     * Validate that an attribute is a valid ULID.
2186
     *
2187
     * @param  string  $attribute
2188
     * @param  mixed  $value
2189
     * @return bool
2190
     */
2191
    public function validateUlid($attribute, $value)
2192
    {
2193
        return Str::isUlid($value);
2194
    }
2195
 
2196
    /**
2197
     * Validate that an attribute is a valid UUID.
2198
     *
2199
     * @param  string  $attribute
2200
     * @param  mixed  $value
2201
     * @return bool
2202
     */
2203
    public function validateUuid($attribute, $value)
2204
    {
2205
        return Str::isUuid($value);
2206
    }
2207
 
2208
    /**
2209
     * Get the size of an attribute.
2210
     *
2211
     * @param  string  $attribute
2212
     * @param  mixed  $value
2213
     * @return mixed
2214
     */
2215
    protected function getSize($attribute, $value)
2216
    {
2217
        $hasNumeric = $this->hasRule($attribute, $this->numericRules);
2218
 
2219
        // This method will determine if the attribute is a number, string, or file and
2220
        // return the proper size accordingly. If it is a number, then number itself
2221
        // is the size. If it is a file, we take kilobytes, and for a string the
2222
        // entire length of the string will be considered the attribute size.
2223
        if (is_numeric($value) && $hasNumeric) {
2224
            return $value;
2225
        } elseif (is_array($value)) {
2226
            return count($value);
2227
        } elseif ($value instanceof File) {
2228
            return $value->getSize() / 1024;
2229
        }
2230
 
2231
        return mb_strlen($value ?? '');
2232
    }
2233
 
2234
    /**
2235
     * Check that the given value is a valid file instance.
2236
     *
2237
     * @param  mixed  $value
2238
     * @return bool
2239
     */
2240
    public function isValidFileInstance($value)
2241
    {
2242
        if ($value instanceof UploadedFile && ! $value->isValid()) {
2243
            return false;
2244
        }
2245
 
2246
        return $value instanceof File;
2247
    }
2248
 
2249
    /**
2250
     * Determine if a comparison passes between the given values.
2251
     *
2252
     * @param  mixed  $first
2253
     * @param  mixed  $second
2254
     * @param  string  $operator
2255
     * @return bool
2256
     *
2257
     * @throws \InvalidArgumentException
2258
     */
2259
    protected function compare($first, $second, $operator)
2260
    {
2261
        return match ($operator) {
2262
            '<' => $first < $second,
2263
            '>' => $first > $second,
2264
            '<=' => $first <= $second,
2265
            '>=' => $first >= $second,
2266
            '=' => $first == $second,
2267
            default => throw new InvalidArgumentException,
2268
        };
2269
    }
2270
 
2271
    /**
2272
     * Parse named parameters to $key => $value items.
2273
     *
2274
     * @param  array<int, int|string>  $parameters
2275
     * @return array
2276
     */
2277
    public function parseNamedParameters($parameters)
2278
    {
2279
        return array_reduce($parameters, function ($result, $item) {
2280
            [$key, $value] = array_pad(explode('=', $item, 2), 2, null);
2281
 
2282
            $result[$key] = $value;
2283
 
2284
            return $result;
2285
        });
2286
    }
2287
 
2288
    /**
2289
     * Require a certain number of parameters to be present.
2290
     *
2291
     * @param  int  $count
2292
     * @param  array<int, int|string>  $parameters
2293
     * @param  string  $rule
2294
     * @return void
2295
     *
2296
     * @throws \InvalidArgumentException
2297
     */
2298
    public function requireParameterCount($count, $parameters, $rule)
2299
    {
2300
        if (count($parameters) < $count) {
2301
            throw new InvalidArgumentException("Validation rule $rule requires at least $count parameters.");
2302
        }
2303
    }
2304
 
2305
    /**
2306
     * Check if the parameters are of the same type.
2307
     *
2308
     * @param  mixed  $first
2309
     * @param  mixed  $second
2310
     * @return bool
2311
     */
2312
    protected function isSameType($first, $second)
2313
    {
2314
        return gettype($first) == gettype($second);
2315
    }
2316
 
2317
    /**
2318
     * Adds the existing rule to the numericRules array if the attribute's value is numeric.
2319
     *
2320
     * @param  string  $attribute
2321
     * @param  string  $rule
2322
     * @return void
2323
     */
2324
    protected function shouldBeNumeric($attribute, $rule)
2325
    {
2326
        if (is_numeric($this->getValue($attribute))) {
2327
            $this->numericRules[] = $rule;
2328
        }
2329
    }
2330
}