Completed
Push — ip-validator-message ( 93ed68...63eb47 )
by Dmitry
09:50
created

IpValidator::prepareRanges()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6
Metric Value
dl 0
loc 16
rs 8.8571
ccs 14
cts 14
cp 1
cc 6
eloc 12
nc 3
nop 1
crap 6
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\validators;
9
10
use Yii;
11
use yii\base\InvalidConfigException;
12
use yii\helpers\Html;
13
use yii\helpers\Json;
14
use yii\helpers\StringHelper;
15
use yii\web\JsExpression;
16
17
/**
18
 * The validator checks if the attribute value is a valid IPv4/IPv6 address or subnet.
19
 *
20
 * It also may change attribute's value if normalization of IPv6 expansion is enabled.
21
 *
22
 * The following are examples of validation rules using this validator:
23
 *
24
 * ```php
25
 * ['ip_address', 'ip'], // IPv4 or IPv6 address
26
 * ['ip_address', 'ip', 'ipv6' => false], // IPv4 address (IPv6 is disabled)
27
 * ['ip_address', 'ip', 'subnet' => true], // requires a CIDR prefix (like 10.0.0.1/24) for the IP address
28
 * ['ip_address', 'ip', 'subnet' => null], // CIDR prefix is optional
29
 * ['ip_address', 'ip', 'subnet' => null, 'normalize' => true], // CIDR prefix is optional and will be added when missing
30
 * ['ip_address', 'ip', 'ranges' => ['192.168.0.0/24']], // only IP addresses from the specified subnet are allowed
31
 * ['ip_address', 'ip', 'ranges' => ['!192.168.0.0/24', 'any']], // any IP is allowed except IP in the specified subnet
32
 * ['ip_address', 'ip', 'expandIPv6' => true], // expands IPv6 address to a full notation format
33
 * ```
34
 *
35
 * @author Dmitry Naumenko <[email protected]>
36
 * @since 2.0.7
37
 */
