Subversion-Projekte lars-tiefland.laravel_shop

Revision

Zur aktuellen Revision | Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Validation\Concerns;
4
 
5
use Closure;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Str;
8
use Symfony\Component\HttpFoundation\File\UploadedFile;
9
 
10
trait FormatsMessages
11
{
12
    use ReplacesAttributes;
13
 
14
    /**
15
     * Get the validation message for an attribute and rule.
16
     *
17
     * @param  string  $attribute
18
     * @param  string  $rule
19
     * @return string
20
     */
21
    protected function getMessage($attribute, $rule)
22
    {
23
        $attributeWithPlaceholders = $attribute;
24
 
25
        $attribute = $this->replacePlaceholderInString($attribute);
26
 
27
        $inlineMessage = $this->getInlineMessage($attribute, $rule);
28
 
29
        // First we will retrieve the custom message for the validation rule if one
30
        // exists. If a custom validation message is being used we'll return the
31
        // custom message, otherwise we'll keep searching for a valid message.
32
        if (! is_null($inlineMessage)) {
33
            return $inlineMessage;
34
        }
35
 
36
        $lowerRule = Str::snake($rule);
37
 
38
        $customKey = "validation.custom.{$attribute}.{$lowerRule}";
39
 
40
        $customMessage = $this->getCustomMessageFromTranslator(
41
                in_array($rule, $this->sizeRules)
42
                    ? [$customKey.".{$this->getAttributeType($attribute)}", $customKey]
43
                    : $customKey
44
        );
45
 
46
        // First we check for a custom defined validation message for the attribute
47
        // and rule. This allows the developer to specify specific messages for
48
        // only some attributes and rules that need to get specially formed.
49
        if ($customMessage !== $customKey) {
50
            return $customMessage;
51
        }
52
 
53
        // If the rule being validated is a "size" rule, we will need to gather the
54
        // specific error message for the type of attribute being validated such
55
        // as a number, file or string which all have different message types.
56
        elseif (in_array($rule, $this->sizeRules)) {
57
            return $this->getSizeMessage($attributeWithPlaceholders, $rule);
58
        }
59
 
60
        // Finally, if no developer specified messages have been set, and no other
61
        // special messages apply for this rule, we will just pull the default
62
        // messages out of the translator service for this validation rule.
63
        $key = "validation.{$lowerRule}";
64
 
65
        if ($key !== ($value = $this->translator->get($key))) {
66
            return $value;
67
        }
68
 
69
        return $this->getFromLocalArray(
70
            $attribute, $lowerRule, $this->fallbackMessages
71
        ) ?: $key;
72
    }
73
 
74
    /**
75
     * Get the proper inline error message for standard and size rules.
76
     *
77
     * @param  string  $attribute
78
     * @param  string  $rule
79
     * @return string|null
80
     */
81
    protected function getInlineMessage($attribute, $rule)
82
    {
83
        $inlineEntry = $this->getFromLocalArray($attribute, Str::snake($rule));
84
 
85
        return is_array($inlineEntry) && in_array($rule, $this->sizeRules)
86
                    ? $inlineEntry[$this->getAttributeType($attribute)]
87
                    : $inlineEntry;
88
    }
89
 
90
    /**
91
     * Get the inline message for a rule if it exists.
92
     *
93
     * @param  string  $attribute
94
     * @param  string  $lowerRule
95
     * @param  array|null  $source
96
     * @return string|null
97
     */
98
    protected function getFromLocalArray($attribute, $lowerRule, $source = null)
99
    {
100
        $source = $source ?: $this->customMessages;
101
 
102
        $keys = ["{$attribute}.{$lowerRule}", $lowerRule, $attribute];
103
 
104
        // First we will check for a custom message for an attribute specific rule
105
        // message for the fields, then we will check for a general custom line
106
        // that is not attribute specific. If we find either we'll return it.
107
        foreach ($keys as $key) {
108
            foreach (array_keys($source) as $sourceKey) {
109
                if (str_contains($sourceKey, '*')) {
110
                    $pattern = str_replace('\*', '([^.]*)', preg_quote($sourceKey, '#'));
111
 
112
                    if (preg_match('#^'.$pattern.'\z#u', $key) === 1) {
113
                        $message = $source[$sourceKey];
114
 
115
                        if (is_array($message) && isset($message[$lowerRule])) {
116
                            return $message[$lowerRule];
117
                        }
118
 
119
                        return $message;
120
                    }
121
 
122
                    continue;
123
                }
124
 
125
                if (Str::is($sourceKey, $key)) {
126
                    $message = $source[$sourceKey];
127
 
128
                    if ($sourceKey === $attribute && is_array($message) && isset($message[$lowerRule])) {
129
                        return $message[$lowerRule];
130
                    }
131
 
132
                    return $message;
133
                }
134
            }
135
        }
136
    }
137
 
138
    /**
139
     * Get the custom error message from the translator.
140
     *
141
     * @param  array|string  $keys
142
     * @return string
143
     */
144
    protected function getCustomMessageFromTranslator($keys)
145
    {
146
        foreach (Arr::wrap($keys) as $key) {
147
            if (($message = $this->translator->get($key)) !== $key) {
148
                return $message;
149
            }
150
 
151
            // If an exact match was not found for the key, we will collapse all of these
152
            // messages and loop through them and try to find a wildcard match for the
153
            // given key. Otherwise, we will simply return the key's value back out.
154
            $shortKey = preg_replace(
155
                '/^validation\.custom\./', '', $key
156
            );
157
 
158
            $message = $this->getWildcardCustomMessages(Arr::dot(
159
                (array) $this->translator->get('validation.custom')
160
            ), $shortKey, $key);
161
 
162
            if ($message !== $key) {
163
                return $message;
164
            }
165
        }
166
 
167
        return Arr::last(Arr::wrap($keys));
168
    }
169
 
170
    /**
171
     * Check the given messages for a wildcard key.
172
     *
173
     * @param  array  $messages
174
     * @param  string  $search
175
     * @param  string  $default
176
     * @return string
177
     */
178
    protected function getWildcardCustomMessages($messages, $search, $default)
179
    {
180
        foreach ($messages as $key => $message) {
181
            if ($search === $key || (Str::contains($key, ['*']) && Str::is($key, $search))) {
182
                return $message;
183
            }
184
        }
185
 
186
        return $default;
187
    }
188
 
189
    /**
190
     * Get the proper error message for an attribute and size rule.
191
     *
192
     * @param  string  $attribute
193
     * @param  string  $rule
194
     * @return string
195
     */
196
    protected function getSizeMessage($attribute, $rule)
197
    {
198
        $lowerRule = Str::snake($rule);
199
 
200
        // There are three different types of size validations. The attribute may be
201
        // either a number, file, or string so we will check a few things to know
202
        // which type of value it is and return the correct line for that type.
203
        $type = $this->getAttributeType($attribute);
204
 
205
        $key = "validation.{$lowerRule}.{$type}";
206
 
207
        return $this->translator->get($key);
208
    }
209
 
210
    /**
211
     * Get the data type of the given attribute.
212
     *
213
     * @param  string  $attribute
214
     * @return string
215
     */
216
    protected function getAttributeType($attribute)
217
    {
218
        // We assume that the attributes present in the file array are files so that
219
        // means that if the attribute does not have a numeric rule and the files
220
        // list doesn't have it we'll just consider it a string by elimination.
221
        if ($this->hasRule($attribute, $this->numericRules)) {
222
            return 'numeric';
223
        } elseif ($this->hasRule($attribute, ['Array'])) {
224
            return 'array';
225
        } elseif ($this->getValue($attribute) instanceof UploadedFile) {
226
            return 'file';
227
        }
228
 
229
        return 'string';
230
    }
231
 
232
    /**
233
     * Replace all error message place-holders with actual values.
234
     *
235
     * @param  string  $message
236
     * @param  string  $attribute
237
     * @param  string  $rule
238
     * @param  array  $parameters
239
     * @return string
240
     */
241
    public function makeReplacements($message, $attribute, $rule, $parameters)
242
    {
243
        $message = $this->replaceAttributePlaceholder(
244
            $message, $this->getDisplayableAttribute($attribute)
245
        );
246
 
247
        $message = $this->replaceInputPlaceholder($message, $attribute);
248
        $message = $this->replaceIndexPlaceholder($message, $attribute);
249
        $message = $this->replacePositionPlaceholder($message, $attribute);
250
 
251
        if (isset($this->replacers[Str::snake($rule)])) {
252
            return $this->callReplacer($message, $attribute, Str::snake($rule), $parameters, $this);
253
        } elseif (method_exists($this, $replacer = "replace{$rule}")) {
254
            return $this->$replacer($message, $attribute, $rule, $parameters);
255
        }
256
 
257
        return $message;
258
    }
259
 
260
    /**
261
     * Get the displayable name of the attribute.
262
     *
263
     * @param  string  $attribute
264
     * @return string
265
     */
266
    public function getDisplayableAttribute($attribute)
267
    {
268
        $primaryAttribute = $this->getPrimaryAttribute($attribute);
269
 
270
        $expectedAttributes = $attribute != $primaryAttribute
271
                    ? [$attribute, $primaryAttribute] : [$attribute];
272
 
273
        foreach ($expectedAttributes as $name) {
274
            // The developer may dynamically specify the array of custom attributes on this
275
            // validator instance. If the attribute exists in this array it is used over
276
            // the other ways of pulling the attribute name for this given attributes.
277
            if (isset($this->customAttributes[$name])) {
278
                return $this->customAttributes[$name];
279
            }
280
 
281
            // We allow for a developer to specify language lines for any attribute in this
282
            // application, which allows flexibility for displaying a unique displayable
283
            // version of the attribute name instead of the name used in an HTTP POST.
284
            if ($line = $this->getAttributeFromTranslations($name)) {
285
                return $line;
286
            }
287
        }
288
 
289
        // When no language line has been specified for the attribute and it is also
290
        // an implicit attribute we will display the raw attribute's name and not
291
        // modify it with any of these replacements before we display the name.
292
        if (isset($this->implicitAttributes[$primaryAttribute])) {
293
            return ($formatter = $this->implicitAttributesFormatter)
294
                            ? $formatter($attribute)
295
                            : $attribute;
296
        }
297
 
298
        return str_replace('_', ' ', Str::snake($attribute));
299
    }
300
 
301
    /**
302
     * Get the given attribute from the attribute translations.
303
     *
304
     * @param  string  $name
305
     * @return string
306
     */
307
    protected function getAttributeFromTranslations($name)
308
    {
309
        return Arr::get($this->translator->get('validation.attributes'), $name);
310
    }
311
 
312
    /**
313
     * Replace the :attribute placeholder in the given message.
314
     *
315
     * @param  string  $message
316
     * @param  string  $value
317
     * @return string
318
     */
319
    protected function replaceAttributePlaceholder($message, $value)
320
    {
321
        return str_replace(
322
            [':attribute', ':ATTRIBUTE', ':Attribute'],
323
            [$value, Str::upper($value), Str::ucfirst($value)],
324
            $message
325
        );
326
    }
327
 
328
    /**
329
     * Replace the :index placeholder in the given message.
330
     *
331
     * @param  string  $message
332
     * @param  string  $attribute
333
     * @return string
334
     */
335
    protected function replaceIndexPlaceholder($message, $attribute)
336
    {
337
        return $this->replaceIndexOrPositionPlaceholder(
338
            $message, $attribute, 'index'
339
        );
340
    }
341
 
342
    /**
343
     * Replace the :position placeholder in the given message.
344
     *
345
     * @param  string  $message
346
     * @param  string  $attribute
347
     * @return string
348
     */
349
    protected function replacePositionPlaceholder($message, $attribute)
350
    {
351
        return $this->replaceIndexOrPositionPlaceholder(
352
            $message, $attribute, 'position', fn ($segment) => $segment + 1
353
        );
354
    }
355
 
356
    /**
357
     * Replace the :index or :position placeholder in the given message.
358
     *
359
     * @param  string  $message
360
     * @param  string  $attribute
361
     * @param  string  $placeholder
362
     * @param  \Closure|null  $modifier
363
     * @return string
364
     */
365
    protected function replaceIndexOrPositionPlaceholder($message, $attribute, $placeholder, Closure $modifier = null)
366
    {
367
        $segments = explode('.', $attribute);
368
 
369
        $modifier ??= fn ($value) => $value;
370
 
371
        $numericIndex = 1;
372
 
373
        foreach ($segments as $segment) {
374
            if (is_numeric($segment)) {
375
                if ($numericIndex === 1) {
376
                    $message = str_ireplace(':'.$placeholder, $modifier((int) $segment), $message);
377
                }
378
 
379
                $message = str_ireplace(
380
                    ':'.$this->numberToIndexOrPositionWord($numericIndex).'-'.$placeholder,
381
                    $modifier((int) $segment),
382
                    $message
383
                );
384
 
385
                $numericIndex++;
386
            }
387
        }
388
 
389
        return $message;
390
    }
391
 
392
    /**
393
     * Get the word for a index or position segment.
394
     *
395
     * @param  int  $value
396
     * @return string
397
     */
398
    protected function numberToIndexOrPositionWord(int $value)
399
    {
400
        return [
401
            1 => 'first',
402
            2 => 'second',
403
            3 => 'third',
404
            4 => 'fourth',
405
            5 => 'fifth',
406
            6 => 'sixth',
407
            7 => 'seventh',
408
            8 => 'eighth',
409
            9 => 'ninth',
410
            10 => 'tenth',
411
        ][(int) $value] ?? 'other';
412
    }
413
 
414
    /**
415
     * Replace the :input placeholder in the given message.
416
     *
417
     * @param  string  $message
418
     * @param  string  $attribute
419
     * @return string
420
     */
421
    protected function replaceInputPlaceholder($message, $attribute)
422
    {
423
        $actualValue = $this->getValue($attribute);
424
 
425
        if (is_scalar($actualValue) || is_null($actualValue)) {
426
            $message = str_replace(':input', $this->getDisplayableValue($attribute, $actualValue), $message);
427
        }
428
 
429
        return $message;
430
    }
431
 
432
    /**
433
     * Get the displayable name of the value.
434
     *
435
     * @param  string  $attribute
436
     * @param  mixed  $value
437
     * @return string
438
     */
439
    public function getDisplayableValue($attribute, $value)
440
    {
441
        if (isset($this->customValues[$attribute][$value])) {
442
            return $this->customValues[$attribute][$value];
443
        }
444
 
445
        $key = "validation.values.{$attribute}.{$value}";
446
 
447
        if (($line = $this->translator->get($key)) !== $key) {
448
            return $line;
449
        }
450
 
451
        if (is_bool($value)) {
452
            return $value ? 'true' : 'false';
453
        }
454
 
455
        if (is_null($value)) {
456
            return 'empty';
457
        }
458
 
459
        return (string) $value;
460
    }
461
 
462
    /**
463
     * Transform an array of attributes to their displayable form.
464
     *
465
     * @param  array  $values
466
     * @return array
467
     */
468
    protected function getAttributeList(array $values)
469
    {
470
        $attributes = [];
471
 
472
        // For each attribute in the list we will simply get its displayable form as
473
        // this is convenient when replacing lists of parameters like some of the
474
        // replacement functions do when formatting out the validation message.
475
        foreach ($values as $key => $value) {
476
            $attributes[$key] = $this->getDisplayableAttribute($value);
477
        }
478
 
479
        return $attributes;
480
    }
481
 
482
    /**
483
     * Call a custom validator message replacer.
484
     *
485
     * @param  string  $message
486
     * @param  string  $attribute
487
     * @param  string  $rule
488
     * @param  array  $parameters
489
     * @param  \Illuminate\Validation\Validator  $validator
490
     * @return string|null
491
     */
492
    protected function callReplacer($message, $attribute, $rule, $parameters, $validator)
493
    {
494
        $callback = $this->replacers[$rule];
495
 
496
        if ($callback instanceof Closure) {
497
            return $callback(...func_get_args());
498
        } elseif (is_string($callback)) {
499
            return $this->callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters, $validator);
500
        }
501
    }
502
 
503
    /**
504
     * Call a class based validator message replacer.
505
     *
506
     * @param  string  $callback
507
     * @param  string  $message
508
     * @param  string  $attribute
509
     * @param  string  $rule
510
     * @param  array  $parameters
511
     * @param  \Illuminate\Validation\Validator  $validator
512
     * @return string
513
     */
514
    protected function callClassBasedReplacer($callback, $message, $attribute, $rule, $parameters, $validator)
515
    {
516
        [$class, $method] = Str::parseCallback($callback, 'replace');
517
 
518
        return $this->container->make($class)->{$method}(...array_slice(func_get_args(), 1));
519
    }
520
}