Completed
Push — ip-validator-message ( 93ed68 )
by Carsten
08:12
created

IpValidator   C

Complexity

Total Complexity 79

Size/Duplication

Total Lines 584
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 85.64%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 79
c 2
b 0
f 0
lcom 1
cbo 7
dl 0
loc 584
ccs 161
cts 188
cp 0.8564
rs 5.442

17 Methods

Rating   Name   Duplication   Size   Complexity  
D init() 0 34 12
A setRanges() 0 4 1
A getRanges() 0 4 1
A validateValue() 0 10 3
A validateAttribute() 0 12 3
D validateSubnet() 0 76 28
A expandIPv6() 0 5 1
A isAllowed() 0 15 4
A parseNegatedRange() 0 4 2
B prepareRanges() 0 16 6
A validateIPv4() 0 4 1
A validateIPv6() 0 4 1
A getIpVersion() 0 4 2
A getIpParsePattern() 0 4 1
B inRange() 0 24 6
A ip2bin() 0 15 4
B clientValidateAttribute() 0 33 3

How to fix   Complexity   

Complex Class

Complex classes like IpValidator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use IpValidator, and based on these observations, apply Extract Interface, too.

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
 * @author Dmitry Naumenko <[email protected]>
23
 * @since 2.0.7
24
 */
25
class IpValidator extends Validator
26
{
27
    /**
28
     * The length of IPv6 address in bits
29
     */
30
    const IPV6_ADDRESS_LENGTH = 128;
31
    /**
32
     * The length of IPv4 address in bits
33
     */
34
    const IPV4_ADDRESS_LENGTH = 32;
35
    /**
36
     * Negation char. Used to negate [[ranges]] or [[networks]]
37
     * or to negate validating value when [[negation]] is set to `true`
38
     * @see negation
39
     * @see networks
40
     * @see ranges
41
     */
42
    const NEGATION_CHAR = '!';
43
44
    /**
45
     * @var array The network aliases, that can be used in [[ranges]].
46
     *  - key - alias name
47
     *  - value - array of strings. String can be an IP range, IP address or another alias. String can be
48
     * negated with [[NEGATION_CHAR]] (independent of `negation` option).
49
     *
50
     * The following aliases are defined by default:
51
     *  - `*`: `any`
52
     *  - `any`: `0.0.0.0/0, ::/0`
53
     *  - `private`: `10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fd00::/8`
54
     *  - `multicast`: `224.0.0.0/4, ff00::/8`
55
     *  - `linklocal`: `169.254.0.0/16, fe80::/10`
56
     *  - `localhost`: `127.0.0.0/8', ::1`
57
     *  - `documentation`: `192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24, 2001:db8::/32`
58
     *  - `system`: `multicast, linklocal, localhost, documentation`
59
     *
60
     */
61
    public $networks = [
62
        '*' => ['any'],
63
        'any' => ['0.0.0.0/0', '::/0'],
64
        'private' => ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', 'fd00::/8'],
65
        'multicast' => ['224.0.0.0/4', 'ff00::/8'],
66
        'linklocal' => ['169.254.0.0/16', 'fe80::/10'],
67
        'localhost' => ['127.0.0.0/8', '::1'],
68
        'documentation' => ['192.0.2.0/24', '198.51.100.0/24', '203.0.113.0/24', '2001:db8::/32'],
69
        'system' => ['multicast', 'linklocal', 'localhost', 'documentation'],
70
    ];
71
    /**
72
     * @var boolean whether the validating value can be an IPv6 address. Defaults to `true`.
73
     */
74
    public $ipv6 = true;
75
    /**
76
     * @var boolean whether the validating value can be an IPv4 address. Defaults to `true`.
77
     */
78
    public $ipv4 = true;
79
    /**
80
     * @var boolean whether the address can be an IP with CIDR subnet, like `192.168.10.0/24`.
81
     * The following values are possible:
82
     *
83
     * - `false` - the address must not have a subnet (default).
84
     * - `true` - specifying a subnet is required.
85
     * - `null` - specifying a subnet is optional.
86
     */
87
    public $subnet = false;
88
    /**
89
     * @var boolean whether to add the CIDR prefix with the smallest length (32 for IPv4 and 128 for IPv6) to an
90
     * address without it. Works only when `subnet` is not `false`. For example:
91
     *  - `10.0.1.5` will normalized to `10.0.1.5/32`
92
     *  - `2008:db0::1` will be normalized to `2008:db0::1/128`
93
     * Defaults to `false`.
94
     * @see subnet
95
     */
96
    public $normalize = false;
97
    /**
98
     * @var boolean whether address may have a [[NEGATION_CHAR]] character at the beginning.
99
     * Defaults to `false`.
100
     */
101
    public $negation = false;
102
    /**
103
     * @var boolean whether to expand an IPv6 address to the full notation format.
104
     * Defaults to `false`.
105
     */
106
    public $expandIPv6 = false;
107
    /**
108
     * @var string Regexp-pattern to validate IPv4 address
109
     */
110
    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]))$/';
