Passed
Pull Request — 2.2 (#19879)
by Wilmer
05:27
created

CompareValidator::getClientOptions()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 30
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 20
nc 6
nop 2
dl 0
loc 30
ccs 0
cts 22
cp 0
crap 20
rs 9.6
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link https://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license https://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
15
/**
16
 * CompareValidator compares the specified attribute value with another value.
17
 *
18
 * The value being compared with can be another attribute value
19
 * (specified via [[compareAttribute]]) or a constant (specified via
20
 * [[compareValue]]). When both are specified, the latter takes
21
 * precedence. If neither is specified, the attribute will be compared
22
 * with another attribute whose name is by appending "_repeat" to the source
23
 * attribute name.
24
 *
25
 * CompareValidator supports different comparison operators, specified
26
 * via the [[operator]] property.
27
 *
28
 * The default comparison function is based on string values, which means the values
29
 * are compared byte by byte. When comparing numbers, make sure to set the [[$type]]
30
 * to [[TYPE_NUMBER]] to enable numeric comparison.
31
 *
32
 * @author Qiang Xue <[email protected]>
33
 * @since 2.0
34
 */
35
class CompareValidator extends Validator
36
{
37
    /**
38
     * Constant for specifying the comparison [[type]] by numeric values.
39
     * @since 2.0.11
40
     * @see type
41
     */
42
    const TYPE_STRING = 'string';
43
    /**
44
     * Constant for specifying the comparison [[type]] by numeric values.
45
     * @since 2.0.11
46
     * @see type
47
     */
48
    const TYPE_NUMBER = 'number';
49
50
    /**
51
     * @var string the name of the attribute to be compared with. When both this property
52
     * and [[compareValue]] are set, the latter takes precedence. If neither is set,
53
     * it assumes the comparison is against another attribute whose name is formed by
54
     * appending '_repeat' to the attribute being validated. For example, if 'password' is
55
     * being validated, then the attribute to be compared would be 'password_repeat'.
56
     * @see compareValue
57
     */
58
    public $compareAttribute;
59
    /**
60
     * @var mixed the constant value to be compared with. When both this property
61
     * and [[compareAttribute]] are set, this property takes precedence.
62
     * @see compareAttribute
63
     */
64
    public $compareValue;
65
    /**
66
     * @var string the type of the values being compared. The follow types are supported:
67
     *
68
     * - [[TYPE_STRING|string]]: the values are being compared as strings. No conversion will be done before comparison.
69
     * - [[TYPE_NUMBER|number]]: the values are being compared as numbers. String values will be converted into numbers before comparison.
70
     */
71
    public $type = self::TYPE_STRING;
72
    /**
73
     * @var string the operator for comparison. The following operators are supported:
74
     *
75
     * - `==`: check if two values are equal. The comparison is done is non-strict mode.
76
     * - `===`: check if two values are equal. The comparison is done is strict mode.
77
     * - `!=`: check if two values are NOT equal. The comparison is done is non-strict mode.
78
     * - `!==`: check if two values are NOT equal. The comparison is done is strict mode.
79
     * - `>`: check if value being validated is greater than the value being compared with.
80
     * - `>=`: check if value being validated is greater than or equal to the value being compared with.
81
     * - `<`: check if value being validated is less than the value being compared with.
82
     * - `<=`: check if value being validated is less than or equal to the value being compared with.
83
     *
84
     * When you want to compare numbers, make sure to also set [[type]] to `number`.
85
     */
86
    public $operator = '==';
87
    /**
88
     * @var string the user-defined error message. It may contain the following placeholders which
89
     * will be replaced accordingly by the validator:
90
     *
91
     * - `{attribute}`: the label of the attribute being validated
92
     * - `{value}`: the value of the attribute being validated
93
     * - `{compareValue}`: the value or the attribute label to be compared with
94
     * - `{compareAttribute}`: the label of the attribute to be compared with
95
     * - `{compareValueOrAttribute}`: the value or the attribute label to be compared with
96
     */
97
    public $message;
98
99
100
    /**
101
     * {@inheritdoc}
102
     */
103 7
    public function init()
104
    {
105 7
        parent::init();
106 7
        if ($this->message === null) {
107 7
            switch ($this->operator) {
108 7
                case '==':
109 4
                case '===':
110 7
                    $this->message = Yii::t('yii', '{attribute} must be equal to "{compareValueOrAttribute}".');
111 7
                    break;
112 4
                case '!=':
113 3
                case '!==':
114 4
                    $this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValueOrAttribute}".');
115 4
                    break;
116 3
                case '>':
117 3
                    $this->message = Yii::t('yii', '{attribute} must be greater than "{compareValueOrAttribute}".');
118 3
                    break;
119 3
                case '>=':
120 3
                    $this->message = Yii::t('yii', '{attribute} must be greater than or equal to "{compareValueOrAttribute}".');
121 3
                    break;
122 3
                case '<':
123 3
                    $this->message = Yii::t('yii', '{attribute} must be less than "{compareValueOrAttribute}".');
124 3
                    break;
125 3
                case '<=':
126 3
                    $this->message = Yii::t('yii', '{attribute} must be less than or equal to "{compareValueOrAttribute}".');
127 3
                    break;
128
                default:
129 1
                    throw new InvalidConfigException("Unknown operator: {$this->operator}");
130
            }
131
        }
132
    }
133
134
    /**
135
     * {@inheritdoc}
136
     */
137 4
    public function validateAttribute($model, $attribute)
138
    {
139 4
        $value = $model->$attribute;
140 4
        if (is_array($value)) {
141 1
            $this->addError($model, $attribute, Yii::t('yii', '{attribute} is invalid.'));
142
143 1
            return;
144
        }
145 4
        if ($this->compareValue !== null) {
146 3
            if ($this->compareValue instanceof \Closure) {
147
                $this->compareValue = call_user_func($this->compareValue);
148
            }
149 3
            $compareLabel = $compareValue = $compareValueOrAttribute = $this->compareValue;
150
        } else {
151 3
            $compareAttribute = $this->compareAttribute === null ? $attribute . '_repeat' : $this->compareAttribute;
152 3
            $compareValue = $model->$compareAttribute;
153 3
            $compareLabel = $compareValueOrAttribute = $model->getAttributeLabel($compareAttribute);
154
155 3
            if (!$this->skipOnError && $model->hasErrors($compareAttribute)) {
156 1
                $this->addError(
157 1
                    $model,
158 1
                    $attribute,
159 1
                    Yii::t('yii', '{compareAttribute} is invalid.'),
160 1
                    ['compareAttribute' => $compareLabel]
161 1
                );
162
163 1
                return;
164
            }
165
        }
166
167 4
        if (!$this->compareValues($this->operator, $this->type, $value, $compareValue)) {
168 4
            $this->addError($model, $attribute, $this->message, [
169 4
                'compareAttribute' => $compareLabel,
170 4
                'compareValue' => $compareValue,
171 4
                'compareValueOrAttribute' => $compareValueOrAttribute,
172 4
            ]);
173
        }
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179 2
    protected function validateValue($value)
180
    {
181 2
        if ($this->compareValue === null) {
182 1
            throw new InvalidConfigException('CompareValidator::compareValue must be set.');
183
        }
184 1
        if ($this->compareValue instanceof \Closure) {
185 1
            $this->compareValue = call_user_func($this->compareValue);
186
        }
187 1
        if (!$this->compareValues($this->operator, $this->type, $value, $this->compareValue)) {
188 1
            return [$this->message, [
189 1
                'compareAttribute' => $this->compareValue,
190 1
                'compareValue' => $this->compareValue,
191 1
                'compareValueOrAttribute' => $this->compareValue,
192 1
            ]];
193
        }
194
195 1
        return null;
196
    }
197
198
    /**
199
     * Compares two values with the specified operator.
200
     * @param string $operator the comparison operator
201
     * @param string $type the type of the values being compared
202
     * @param mixed $value the value being compared
203
     * @param mixed $compareValue another value being compared
204
     * @return bool whether the comparison using the specified operator is true.
205
     */
206 5
    protected function compareValues($operator, $type, $value, $compareValue)
207
    {
208 5
        if ($type === self::TYPE_NUMBER) {
209
            $value = (float) $value;
210
            $compareValue = (float) $compareValue;
211
        } else {
212 5
            $value = (string) $value;
213 5
            $compareValue = (string) $compareValue;
214
        }
215
        switch ($operator) {
216 5
            case '==':
217 4
                return $value == $compareValue;
218 5
            case '===':
219 3
                return $value === $compareValue;
220 5
            case '!=':
221 4
                return $value != $compareValue;
222 4
            case '!==':
223 3
                return $value !== $compareValue;
224 4
            case '>':
225 3
                return $value > $compareValue;
226 4
            case '>=':
227 3
                return $value >= $compareValue;
228 4
            case '<':
229 3
                return $value < $compareValue;
230 4
            case '<=':
231 3
                return $value <= $compareValue;
232
            default:
233 1
                return false;
234
        }
235
    }
236
237
    /**
238
     * {@inheritdoc}
239
     */
240
    public function clientValidateAttribute($model, $attribute, $view)
241
    {
242
        if ($this->compareValue != null && $this->compareValue instanceof \Closure) {
243
            $this->compareValue = call_user_func($this->compareValue);
244
        }
245
246
        ValidationAsset::register($view);
247
        $options = $this->getClientOptions($model, $attribute);
248
249
        return 'yii.validation.compare(value, messages, ' . Json::htmlEncode($options) . ', $form);';
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255
    public function getClientOptions($model, $attribute)
256
    {
257
        $options = [
258
            'operator' => $this->operator,
259
            'type' => $this->type,
260
        ];
261
262
        if ($this->compareValue !== null) {
263
            $options['compareValue'] = $this->compareValue;
264
            $compareLabel = $compareValue = $compareValueOrAttribute = $this->compareValue;
265
        } else {
266
            $compareAttribute = $this->compareAttribute === null ? $attribute . '_repeat' : $this->compareAttribute;
267
            $compareValue = $model->getAttributeLabel($compareAttribute);
268
            $options['compareAttribute'] = Html::getInputId($model, $compareAttribute);
269
            $options['compareAttributeName'] = Html::getInputName($model, $compareAttribute);
270
            $compareLabel = $compareValueOrAttribute = $model->getAttributeLabel($compareAttribute);
271
        }
272
273
        if ($this->skipOnEmpty) {
274
            $options['skipOnEmpty'] = 1;
275
        }
276
277
        $options['message'] = $this->formatMessage($this->message, [
278
            'attribute' => $model->getAttributeLabel($attribute),
279
            'compareAttribute' => $compareLabel,
280
            'compareValue' => $compareValue,
281
            'compareValueOrAttribute' => $compareValueOrAttribute,
282
        ]);
283
284
        return $options;
285
    }
286
}
287