Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 150 | 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
 
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)),
458
            fn ($size) => $size->isGreaterThanOrEqualTo($parameters[0]) && $size->isLessThanOrEqualTo($parameters[1])
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]))) {
399 lars 1101
            return BigNumber::of($this->getSize($attribute, $value))->isGreaterThan($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)) {
399 lars 1109
            return BigNumber::of($value)->isGreaterThan($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]))) {
399 lars 1136
            return BigNumber::of($this->getSize($attribute, $value))->isLessThan($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)) {
399 lars 1144
            return BigNumber::of($value)->isLessThan($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]))) {
399 lars 1171
            return BigNumber::of($this->getSize($attribute, $value))->isGreaterThanOrEqualTo($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)) {
399 lars 1179
            return BigNumber::of($value)->isGreaterThanOrEqualTo($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]))) {
399 lars 1206
            return BigNumber::of($this->getSize($attribute, $value))->isLessThanOrEqualTo($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)) {
399 lars 1214
            return BigNumber::of($value)->isLessThanOrEqualTo($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
 
399 lars 1406
        return BigNumber::of($this->getSize($attribute, $value))->isLessThanOrEqualTo($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
 
399 lars 1508
        return BigNumber::of($this->getSize($attribute, $value))->isGreaterThanOrEqualTo($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
 
1595
        return ! Arr::hasAny($this->data, $parameters);
1596
    }
1597
 
1598
    /**
1599
     * Validate that an attribute is missing when all given attributes are present.
1600
     *
1601
     * @param  string  $attribute
1602
     * @param  mixed  $value
1603
     * @param  array<int, int|string>  $parameters
1604
     * @return bool
1605
     */
1606
    public function validateMissingWithAll($attribute, $value, $parameters)
1607
    {
1608
        $this->requireParameterCount(1, $parameters, 'missing_with');
1609
 
1610
        return ! Arr::has($this->data, $parameters);
1611
    }
1612
 
1613
    /**
148 lars 1614
     * Validate the value of an attribute is a multiple of a given value.
1615
     *
1616
     * @param  string  $attribute
1617
     * @param  mixed  $value
1618
     * @param  array<int, int|string>  $parameters
1619
     * @return bool
1620
     */
1621
    public function validateMultipleOf($attribute, $value, $parameters)
1622
    {
1623
        $this->requireParameterCount(1, $parameters, 'multiple_of');
1624
 
1625
        if (! $this->validateNumeric($attribute, $value) || ! $this->validateNumeric($attribute, $parameters[0])) {
1626
            return false;
1627
        }
1628
 
399 lars 1629
        try {
1630
            $numerator = BigDecimal::of($value);
1631
            $denominator = BigDecimal::of($parameters[0]);
1632
 
1633
            if ($numerator->isZero() && $denominator->isZero()) {
1634
                return false;
1635
            }
1636
 
1637
            if ($numerator->isZero()) {
1638
                return true;
1639
            }
1640
 
1641
            if ($denominator->isZero()) {
1642
                return false;
1643
            }
1644
 
1645
            return $numerator->remainder($denominator)->isZero();
1646
        } catch (BrickMathException $e) {
1647
            throw new MathException('An error occurred while handling the mulitple_of input values.', previous: $e);
148 lars 1648
        }
1649
    }
1650
 
1651
    /**
1652
     * "Indicate" validation should pass if value is null.
1653
     *
1654
     * Always returns true, just lets us put "nullable" in rules.
1655
     *
1656
     * @return bool
1657
     */
1658
    public function validateNullable()
1659
    {
1660
        return true;
1661
    }
1662
 
1663
    /**
1664
     * Validate an attribute is not contained within a list of values.
1665
     *
1666
     * @param  string  $attribute
1667
     * @param  mixed  $value
1668
     * @param  array<int, int|string>  $parameters
1669
     * @return bool
1670
     */
1671
    public function validateNotIn($attribute, $value, $parameters)
1672
    {
1673
        return ! $this->validateIn($attribute, $value, $parameters);
1674
    }
1675
 
1676
    /**
1677
     * Validate that an attribute is numeric.
1678
     *
1679
     * @param  string  $attribute
1680
     * @param  mixed  $value
1681
     * @return bool
1682
     */
1683
    public function validateNumeric($attribute, $value)
1684
    {
1685
        return is_numeric($value);
1686
    }
1687
 
1688
    /**
1689
     * Validate that an attribute exists even if not filled.
1690
     *
1691
     * @param  string  $attribute
1692
     * @param  mixed  $value
1693
     * @return bool
1694
     */
1695
    public function validatePresent($attribute, $value)
1696
    {
1697
        return Arr::has($this->data, $attribute);
1698
    }
1699
 
1700
    /**
1701
     * Validate that an attribute passes a regular expression check.
1702
     *
1703
     * @param  string  $attribute
1704
     * @param  mixed  $value
1705
     * @param  array<int, int|string>  $parameters
1706
     * @return bool
1707
     */
1708
    public function validateRegex($attribute, $value, $parameters)
1709
    {
1710
        if (! is_string($value) && ! is_numeric($value)) {
1711
            return false;
1712
        }
1713
 
1714
        $this->requireParameterCount(1, $parameters, 'regex');
1715
 
1716
        return preg_match($parameters[0], $value) > 0;
1717
    }
1718
 
1719
    /**
1720
     * Validate that an attribute does not pass a regular expression check.
1721
     *
1722
     * @param  string  $attribute
1723
     * @param  mixed  $value
1724
     * @param  array<int, int|string>  $parameters
1725
     * @return bool
1726
     */
1727
    public function validateNotRegex($attribute, $value, $parameters)
1728
    {
1729
        if (! is_string($value) && ! is_numeric($value)) {
1730
            return false;
1731
        }
1732
 
1733
        $this->requireParameterCount(1, $parameters, 'not_regex');
1734
 
1735
        return preg_match($parameters[0], $value) < 1;
1736
    }
1737
 
1738
    /**
1739
     * Validate that a required attribute exists.
1740
     *
1741
     * @param  string  $attribute
1742
     * @param  mixed  $value
1743
     * @return bool
1744
     */
1745
    public function validateRequired($attribute, $value)
1746
    {
1747
        if (is_null($value)) {
1748
            return false;
1749
        } elseif (is_string($value) && trim($value) === '') {
1750
            return false;
1751
        } elseif (is_countable($value) && count($value) < 1) {
1752
            return false;
1753
        } elseif ($value instanceof File) {
1754
            return (string) $value->getPath() !== '';
1755
        }
1756
 
1757
        return true;
1758
    }
1759
 
1760
    /**
1761
     * Validate that an attribute exists 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 validateRequiredIf($attribute, $value, $parameters)
1769
    {
1770
        $this->requireParameterCount(2, $parameters, 'required_if');
1771
 
1772
        if (! Arr::has($this->data, $parameters[0])) {
1773
            return true;
1774
        }
1775
 
1776
        [$values, $other] = $this->parseDependentRuleParameters($parameters);
1777
 
1778
        if (in_array($other, $values, is_bool($other) || is_null($other))) {
1779
            return $this->validateRequired($attribute, $value);
1780
        }
1781
 
1782
        return true;
1783
    }
1784
 
1785
    /**
1786
     * Validate that an attribute exists when another attribute was "accepted".
1787
     *
1788
     * @param  string  $attribute
1789
     * @param  mixed  $value
1790
     * @param  mixed  $parameters
1791
     * @return bool
1792
     */
1793
    public function validateRequiredIfAccepted($attribute, $value, $parameters)
1794
    {
1795
        $this->requireParameterCount(1, $parameters, 'required_if_accepted');
1796
 
1797
        if ($this->validateAccepted($parameters[0], $this->getValue($parameters[0]))) {
1798
            return $this->validateRequired($attribute, $value);
1799
        }
1800
 
1801
        return true;
1802
    }
1803
 
1804
    /**
150 lars 1805
     * Validate that an attribute does not exist or is an empty string.
148 lars 1806
     *
1807
     * @param  string  $attribute
1808
     * @param  mixed  $value
1809
     * @return bool
1810
     */
1811
    public function validateProhibited($attribute, $value)
1812
    {
399 lars 1813
        return ! $this->validateRequired($attribute, $value);
148 lars 1814
    }
1815
 
1816
    /**
1817
     * Validate that an attribute does not exist when another attribute has a given value.
1818
     *
1819
     * @param  string  $attribute
1820
     * @param  mixed  $value
1821
     * @param  mixed  $parameters
1822
     * @return bool
1823
     */
1824
    public function validateProhibitedIf($attribute, $value, $parameters)
1825
    {
1826
        $this->requireParameterCount(2, $parameters, 'prohibited_if');
1827
 
1828
        [$values, $other] = $this->parseDependentRuleParameters($parameters);
1829
 
1830
        if (in_array($other, $values, is_bool($other) || is_null($other))) {
1831
            return ! $this->validateRequired($attribute, $value);
1832
        }
1833
 
1834
        return true;
1835
    }
1836
 
1837
    /**
1838
     * Validate that an attribute does not exist unless another attribute has a given value.
1839
     *
1840
     * @param  string  $attribute
1841
     * @param  mixed  $value
1842
     * @param  mixed  $parameters
1843
     * @return bool
1844
     */
1845
    public function validateProhibitedUnless($attribute, $value, $parameters)
1846
    {
1847
        $this->requireParameterCount(2, $parameters, 'prohibited_unless');
1848
 
1849
        [$values, $other] = $this->parseDependentRuleParameters($parameters);
1850
 
1851
        if (! in_array($other, $values, is_bool($other) || is_null($other))) {
1852
            return ! $this->validateRequired($attribute, $value);
1853
        }
1854
 
1855
        return true;
1856
    }
1857
 
1858
    /**
1859
     * Validate that other attributes do not exist when this attribute exists.
1860
     *
1861
     * @param  string  $attribute
1862
     * @param  mixed  $value
1863
     * @param  mixed  $parameters
1864
     * @return bool
1865
     */
1866
    public function validateProhibits($attribute, $value, $parameters)
1867
    {
399 lars 1868
        if ($this->validateRequired($attribute, $value)) {
1869
            foreach ($parameters as $parameter) {
1870
                if ($this->validateRequired($parameter, Arr::get($this->data, $parameter))) {
1871
                    return false;
1872
                }
1873
            }
1874
        }
1875
 
1876
        return true;
148 lars 1877
    }
1878
 
1879
    /**
1880
     * Indicate that an attribute is excluded.
1881
     *
1882
     * @return bool
1883
     */
1884
    public function validateExclude()
1885
    {
1886
        return false;
1887
    }
1888
 
1889
    /**
1890
     * Indicate that an attribute should be excluded when another attribute has a given value.
1891
     *
1892
     * @param  string  $attribute
1893
     * @param  mixed  $value
1894
     * @param  mixed  $parameters
1895
     * @return bool
1896
     */
1897
    public function validateExcludeIf($attribute, $value, $parameters)
1898
    {
1899
        $this->requireParameterCount(2, $parameters, 'exclude_if');
1900
 
1901
        if (! Arr::has($this->data, $parameters[0])) {
1902
            return true;
1903
        }
1904
 
1905
        [$values, $other] = $this->parseDependentRuleParameters($parameters);
1906
 
1907
        return ! in_array($other, $values, is_bool($other) || is_null($other));
1908
    }
1909
 
1910
    /**
1911
     * Indicate that an attribute should be excluded when another attribute does not have a given value.
1912
     *
1913
     * @param  string  $attribute
1914
     * @param  mixed  $value
1915
     * @param  mixed  $parameters
1916
     * @return bool
1917
     */
1918
    public function validateExcludeUnless($attribute, $value, $parameters)
1919
    {
1920
        $this->requireParameterCount(2, $parameters, 'exclude_unless');
1921
 
1922
        [$values, $other] = $this->parseDependentRuleParameters($parameters);
1923
 
1924
        return in_array($other, $values, is_bool($other) || is_null($other));
1925
    }
1926
 
1927
    /**
1928
     * Validate that an attribute exists when another attribute does not have a given value.
1929
     *
1930
     * @param  string  $attribute
1931
     * @param  mixed  $value
1932
     * @param  mixed  $parameters
1933
     * @return bool
1934
     */
1935
    public function validateRequiredUnless($attribute, $value, $parameters)
1936
    {
1937
        $this->requireParameterCount(2, $parameters, 'required_unless');
1938
 
1939
        [$values, $other] = $this->parseDependentRuleParameters($parameters);
1940
 
1941
        if (! in_array($other, $values, is_bool($other) || is_null($other))) {
1942
            return $this->validateRequired($attribute, $value);
1943
        }
1944
 
1945
        return true;
1946
    }
1947
 
1948
    /**
1949
     * Indicate that an attribute should be excluded when another attribute presents.
1950
     *
1951
     * @param  string  $attribute
1952
     * @param  mixed  $value
1953
     * @param  mixed  $parameters
1954
     * @return bool
1955
     */
1956
    public function validateExcludeWith($attribute, $value, $parameters)
1957
    {
1958
        $this->requireParameterCount(1, $parameters, 'exclude_with');
1959
 
1960
        if (! Arr::has($this->data, $parameters[0])) {
1961
            return true;
1962
        }
1963
 
1964
        return false;
1965
    }
1966
 
1967
    /**
1968
     * Indicate that an attribute should be excluded when another attribute is missing.
1969
     *
1970
     * @param  string  $attribute
1971
     * @param  mixed  $value
1972
     * @param  mixed  $parameters
1973
     * @return bool
1974
     */
1975
    public function validateExcludeWithout($attribute, $value, $parameters)
1976
    {
1977
        $this->requireParameterCount(1, $parameters, 'exclude_without');
1978
 
1979
        if ($this->anyFailingRequired($parameters)) {
1980
            return false;
1981
        }
1982
 
1983
        return true;
1984
    }
1985
 
1986
    /**
1987
     * Prepare the values and the other value for validation.
1988
     *
1989
     * @param  array<int, int|string>  $parameters
1990
     * @return array
1991
     */
1992
    public function parseDependentRuleParameters($parameters)
1993
    {
1994
        $other = Arr::get($this->data, $parameters[0]);
1995
 
1996
        $values = array_slice($parameters, 1);
1997
 
1998
        if ($this->shouldConvertToBoolean($parameters[0]) || is_bool($other)) {
1999
            $values = $this->convertValuesToBoolean($values);
2000
        }
2001
 
2002
        if (is_null($other)) {
2003
            $values = $this->convertValuesToNull($values);
2004
        }
2005
 
2006
        return [$values, $other];
2007
    }
2008
 
2009
    /**
2010
     * Check if parameter should be converted to boolean.
2011
     *
2012
     * @param  string  $parameter
2013
     * @return bool
2014
     */
2015
    protected function shouldConvertToBoolean($parameter)
2016
    {
2017
        return in_array('boolean', Arr::get($this->rules, $parameter, []));
2018
    }
2019
 
2020
    /**
2021
     * Convert the given values to boolean if they are string "true" / "false".
2022
     *
2023
     * @param  array  $values
2024
     * @return array
2025
     */
2026
    protected function convertValuesToBoolean($values)
2027
    {
2028
        return array_map(function ($value) {
2029
            if ($value === 'true') {
2030
                return true;
2031
            } elseif ($value === 'false') {
2032
                return false;
2033
            }
2034
 
2035
            return $value;
2036
        }, $values);
2037
    }
2038
 
2039
    /**
2040
     * Convert the given values to null if they are string "null".
2041
     *
2042
     * @param  array  $values
2043
     * @return array
2044
     */
2045
    protected function convertValuesToNull($values)
2046
    {
2047
        return array_map(function ($value) {
2048
            return Str::lower($value) === 'null' ? null : $value;
2049
        }, $values);
2050
    }
2051
 
2052
    /**
2053
     * Validate that an attribute exists when any other attribute exists.
2054
     *
2055
     * @param  string  $attribute
2056
     * @param  mixed  $value
2057
     * @param  mixed  $parameters
2058
     * @return bool
2059
     */
2060
    public function validateRequiredWith($attribute, $value, $parameters)
2061
    {
2062
        if (! $this->allFailingRequired($parameters)) {
2063
            return $this->validateRequired($attribute, $value);
2064
        }
2065
 
2066
        return true;
2067
    }
2068
 
2069
    /**
2070
     * Validate that an attribute exists when all other attributes exist.
2071
     *
2072
     * @param  string  $attribute
2073
     * @param  mixed  $value
2074
     * @param  mixed  $parameters
2075
     * @return bool
2076
     */
2077
    public function validateRequiredWithAll($attribute, $value, $parameters)
2078
    {
2079
        if (! $this->anyFailingRequired($parameters)) {
2080
            return $this->validateRequired($attribute, $value);
2081
        }
2082
 
2083
        return true;
2084
    }
2085
 
2086
    /**
2087
     * Validate that an attribute exists when another attribute does not.
2088
     *
2089
     * @param  string  $attribute
2090
     * @param  mixed  $value
2091
     * @param  mixed  $parameters
2092
     * @return bool
2093
     */
2094
    public function validateRequiredWithout($attribute, $value, $parameters)
2095
    {
2096
        if ($this->anyFailingRequired($parameters)) {
2097
            return $this->validateRequired($attribute, $value);
2098
        }
2099
 
2100
        return true;
2101
    }
2102
 
2103
    /**
2104
     * Validate that an attribute exists when all other attributes do not.
2105
     *
2106
     * @param  string  $attribute
2107
     * @param  mixed  $value
2108
     * @param  mixed  $parameters
2109
     * @return bool
2110
     */
2111
    public function validateRequiredWithoutAll($attribute, $value, $parameters)
2112
    {
2113
        if ($this->allFailingRequired($parameters)) {
2114
            return $this->validateRequired($attribute, $value);
2115
        }
2116
 
2117
        return true;
2118
    }
2119
 
2120
    /**
2121
     * Determine if any of the given attributes fail the required test.
2122
     *
2123
     * @param  array  $attributes
2124
     * @return bool
2125
     */
2126
    protected function anyFailingRequired(array $attributes)
2127
    {
2128
        foreach ($attributes as $key) {
2129
            if (! $this->validateRequired($key, $this->getValue($key))) {
2130
                return true;
2131
            }
2132
        }
2133
 
2134
        return false;
2135
    }
2136
 
2137
    /**
2138
     * Determine if all of the given attributes fail the required test.
2139
     *
2140
     * @param  array  $attributes
2141
     * @return bool
2142
     */
2143
    protected function allFailingRequired(array $attributes)
2144
    {
2145
        foreach ($attributes as $key) {
2146
            if ($this->validateRequired($key, $this->getValue($key))) {
2147
                return false;
2148
            }
2149
        }
2150
 
2151
        return true;
2152
    }
2153
 
2154
    /**
2155
     * Validate that two attributes match.
2156
     *
2157
     * @param  string  $attribute
2158
     * @param  mixed  $value
2159
     * @param  array<int, int|string>  $parameters
2160
     * @return bool
2161
     */
2162
    public function validateSame($attribute, $value, $parameters)
2163
    {
2164
        $this->requireParameterCount(1, $parameters, 'same');
2165
 
2166
        $other = Arr::get($this->data, $parameters[0]);
2167
 
2168
        return $value === $other;
2169
    }
2170
 
2171
    /**
2172
     * Validate the size of an attribute.
2173
     *
2174
     * @param  string  $attribute
2175
     * @param  mixed  $value
2176
     * @param  array<int, int|string>  $parameters
2177
     * @return bool
2178
     */
2179
    public function validateSize($attribute, $value, $parameters)
2180
    {
2181
        $this->requireParameterCount(1, $parameters, 'size');
2182
 
399 lars 2183
        return BigNumber::of($this->getSize($attribute, $value))->isEqualTo($parameters[0]);
148 lars 2184
    }
2185
 
2186
    /**
2187
     * "Validate" optional attributes.
2188
     *
2189
     * Always returns true, just lets us put sometimes in rules.
2190
     *
2191
     * @return bool
2192
     */
2193
    public function validateSometimes()
2194
    {
2195
        return true;
2196
    }
2197
 
2198
    /**
2199
     * Validate the attribute starts with a given substring.
2200
     *
2201
     * @param  string  $attribute
2202
     * @param  mixed  $value
2203
     * @param  array<int, int|string>  $parameters
2204
     * @return bool
2205
     */
2206
    public function validateStartsWith($attribute, $value, $parameters)
2207
    {
2208
        return Str::startsWith($value, $parameters);
2209
    }
2210
 
2211
    /**
2212
     * Validate the attribute does not start with a given substring.
2213
     *
2214
     * @param  string  $attribute
2215
     * @param  mixed  $value
2216
     * @param  array<int, int|string>  $parameters
2217
     * @return bool
2218
     */
2219
    public function validateDoesntStartWith($attribute, $value, $parameters)
2220
    {
2221
        return ! Str::startsWith($value, $parameters);
2222
    }
2223
 
2224
    /**
2225
     * Validate the attribute ends with a given substring.
2226
     *
2227
     * @param  string  $attribute
2228
     * @param  mixed  $value
2229
     * @param  array<int, int|string>  $parameters
2230
     * @return bool
2231
     */
2232
    public function validateEndsWith($attribute, $value, $parameters)
2233
    {
2234
        return Str::endsWith($value, $parameters);
2235
    }
2236
 
2237
    /**
2238
     * Validate the attribute does not end with a given substring.
2239
     *
2240
     * @param  string  $attribute
2241
     * @param  mixed  $value
2242
     * @param  array<int, int|string>  $parameters
2243
     * @return bool
2244
     */
2245
    public function validateDoesntEndWith($attribute, $value, $parameters)
2246
    {
2247
        return ! Str::endsWith($value, $parameters);
2248
    }
2249
 
2250
    /**
2251
     * Validate that an attribute is a string.
2252
     *
2253
     * @param  string  $attribute
2254
     * @param  mixed  $value
2255
     * @return bool
2256
     */
2257
    public function validateString($attribute, $value)
2258
    {
2259
        return is_string($value);
2260
    }
2261
 
2262
    /**
2263
     * Validate that an attribute is a valid timezone.
2264
     *
2265
     * @param  string  $attribute
2266
     * @param  mixed  $value
2267
     * @return bool
2268
     */
2269
    public function validateTimezone($attribute, $value)
2270
    {
2271
        return in_array($value, timezone_identifiers_list(), true);
2272
    }
2273
 
2274
    /**
2275
     * Validate that an attribute is a valid URL.
2276
     *
2277
     * @param  string  $attribute
2278
     * @param  mixed  $value
2279
     * @return bool
2280
     */
2281
    public function validateUrl($attribute, $value)
2282
    {
2283
        if (! is_string($value)) {
2284
            return false;
2285
        }
2286
 
2287
        /*
2288
         * This pattern is derived from Symfony\Component\Validator\Constraints\UrlValidator (5.0.7).
2289
         *
2290
         * (c) Fabien Potencier <fabien@symfony.com> http://symfony.com
2291
         */
2292
        $pattern = '~^
2293
            (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
2294
            (((?:[\_\.\pL\pN-]|%[0-9A-Fa-f]{2})+:)?((?:[\_\.\pL\pN-]|%[0-9A-Fa-f]{2})+)@)?  # basic auth
2295
            (
2296
                ([\pL\pN\pS\-\_\.])+(\.?([\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name
2297
                    |                                                 # or
2298
                \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}                    # an IP address
2299
                    |                                                 # or
2300
                \[
2301
                    (?:(?:(?:(?:(?:(?:(?:[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})))?::))))
2302
                \]  # an IPv6 address
2303
            )
2304
            (:[0-9]+)?                              # a port (optional)
2305
            (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})* )*          # a path
2306
            (?:\? (?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%[0-9A-Fa-f]{2})* )?   # a query (optional)
2307
            (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%[0-9A-Fa-f]{2})* )?       # a fragment (optional)
2308
        $~ixu';
2309
 
2310
        return preg_match($pattern, $value) > 0;
2311
    }
2312
 
2313
    /**
2314
     * Validate that an attribute is a valid ULID.
2315
     *
2316
     * @param  string  $attribute
2317
     * @param  mixed  $value
2318
     * @return bool
2319
     */
2320
    public function validateUlid($attribute, $value)
2321
    {
2322
        return Str::isUlid($value);
2323
    }
2324
 
2325
    /**
2326
     * Validate that an attribute is a valid UUID.
2327
     *
2328
     * @param  string  $attribute
2329
     * @param  mixed  $value
2330
     * @return bool
2331
     */
2332
    public function validateUuid($attribute, $value)
2333
    {
2334
        return Str::isUuid($value);
2335
    }
2336
 
2337
    /**
2338
     * Get the size of an attribute.
2339
     *
2340
     * @param  string  $attribute
2341
     * @param  mixed  $value
399 lars 2342
     * @return int|float|string
148 lars 2343
     */
2344
    protected function getSize($attribute, $value)
2345
    {
2346
        $hasNumeric = $this->hasRule($attribute, $this->numericRules);
2347
 
2348
        // This method will determine if the attribute is a number, string, or file and
2349
        // return the proper size accordingly. If it is a number, then number itself
2350
        // is the size. If it is a file, we take kilobytes, and for a string the
2351
        // entire length of the string will be considered the attribute size.
2352
        if (is_numeric($value) && $hasNumeric) {
2353
            return $value;
2354
        } elseif (is_array($value)) {
2355
            return count($value);
2356
        } elseif ($value instanceof File) {
2357
            return $value->getSize() / 1024;
2358
        }
2359
 
2360
        return mb_strlen($value ?? '');
2361
    }
2362
 
2363
    /**
2364
     * Check that the given value is a valid file instance.
2365
     *
2366
     * @param  mixed  $value
2367
     * @return bool
2368
     */
2369
    public function isValidFileInstance($value)
2370
    {
2371
        if ($value instanceof UploadedFile && ! $value->isValid()) {
2372
            return false;
2373
        }
2374
 
2375
        return $value instanceof File;
2376
    }
2377
 
2378
    /**
2379
     * Determine if a comparison passes between the given values.
2380
     *
2381
     * @param  mixed  $first
2382
     * @param  mixed  $second
2383
     * @param  string  $operator
2384
     * @return bool
2385
     *
2386
     * @throws \InvalidArgumentException
2387
     */
2388
    protected function compare($first, $second, $operator)
2389
    {
2390
        return match ($operator) {
2391
            '<' => $first < $second,
2392
            '>' => $first > $second,
2393
            '<=' => $first <= $second,
2394
            '>=' => $first >= $second,
2395
            '=' => $first == $second,
2396
            default => throw new InvalidArgumentException,
2397
        };
2398
    }
2399
 
2400
    /**
2401
     * Parse named parameters to $key => $value items.
2402
     *
2403
     * @param  array<int, int|string>  $parameters
2404
     * @return array
2405
     */
2406
    public function parseNamedParameters($parameters)
2407
    {
2408
        return array_reduce($parameters, function ($result, $item) {
2409
            [$key, $value] = array_pad(explode('=', $item, 2), 2, null);
2410
 
2411
            $result[$key] = $value;
2412
 
2413
            return $result;
2414
        });
2415
    }
2416
 
2417
    /**
2418
     * Require a certain number of parameters to be present.
2419
     *
2420
     * @param  int  $count
2421
     * @param  array<int, int|string>  $parameters
2422
     * @param  string  $rule
2423
     * @return void
2424
     *
2425
     * @throws \InvalidArgumentException
2426
     */
2427
    public function requireParameterCount($count, $parameters, $rule)
2428
    {
2429
        if (count($parameters) < $count) {
2430
            throw new InvalidArgumentException("Validation rule $rule requires at least $count parameters.");
2431
        }
2432
    }
2433
 
2434
    /**
2435
     * Check if the parameters are of the same type.
2436
     *
2437
     * @param  mixed  $first
2438
     * @param  mixed  $second
2439
     * @return bool
2440
     */
2441
    protected function isSameType($first, $second)
2442
    {
2443
        return gettype($first) == gettype($second);
2444
    }
2445
 
2446
    /**
2447
     * Adds the existing rule to the numericRules array if the attribute's value is numeric.
2448
     *
2449
     * @param  string  $attribute
2450
     * @param  string  $rule
2451
     * @return void
2452
     */
2453
    protected function shouldBeNumeric($attribute, $rule)
2454
    {
2455
        if (is_numeric($this->getValue($attribute))) {
2456
            $this->numericRules[] = $rule;
2457
        }
2458
    }
2459
}