111
    /**
112
     * @var string Regexp-pattern to validate IPv6 address
113
     */
114
    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]))$/';
115
    /**
116
     * @var string user-defined error message is used when validation fails due to the wrong IP address format.
117
     *
118
     * You may use the following placeholders in the message:
119
     *
120
     * - `{attribute}`: the label of the attribute being validated
121
     * - `{value}`: the value of the attribute being validated
122
     */
123
    public $message;
124
    /**
125
     * @var string user-defined error message is used when validation fails due to the disabled IPv6 validation.
126
     *
127
     * You may use the following placeholders in the message:
128
     *
129
     * - `{attribute}`: the label of the attribute being validated
130
     * - `{value}`: the value of the attribute being validated
131
     *
132
     * @see ipv6
133
     */
134
    public $ipv6NotAllowed;
135
    /**
136
     * @var string user-defined error message is used when validation fails due to the disabled IPv4 validation.
137
     *
138
     * You may use the following placeholders in the message:
139
     *
140
     * - `{attribute}`: the label of the attribute being validated
141
     * - `{value}`: the value of the attribute being validated
142
     *
143
     * @see ipv4
144
     */
145
    public $ipv4NotAllowed;
146
    /**
147
     * @var string user-defined error message is used when validation fails due to the wrong CIDR.
148
     *
149
     * You may use the following placeholders in the message:
150
     *
151
     * - `{attribute}`: the label of the attribute being validated
152
     * - `{value}`: the value of the attribute being validated
153
     * @see subnet
154
     */
155
    public $wrongCidr;
156
    /**
157
     * @var string user-defined error message is used when validation fails due to subnet [[subnet]] set to 'only',
158
     * but the CIDR prefix is not set.
159
     *
160
     * You may use the following placeholders in the message:
161
     *
162
     * - `{attribute}`: the label of the attribute being validated
163
     * - `{value}`: the value of the attribute being validated
164
     *
165
     * @see subnet
166
     */
167
    public $noSubnet;
168
    /**
169
     * @var string user-defined error message is used when validation fails
170
     * due to [[subnet]] is false, but CIDR prefix is present.
171
     *
172
     * You may use the following placeholders in the message:
173
     *
174
     * - `{attribute}`: the label of the attribute being validated
175
     * - `{value}`: the value of the attribute being validated
176
     *
177
     * @see subnet
178
     */
179
    public $hasSubnet;
180
    /**
181
     * @var string user-defined error message is used when validation fails due to IP address
182
     * is not not allowed by [[ranges]] check.
183
     *
184
     * You may use the following placeholders in the message:
185
     *
186
     * - `{attribute}`: the label of the attribute being validated
187
     * - `{value}`: the value of the attribute being validated
188
     *
189
     * @see ranges
190
     */
191
    public $notInRange;