38
class IpValidator extends Validator
39
{
40
    /**
41
     * The length of IPv6 address in bits
42
     */
43
    const IPV6_ADDRESS_LENGTH = 128;
44
    /**
45
     * The length of IPv4 address in bits
46
     */
47
    const IPV4_ADDRESS_LENGTH = 32;
48
    /**
49
     * Negation char. Used to negate [[ranges]] or [[networks]]
50
     * or to negate validating value when [[negation]] is set to `true`
51
     * @see negation
52
     * @see networks
53
     * @see ranges
54
     */
55
    const NEGATION_CHAR = '!';
56
57
    /**
58
     * @var array The network aliases, that can be used in [[ranges]].
59
     *  - key - alias name
60
     *  - value - array of strings. String can be an IP range, IP address or another alias. String can be
61
     *    negated with [[NEGATION_CHAR]] (independent of `negation` option).
62
     *
63
     * The following aliases are defined by default:
64
     *  - `*`: `any`
65
     *  - `any`: `0.0.0.0/0, ::/0`
66
     *  - `private`: `10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fd00::/8`
67
     *  - `multicast`: `224.0.0.0/4, ff00::/8`
68
     *  - `linklocal`: `169.254.0.0/16, fe80::/10`
69
     *  - `localhost`: `127.0.0.0/8', ::1`
70
     *  - `documentation`: `192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 2001:db8::/32`
71
     *  - `system`: `multicast, linklocal, localhost, documentation`
72
     *
73
     */
74
    public $networks = [
75
        '*' => ['any'],
76
        'any' => ['0.0.0.0/0', '::/0'],
77
        'private' => ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', 'fd00::/8'],
78
        'multicast' => ['224.0.0.0/4', 'ff00::/8'],
79
        'linklocal' => ['169.254.0.0/16', 'fe80::/10'],
80
        'localhost' => ['127.0.0.0/8', '::1'],
81
        'documentation' => ['192.0.2.0/24', '198.51.100.0/24', '203.0.113.0/24', '2001:db8::/32'],
82
        'system' => ['multicast', 'linklocal', 'localhost', 'documentation'],
83
    ];
84
    /**
85
     * @var boolean whether the validating value can be an IPv6 address. Defaults to `true`.
86
     */
87
    public $ipv6 = true;
88
    /**
89
     * @var boolean whether the validating value can be an IPv4 address. Defaults to `true`.
90
     */
91
    public $ipv4 = true;
92
    /**
93
     * @var boolean whether the address can be an IP with CIDR subnet, like `192.168.10.0/24`.
94
     * The following values are possible:
95
     *
96
     * - `false` - the address must not have a subnet (default).
97
     * - `true` - specifying a subnet is required.
98
     * - `null` - specifying a subnet is optional.
99
     */
100
    public $subnet = false;
101
    /**
102
     * @var boolean whether to add the CIDR prefix with the smallest length (32 for IPv4 and 128 for IPv6) to an
103
     * address without it. Works only when `subnet` is not `false`. For example:
104
     *  - `10.0.1.5` will normalized to `10.0.1.5/32`
105
     *  - `2008:db0::1` will be normalized to `2008:db0::1/128`
106
     *    Defaults to `false`.
107
     * @see subnet
108
     */
109
    public $normalize = false;
110
    /**
111
     * @var boolean whether address may have a [[NEGATION_CHAR]] character at the beginning.
112
     * Defaults to `false`.
113
     */
114
    public $negation = false;
115
    /**
116
     * @var boolean whether to expand an IPv6 address to the full notation format.
117
     * Defaults to `false`.
118
     */
119
    public $expandIPv6 = false;
120
    /**
121
     * @var string Regexp-pattern to validate IPv4 address
122
     */
123
    public $ipv4Pattern = '/^(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))$/';
124
    /**
125
     * @var string Regexp-pattern to validate IPv6 address
126
     */
127
    public $ipv6Pattern = '/^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/';
128
    /**
129
     * @var string user-defined error message is used when validation fails due to the wrong IP address format.
130
     *
131
     * You may use the following placeholders in the message:
132
     *
133
     * - `{attribute}`: the label of the attribute being validated
134
     * - `{value}`: the value of the attribute being validated
135
     */
136
    public $message;
137
    /**
138
     * @var string user-defined error message is used when validation fails due to the disabled IPv6 validation.
139
     *
140
     * You may use the following placeholders in the message:
141
     *
142
     * - `{attribute}`: the label of the attribute being validated
143
     * - `{value}`: the value of the attribute being validated
144
     *
145
     * @see ipv6
146
     */
147
    public $ipv6NotAllowed;
148
    /**
149
     * @var string user-defined error message is used when validation fails due to the disabled IPv4 validation.
150
     *
151
     * You may use the following placeholders in the message:
152
     *
153
     * - `{attribute}`: the label of the attribute being validated
154
     * - `{value}`: the value of the attribute being validated
155
     *
156
     * @see ipv4
157
     */
158
    public $ipv4NotAllowed;
159
    /**
160
     * @var string user-defined error message is used when validation fails due to the wrong CIDR.
161
     *
162
     * You may use the following placeholders in the message:
163
     *
164
     * - `{attribute}`: the label of the attribute being validated
165
     * - `{value}`: the value of the attribute being validated
166
     * @see subnet
167
     */
168
    public $wrongCidr;
169
    /**
170
     * @var string user-defined error message is used when validation fails due to subnet [[subnet]] set to 'only',
171
     * but the CIDR prefix is not set.
172
     *
173
     * You may use the following placeholders in the message:
174
     *
175
     * - `{attribute}`: the label of the attribute being validated
176
     * - `{value}`: the value of the attribute being validated
177
     *
178
     * @see subnet
179
     */
180
    public $noSubnet;
181
    /**
182
     * @var string user-defined error message is used when validation fails
183
     * due to [[subnet]] is false, but CIDR prefix is present.
184
     *
185
     * You may use the following placeholders in the message:
186
     *
187
     * - `{attribute}`: the label of the attribute being validated
188
     * - `{value}`: the value of the attribute being validated
189
     *
190
     * @see subnet
191
     */
192
    public $hasSubnet;
193
    /**
194
     * @var string user-defined error message is used when validation fails due to IP address
195
     * is not not allowed by [[ranges]] check.
196
     *
197
     * You may use the following placeholders in the message:
198
     *
199
     * - `{attribute}`: the label of the attribute being validated
200
     * - `{value}`: the value of the attribute being validated
201
     *
202
     * @see ranges
203
     */
204
    public $notInRange;
205
206
    /**
207
     * @var array
208
     */
209
    private $_ranges = [];
210
211
212
    /**
213
     * @inheritdoc
214
     */
215 19
    public function init()
216
    {
217 19
        parent::init();
218
219 19
        if (!$this->ipv4 && !$this->ipv6) {
220 1
            throw new InvalidConfigException('Both IPv4 and IPv6 checks can not be disabled at the same time');
221
        }
222
223 18
        if (!defined('AF_INET6') && $this->ipv6) {
224
            throw new InvalidConfigException('IPv6 validation can not be used. PHP is compiled without IPv6');
225
        }
226
227 18
        if ($this->message === null) {
228 18
            $this->message = Yii::t('yii', '{attribute} must be a valid IP address.');
229 18
        }
230 18
        if ($this->ipv6NotAllowed === null) {
231 18
            $this->ipv6NotAllowed = Yii::t('yii', '{attribute} must not be an IPv6 address.');
232 18
        }
233 18
        if ($this->ipv4NotAllowed === null) {
234 18
            $this->ipv4NotAllowed = Yii::t('yii', '{attribute} must not be an IPv4 address.');
235 18
        }
236 18
        if ($this->wrongCidr === null) {
237 18
            $this->wrongCidr = Yii::t('yii', '{attribute} contains wrong subnet mask.');
238 18
        }
239 18
        if ($this->noSubnet === null) {
240 18
            $this->noSubnet = Yii::t('yii', '{attribute} must be an IP address with specified subnet.');
241 18
        }
242 18
        if ($this->hasSubnet === null) {
243 18
            $this->hasSubnet = Yii::t('yii', '{attribute} must not be a subnet.');
244 18
        }
245 18
        if ($this->notInRange === null) {
246 18
            $this->notInRange = Yii::t('yii', '{attribute} is not in the allowed range.');
247 18
        }
248 18
    }
249
250
    /**
251
     * Set the IPv4 or IPv6 ranges that are allowed or forbidden.
252
     *
253
     * The following preparation tasks are performed:
254
     *
255
     * - Recursively substitutes aliases (described in [[networks]]) with their values.
256
     * - Removes duplicates
257
     *
258
     * @property array the IPv4 or IPv6 ranges that are allowed or forbidden.
259
     * See [[setRanges()]] for detailed description.
260
     * @param array $ranges the IPv4 or IPv6 ranges that are allowed or forbidden.
261
     *
262
     * When the array is empty, or the option not set, all IP addresses are allowed.
263
     *
264
     * Otherwise, the rules are checked sequentially until the first match is found.
265
     * An IP address is forbidden, when it has not matched any of the rules.
266
     *
267
     * Example:
268
     *
269
     * ```php
270
     * [
271
     *      'ranges' => [
272
     *          '192.168.10.128'
273
     *          '!192.168.10.0/24',
274
     *          'any' // allows any other IP addresses
275
     *      ]
276
     * ]
277
     * ```
278
     *
279
     * In this example, access is allowed for all the IPv4 and IPv6 addresses excluding the `192.168.10.0/24` subnet.
280
     * IPv4 address `192.168.10.128` is also allowed, because it is listed before the restriction.
281
     */
282 8
    public function setRanges($ranges)
283
    {
284 8
        $this->_ranges = $this->prepareRanges((array) $ranges);
285 8
    }
286
287
    /**
288
     * @return array The IPv4 or IPv6 ranges that are allowed or forbidden.
289
     */
290 13
    public function getRanges()
291
    {
292 13
        return $this->_ranges;
293
    }
294
295
    /**
296
     * @inheritdoc
297
     */
298 12
    protected function validateValue($value)
299
    {
300 12
        $result = $this->validateSubnet($value);
301 12
        if (is_array($result)) {
302 12
            $result[1] = array_merge(['ip' => is_array($value) ? 'array()' : $value], $result[1]);
303 12
            return $result;
304
        } else {
305 7
            return null;
306
        }
307
    }
308
309
    /**
310
     * @inheritdoc
311
     */
312 2
    public function validateAttribute($model, $attribute)
313
    {
314 2
        $value = $model->$attribute;
315
316 2
        $result = $this->validateSubnet($value);
317 2
        if (is_array($result)) {
318 2
            $result[1] = array_merge(['ip' => is_array($value) ? 'array()' : $value], $result[1]);
319 2
            $this->addError($model, $attribute, $result[0], $result[1]);
320 2
        } else {
321 2
            $model->$attribute = $result;
322
        }
323 2
    }
324
325
    /**
326
     * Validates an IPv4/IPv6 address or subnet
327
     *
328
     * @param $ip string
329
     * @return string|array
330
     * string - the validation was successful;
331
     * array  - an error occurred during the validation.
332
     * Array[0] contains the text of an error, array[1] contains values for the placeholders in the error message
333
     */
334 14
    private function validateSubnet($ip)
335
    {
336 14
        if (!is_string($ip)) {
337 4
            return [$this->message, []];
338
        }
339
340 10
        $negation = null;
341 10
        $cidr = null;
342 10
        $isCidrDefault = false;
343
344 10
        if (preg_match($this->getIpParsePattern(), $ip, $matches)) {
345 10
            $negation = ($matches[1] !== '') ? $matches[1] : null;
346 10
            $ip = $matches[2];
347 10
            $cidr = isset($matches[4]) ? $matches[4] : null;
348 10
        }
349
350 10
        if ($this->subnet === true && $cidr === null) {
351 3
            return [$this->noSubnet, []];
352
        }
353 10
        if ($this->subnet === false && $cidr !== null) {
354 5
            return [$this->hasSubnet, []];
355
        }
356 10
        if ($this->negation === false && $negation !== null) {
357 3
            return [$this->message, []];
358
        }
359
360 10
        if ($this->getIpVersion($ip) == 6) {
361 6
            if ($cidr !== null) {
362 3
                if ($cidr > static::IPV6_ADDRESS_LENGTH || $cidr < 0) {
363 3
                    return [$this->wrongCidr, []];
364
                }
365 3
            } else {
366 6
                $isCidrDefault = true;
367 6
                $cidr = static::IPV6_ADDRESS_LENGTH;
368
            }
369
370 6
            if (!$this->ipv6) {
371 2
                return [$this->ipv6NotAllowed, []];
372
            }
373 6
            if (!$this->validateIPv6($ip)) {
374 2
                return [$this->message, []];
375
            }
376
377 6
            if ($this->expandIPv6) {
378 1
                $ip = $this->expandIPv6($ip);
379 1
            }
380 6
        } else {
381 7
            if ($cidr !== null) {
382 4
                if ($cidr > static::IPV4_ADDRESS_LENGTH || $cidr < 0) {
383 2
                    return [$this->wrongCidr, []];
384
                }
385 4
            } else {
386 7
                $isCidrDefault = true;
387 7
                $cidr = static::IPV4_ADDRESS_LENGTH;
388
            }
389
390 7
            if (!$this->ipv4) {
391 2
                return [$this->ipv4NotAllowed, []];
392
            }
393 7
            if (!$this->validateIPv4($ip)) {
394 3
                return [$this->message, []];
395
            }
396
        }
397
398 9
        if (!$this->isAllowed($ip, $cidr)) {
399 4
            return [$this->notInRange, []];
400
        }
401
402 9
        $result = $negation . $ip;
403
404 9
        if ($this->subnet !== false && (!$isCidrDefault || $isCidrDefault && $this->normalize)) {
405 7
            $result .= "/$cidr";
406 7
        }
407
408 9
        return $result;
409
    }
410
411
    /**
412
     * Expands an IPv6 address to it's full notation. For example `2001:db8::1` will be
413
     * expanded to `2001:0db8:0000:0000:0000:0000:0000:0001`
414
     *
415
     * @param string $ip the original IPv6
416
     * @return string the expanded IPv6
417
     */
418 1
    private function expandIPv6($ip)
419
    {
420 1
        $hex = unpack('H*hex', inet_pton($ip));
421 1
        return substr(preg_replace('/([a-f0-9]{4})/i', '$1:', $hex['hex']), 0, -1);
422
    }
423
424
    /**
425
     * The method checks whether the IP address with specified CIDR is allowed according to the [[ranges]] list.
426
     *
427
     * @param string $ip
428
     * @param integer $cidr
429
     * @return boolean
430
     * @see ranges
431
     */
432 9
    private function isAllowed($ip, $cidr)
433
    {
434 9
        if (empty($this->ranges)) {
0 ignored issues
show
Bug introduced by
The property ranges does not seem to exist. Did you mean _ranges?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
435 5
            return true;
436
        }
437
438 4
        foreach ($this->ranges as $string) {
0 ignored issues
show
Bug introduced by
The property ranges does not seem to exist. Did you mean _ranges?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
439 4
            list($isNegated, $range) = $this->parseNegatedRange($string);
440 4
            if ($this->inRange($ip, $cidr, $range)) {
441 4
                return !$isNegated;
442
            }
443 4
        }
444
445 3
        return false;
446
    }
447
448
    /**
449
     * Parses IP address/range for the negation with [[NEGATION_CHAR]].
450
     *
451
     * @param $string
452
     * @return array `[0 => boolean, 1 => string]`
453
     *  - boolean: whether the string is negated
454
     *  - string: the string without negation (when the negation were present)
455
     */
456 8
    private function parseNegatedRange ($string) {
457 8
        $isNegated = strpos($string, static::NEGATION_CHAR) === 0;
458 8
        return [$isNegated, ($isNegated ? substr($string, strlen(static::NEGATION_CHAR)) : $string)];
459
    }
460
461
    /**
462
     * Prepares array to fill in [[ranges]]:
463
     *  - Recursively substitutes aliases, described in [[networks]] with their values
464
     *  - Removes duplicates
465
     *
466
     *
467
     * @param $ranges
468
     * @return array
469
     * @see networks
470
     */
471 8
    private function prepareRanges($ranges) {
472 8
        $result = [];
473 8
        foreach ($ranges as $string) {
474 8
            list($isRangeNegated, $range) = $this->parseNegatedRange($string);
475 8
            if (isset($this->networks[$range])) {
476 6
                $replacements = $this->prepareRanges($this->networks[$range]);
477 6
                foreach ($replacements as &$replacement) {
478 6
                    list($isReplacementNegated, $replacement) = $this->parseNegatedRange($replacement);
479 6
                    $result[] = ($isRangeNegated && !$isReplacementNegated ? static::NEGATION_CHAR : '') . $replacement;
480 6
                }
481 6
            } else {
482 8
                $result[] = $string;
483
            }
484 8
        }
485 8
        return array_unique($result);
486
    }
487
488
    /**
489
     * Validates IPv4 address
490
     *
491
     * @param string $value
492
     * @return boolean
493
     */
494 7
    protected function validateIPv4($value)
495
    {
496 7
        return preg_match($this->ipv4Pattern, $value) !== 0;
497
    }
498
499
    /**
500
     * Validates IPv6 address
501
     *
502
     * @param string $value
503
     * @return boolean
504
     */
505 6
    protected function validateIPv6($value)
506
    {
507 6
        return preg_match($this->ipv6Pattern, $value) !== 0;
508
    }
509
510
    /**
511
     * Gets the IP version
512
     *
513
     * @param string $ip
514
     * @return integer
515
     */
516 10
    private function getIpVersion($ip)
517
    {
518 10
        return strpos($ip, ':') === false ? 4 : 6;
519
    }
520
521
    /**
522
     * Used to get the Regexp pattern for initial IP address parsing
523
     * @return string
524
     */
525 10
    private function getIpParsePattern()
526
    {
527 10
        return '/^(' . preg_quote(static::NEGATION_CHAR) . '?)(.+?)(\/(\d+))?$/';
528
    }
529
530
    /**
531
     * Checks whether the IP is in subnet range
532
     *
533
     * @param string $ip an IPv4 or IPv6 address
534
     * @param integer $cidr
535
     * @param string $range subnet in CIDR format e.g. `10.0.0.0/8` or `2001:af::/64`
536
     * @return bool
537
     */
538 4
    private function inRange($ip, $cidr, $range)
539
    {
540 4
        $ipVersion = $this->getIpVersion($ip);
541 4
        $binIp = $this->ip2bin($ip);
542
543 4
        $parts = explode('/', $range);
544 4
        $net = array_shift($parts);
545 4
        $range_cidr = array_shift($parts);
546
547
548 4
        $netVersion = $this->getIpVersion($net);
549 4
        if ($ipVersion !== $netVersion) {
550 2
            return false;
551
        }
552 4
        if ($range_cidr === null) {
553 2
            $range_cidr = $netVersion === 4 ? static::IPV4_ADDRESS_LENGTH : static::IPV6_ADDRESS_LENGTH;
554 2
        }
555
556 4
        $binNet = $this->ip2bin($net);
557 4
        if (substr($binIp, 0, $range_cidr) === substr($binNet, 0, $range_cidr) && $cidr >= $range_cidr) {
558 4
            return true;
559
        }
560 4
        return false;
561
    }
562
563
    /**
564
     * Converts IP address to bits representation
565
     *
566
     * @param string $ip
567
     * @return string bits as a string
568
     */
569 4
    private function ip2bin($ip)
570
    {
571 4
        if ($this->getIpVersion($ip) === 4) {
572 3
            return str_pad(base_convert(ip2long($ip), 10, 2), static::IPV4_ADDRESS_LENGTH, '0', STR_PAD_LEFT);
573
        } else {
574 3
            $unpack = unpack('A16', inet_pton($ip));
575 3
            $binStr = array_shift($unpack);
576 3
            $bytes = static::IPV6_ADDRESS_LENGTH / 8; // 128 bit / 8 = 16 bytes
577 3
            $result = '';
578 3
            while ($bytes-- > 0) {
579 3
                $result = sprintf('%08b', isset($binStr[$bytes]) ? ord($binStr[$bytes]) : '0') . $result;
580 3
            }
581 3
            return $result;
582
        }
583
    }
584
585
    /**
586
     * @inheritdoc
587
     */
588
    public function clientValidateAttribute($model, $attribute, $view)
589
    {
590
        $messages = [
591
            'ipv6NotAllowed' => $this->ipv6NotAllowed,
592
            'ipv4NotAllowed' => $this->ipv4NotAllowed,
593
            'message' => $this->message,
594
            'noSubnet' => $this->noSubnet,
595
            'hasSubnet' => $this->hasSubnet,
596
        ];
597
        foreach ($messages as &$message) {
598
            $message = Yii::$app->getI18n()->format($message, [
599
                'attribute' => $model->getAttributeLabel($attribute),
600
            ], Yii::$app->language);
601
        }
602
603
        $options = [
604
            'ipv4Pattern' => new JsExpression(Html::escapeJsRegularExpression($this->ipv4Pattern)),
605
            'ipv6Pattern' => new JsExpression(Html::escapeJsRegularExpression($this->ipv6Pattern)),
606
            'messages' => $messages,
607
            'ipv4' => (boolean)$this->ipv4,
608
            'ipv6' => (boolean)$this->ipv6,
609
            'ipParsePattern' => new JsExpression(Html::escapeJsRegularExpression($this->getIpParsePattern())),
610
            'negation' => $this->negation,
611
            'subnet' => $this->subnet
612
        ];
613
        if ($this->skipOnEmpty) {
614
            $options['skipOnEmpty'] = 1;
615
        }
616
617
        ValidationAsset::register($view);
618
619
        return 'yii.validation.ip(value, messages, ' . Json::htmlEncode($options) . ');';
620
    }
621
}
622