Subversion-Projekte lars-tiefland.laravel_shop

Revision

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