192
193
    /**
194
     * @var array
195
     */
196
    private $_ranges = [];
197
198
199
    /**
200
     * @inheritdoc
201
     */
202 19
    public function init()
203
    {
204 19
        parent::init();
205
206 19
        if (!$this->ipv4 && !$this->ipv6) {
207 1
            throw new InvalidConfigException('Both IPv4 and IPv6 checks can not be disabled at the same time');
208
        }
209
210 18
        if (!defined('AF_INET6') && $this->ipv6) {
211
            throw new InvalidConfigException('IPv6 validation can not be used. PHP is compiled without IPv6');
212
        }
213
214 18
        if ($this->message === null) {
215 18
            $this->message = Yii::t('yii', '{attribute} must be a valid IP address.');
216 18
        }
217 18
        if ($this->ipv6NotAllowed === null) {
218 18
            $this->ipv6NotAllowed = Yii::t('yii', '{attribute} must not be an IPv6 address.');
219 18
        }
220 18
        if ($this->ipv4NotAllowed === null) {
221 18
            $this->ipv4NotAllowed = Yii::t('yii', '{attribute} must not be an IPv4 address.');
222 18
        }
223 18
        if ($this->wrongCidr === null) {
224 18
            $this->wrongCidr = Yii::t('yii', '{attribute} contains wrong subnet mask.');
225 18
        }
226 18
        if ($this->noSubnet === null) {
227 18
            $this->noSubnet = Yii::t('yii', '{attribute} must be an IP address with specified subnet.');
228 18
        }
229 18
        if ($this->hasSubnet === null) {
230 18
            $this->hasSubnet = Yii::t('yii', '{attribute} must not be a subnet.');
231 18
        }
232 18
        if ($this->notInRange === null) {
233 18
            $this->notInRange = Yii::t('yii', '{attribute} is not in the allowed range.');
234 18
        }
235 18
    }
236
237
    /**
238
     * Set the IPv4 or IPv6 ranges that are allowed or forbidden.
239
     *
240
     * The following preparation tasks are performed:
241
     *
242
     * - Recursively substitutes aliases (described in [[networks]]) with their values.
243
     * - Removes duplicates
244
     *
245
     * @property array the IPv4 or IPv6 ranges that are allowed or forbidden.
246
     * See [[setRanges()]] for detailed description.
247
     * @param array $ranges the IPv4 or IPv6 ranges that are allowed or forbidden.
248
     *
249
     * When the array is empty, or the option not set, all IP addresses are allowed.
250
     *
251
     * Otherwise, the rules are checked sequentially until the first match is found.
252
     * An IP address is forbidden, when it has not matched any of the rules.
253
     *
254
     * Example:
255
     *
256
     * ```php
257
     * [
258
     *      'ranges' => [
259
     *          '192.168.10.128'
260
     *          '!192.168.10.0/24',
261
     *          'any' // allows any other IP addresses
262
     *      ]
263
     * ]
264
     * ```
265
     *
266
     * In this example, access is allowed for all the IPv4 and IPv6 addresses excluding the `192.168.10.0/24` subnet.
267
     * IPv4 address `192.168.10.128` is also allowed, because it is listed before the restriction.
268
     */
269 8
    public function setRanges($ranges)
270
    {
271 8
        $this->_ranges = $this->prepareRanges((array) $ranges);
272 8
    }
273
274
    /**
275
     * @return array The IPv4 or IPv6 ranges that are allowed or forbidden.
276
     */
277 13
    public function getRanges()
278
    {
279 13
        return $this->_ranges;
280
    }
281
282
    /**
283
     * @inheritdoc
284
     */
285 12
    protected function validateValue($value)
286
    {
287 12
        $result = $this->validateSubnet($value);
288 12
        if (is_array($result)) {
289 12
            $result[1] = array_merge(['ip' => is_array($value) ? 'array()' : $value], $result[1]);
290 12
            return $result;
291
        } else {
292 7
            return null;
293
        }
294
    }
295
296
    /**
297
     * @inheritdoc
298
     */
299 2
    public function validateAttribute($model, $attribute)
300
    {
301 2
        $value = $model->$attribute;
302
303 2
        $result = $this->validateSubnet($value);
304 2
        if (is_array($result)) {
305 2
            $result[1] = array_merge(['ip' => is_array($value) ? 'array()' : $value], $result[1]);
306 2
            $this->addError($model, $attribute, $result[0], $result[1]);
307 2
        } else {
308 2
            $model->$attribute = $result;
309
        }
310 2
    }
311
312
    /**
313
     * Validates an IPv4/IPv6 address or subnet
314
     *
315
     * @param $ip string
316
     * @return string|array
317
     *  string - the validation was successful;
318
     *  array  - an error occurred during the validation.
319
     * Array[0] contains the text of an error, array[1] contains values for the placeholders in the error message
320
     */
321 14
    private function validateSubnet($ip)
322
    {
323 14
        if (!is_string($ip)) {
324 4
            return [$this->message, []];
325
        }
326
327 10
        $negation = null;
328 10
        $cidr = null;
329 10
        $isCidrDefault = false;
330
331 10
        if (preg_match($this->getIpParsePattern(), $ip, $matches)) {
332 10
            $negation = ($matches[1] !== '') ? $matches[1] : null;
333 10
            $ip = $matches[2];
334 10
            $cidr = isset($matches[4]) ? $matches[4] : null;
335 10
        }
336
337 10
        if ($this->subnet === true && $cidr === null) {
338 3
            return [$this->noSubnet, []];
339
        }
340 10
        if ($this->subnet === false && $cidr !== null) {
341 5
            return [$this->hasSubnet, []];
342
        }
343 10
        if ($this->negation === false && $negation !== null) {
344 3
            return [$this->message, []];
345
        }
346
347 10
        if ($this->getIpVersion($ip) == 6) {
348 6
            if ($cidr !== null) {
349 3
                if ($cidr > static::IPV6_ADDRESS_LENGTH || $cidr < 0) {
350 3
                    return [$this->wrongCidr, []];
351
                }
352 3
            } else {
353 6
                $isCidrDefault = true;
354 6
                $cidr = static::IPV6_ADDRESS_LENGTH;
355
            }
356
357 6
            if (!$this->ipv6) {
358 2
                return [$this->ipv6NotAllowed, []];
359 2
            }
360 6
            if (!$this->validateIPv6($ip)) {
361 2
                return [$this->message, []];
362
            }
363
364 6
            if ($this->expandIPv6) {
365 1
                $ip = $this->expandIPv6($ip);
366 1
            }
367 6
        } else {
368 7
            if ($cidr !== null) {
369 4
                if ($cidr > static::IPV4_ADDRESS_LENGTH || $cidr < 0) {
370 2
                    return [$this->wrongCidr, []];
371
                }
372 4
            } else {
373 7
                $isCidrDefault = true;
374 7
                $cidr = static::IPV4_ADDRESS_LENGTH;
375
            }
376
377 7
            if (!$this->ipv4) {
378 2
                return [$this->ipv4NotAllowed, []];
379
            }
380 7
            if (!$this->validateIPv4($ip)) {
381 3
                return [$this->message, []];
382
            }
383
        }
384
385 9
        if (!$this->isAllowed($ip, $cidr)) {
386 4
            return [$this->notInRange, []];
387
        }
388
389 9
        $result = $negation . $ip;
390
391 9
        if ($this->subnet !== false && (!$isCidrDefault || $isCidrDefault && $this->normalize)) {
392 7
            $result .= "/$cidr";
393 7
        }
394
395 9
        return $result;
396
    }
397
398
    /**
399
     * Expands an IPv6 address to it's full notation. For example `2001:db8::1` will be
400
     * expanded to `2001:0db8:0000:0000:0000:0000:0000:0001`
401
     *
402
     * @param string $ip the original IPv6
403
     * @return string the expanded IPv6
404
     */
405 1
    private function expandIPv6($ip)
406
    {
407 1
        $hex = unpack('H*hex', inet_pton($ip));
408 1
        return substr(preg_replace('/([a-f0-9]{4})/i', '$1:', $hex['hex']), 0, -1);
409
    }
410
411
    /**
412
     * The method checks whether the IP address with specified CIDR is allowed according to the [[ranges]] list.
413
     *
414
     * @param string $ip
415
     * @param integer $cidr
416
     * @return boolean
417
     * @see ranges
418
     */
419 9
    private function isAllowed($ip, $cidr)
420
    {
421 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...
422 5
            return true;
423
        }
424
425 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...
426 4
            list($isNegated, $range) = $this->parseNegatedRange($string);
427 4
            if ($this->inRange($ip, $cidr, $range)) {
428 4
                return !$isNegated;
429
            }
430 4
        }
431
432 3
        return false;
433
    }
434
435
    /**
436
     * Parses IP address/range for the negation with [[NEGATION_CHAR]].
437
     *
438
     * @param $string
439
     * @return array `[0 => boolean, 1 => string]`
440
     *  - boolean: whether the string is negated
441
     *  - string: the string without negation (when the negation were present)
442
     */
443 8
    private function parseNegatedRange ($string) {
444 8
        $isNegated = strpos($string, static::NEGATION_CHAR) === 0;
445 8
        return [$isNegated, ($isNegated ? substr($string, strlen(static::NEGATION_CHAR)) : $string)];
446
    }
447
448
    /**
449
     * Prepares array to fill in [[ranges]]:
450
     *  - Recursively substitutes aliases, described in [[networks]] with their values
451
     *  - Removes duplicates
452
     *
453
     *
454
     * @param $ranges
455
     * @return array
456
     * @see networks
457
     */
458 8
    private function prepareRanges($ranges) {
459 8
        $result = [];
460 8
        foreach ($ranges as $string) {
461 8
            list($isRangeNegated, $range) = $this->parseNegatedRange($string);
462 8
            if (isset($this->networks[$range])) {
463 6
                $replacements = $this->prepareRanges($this->networks[$range]);
464 6
                foreach ($replacements as &$replacement) {
465 6
                    list($isReplacementNegated, $replacement) = $this->parseNegatedRange($replacement);
466 6
                    $result[] = ($isRangeNegated && !$isReplacementNegated ? static::NEGATION_CHAR : '') . $replacement;
467 6
                }
468 6
            } else {
469 8
                $result[] = $string;
470
            }
471 8
        }
472 8
        return array_unique($result);
473
    }
474
475
    /**
476
     * Validates IPv4 address
477
     *
478
     * @param string $value
479
     * @return boolean
480
     */
481 7
    protected function validateIPv4($value)
482
    {
483 7
        return preg_match($this->ipv4Pattern, $value) !== 0;
484
    }
485
486
    /**
487
     * Validates IPv6 address
488
     *
489
     * @param string $value
490
     * @return boolean
491
     */
492 6
    protected function validateIPv6($value)
493
    {
494 6
        return preg_match($this->ipv6Pattern, $value) !== 0;
495
    }
496
497
    /**
498
     * Gets the IP version
499
     *
500
     * @param string $ip
501
     * @return integer
502
     */
503 10
    private function getIpVersion($ip)
504
    {
505 10
        return strpos($ip, ':') === false ? 4 : 6;
506
    }
507
508
    /**
509
     * Used to get the Regexp pattern for initial IP address parsing
510
     * @return string
511
     */
512 10
    private function getIpParsePattern()
513
    {
514 10
        return '/^(' . preg_quote(static::NEGATION_CHAR) . '?)(.+?)(\/(\d+))?$/';
515
    }
516
517
    /**
518
     * Checks whether the IP is in subnet range
519
     *
520
     * @param string $ip an IPv4 or IPv6 address
521
     * @param integer $cidr
522
     * @param string $range subnet in CIDR format e.g. `10.0.0.0/8` or `2001:af::/64`
523
     * @return bool
524
     */
525 4
    private function inRange($ip, $cidr, $range)
526
    {
527 4
        $ipVersion = $this->getIpVersion($ip);
528 4
        $binIp = $this->ip2bin($ip);
529
530 4
        $parts = explode('/', $range);
531 4
        $net = array_shift($parts);
532 4
        $range_cidr = array_shift($parts);
533
534
535 4
        $netVersion = $this->getIpVersion($net);
536 4
        if ($ipVersion !== $netVersion) {
537 2
            return false;
538
        }
539 4
        if ($range_cidr === null) {
540 2
            $range_cidr = $netVersion === 4 ? static::IPV4_ADDRESS_LENGTH : static::IPV6_ADDRESS_LENGTH;
541 2
        }
542
543 4
        $binNet = $this->ip2bin($net);
544 4
        if (substr($binIp, 0, $range_cidr) === substr($binNet, 0, $range_cidr) && $cidr >= $range_cidr) {
545 4
            return true;
546
        }
547 4
        return false;
548
    }
549
550
    /**
551
     * Converts IP address to bits representation
552
     *
553
     * @param string $ip
554
     * @return string bits as a string
555
     */
556 4
    private function ip2bin($ip)
557
    {
558 4
        if ($this->getIpVersion($ip) === 4) {
559 3
            return str_pad(base_convert(ip2long($ip), 10, 2), static::IPV4_ADDRESS_LENGTH, '0', STR_PAD_LEFT);
560
        } else {
561 3
            $unpack = unpack('A16', inet_pton($ip));
562 3
            $binStr = array_shift($unpack);
563 3
            $bytes = static::IPV6_ADDRESS_LENGTH / 8; // 128 bit / 8 = 16 bytes
564 3
            $result = '';
565 3
            while ($bytes-- > 0) {
566 3
                $result = sprintf('%08b', isset($binStr[$bytes]) ? ord($binStr[$bytes]) : '0') . $result;
567 3
            }
568 3
            return $result;
569
        }
570
    }
571
572
    /**
573
     * @inheritdoc
574
     */
575
    public function clientValidateAttribute($model, $attribute, $view)
576
    {
577
        $messages = [
578
            'ipv6NotAllowed' => $this->ipv6NotAllowed,
579
            'ipv4NotAllowed' => $this->ipv4NotAllowed,
580
            'wrongIp' => $this->message,
581
            'noSubnet' => $this->noSubnet,
582
            'hasSubnet' => $this->hasSubnet,
583
        ];
584
        foreach ($messages as &$message) {
585
            $message = Yii::$app->getI18n()->format($message, [
586
                'attribute' => $model->getAttributeLabel($attribute),
587
            ], Yii::$app->language);
588
        }
589
590
        $options = [
591
            'ipv4Pattern' => new JsExpression(Html::escapeJsRegularExpression($this->ipv4Pattern)),
592
            'ipv6Pattern' => new JsExpression(Html::escapeJsRegularExpression($this->ipv6Pattern)),
593
            'messages' => $messages,
594
            'ipv4' => (boolean)$this->ipv4,
595
            'ipv6' => (boolean)$this->ipv6,
596
            'ipParsePattern' => new JsExpression(Html::escapeJsRegularExpression($this->getIpParsePattern())),
597
            'negation' => $this->negation,
598
            'subnet' => $this->subnet
599
        ];
600
        if ($this->skipOnEmpty) {
601
            $options['skipOnEmpty'] = 1;
602
        }
603
604
        ValidationAsset::register($view);
605
606
        return 'yii.validation.ip(value, messages, ' . Json::htmlEncode($options) . ');';
607
    }
608
}
609