Issues (10)

AtLeastValidator.php (4 issues)

1
<?php
2
3
namespace codeonyii\yii2validators;
4
5
use Yii;
6
use yii\base\InvalidConfigException;
7
use yii\i18n\PhpMessageSource;
8
use yii\validators\Validator;
9
10
/**
11
 * Checks if one or more in a list of attributes are filled.
12
 *
13
 * In the following example, the `attr1` and `attr2` attributes will
14
 * be verified. If none of them are filled `attr1` will receive an error:
15
 *
16
 * ~~~[php]
17
 *      // in rules()
18
 *      return [
19
 *          ['attr1', AtLeastValidator::className(), 'in' => ['attr1', 'attr2']],
20
 *      ];
21
 * ~~~
22
 *
23
 * In the following example, the `attr1`, `attr2` and `attr3` attributes will
24
 * be verified. If at least 2 (`min`) of them are not filled, `attr1` will
25
 * receive an error:
26
 *
27
 * ~~~[php]
28
 *      // in rules()
29
 *      return [
30
 *          ['attr1', AtLeastValidator::className(), 'min' => 2, 'in' => ['attr1', 'attr2', 'attr3']],
31
 *      ];
32
 * ~~~
33
 *
34
 * If you want to show errors in a summary instead in the own attributes, you can do this:
35
 * ~~~[php]
36
 *      // in rules()
37
 *      return [
38
 *          ['!id', AtLeastValidator::className(), 'in' => ['attr1', 'attr2', 'attr3']], // where `id` is the pk
39
 *      ];
40
 *
41
 *      // view:
42
 *      ...
43
 *      echo yii\helpers\Html::errorSummary($model, ['class' => ['text-danger']]);
44
 *      // OR, to show only `id` errors:
45
 *      echo yii\helpers\Html::error($model, 'id', ['class' => ['text-danger']]); 
46
 * ~~~
47
 *
48
 *
49
 * @author Sidney Lins <[email protected]>
50
 */
51
class AtLeastValidator extends Validator
52
{
53
    /**
54
     * @var integer the minimun required quantity of attributes that must to be filled.
55
     * Defaults to 1.
56
     */
57
    public $min = 1;
58
59
    /**
60
     * @var string|array the list of attributes that should receive the error message. Required.
61
     */
62
    public $in;
63
64
    /**
65
     * @inheritdoc
66
     */
67
    public $skipOnEmpty = false;
68
69
    /**
70
     * @inheritdoc
71
     */
72
    public $skipOnError = false;
73
74
    /**
75
     * @inheritdoc
76
     */
77
    public function init()
78
    {
79
        parent::init();
80
        if ($this->in === null) {
81
            throw new InvalidConfigException('The `in` parameter is required.');
82
        } elseif (! is_array($this->in) && count(preg_split('/\s*,\s*/', $this->in, -1, PREG_SPLIT_NO_EMPTY)) <= 1) {
0 ignored issues
show
It seems like preg_split('/\s*,\s*/', ...rs\PREG_SPLIT_NO_EMPTY) can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

82
        } elseif (! is_array($this->in) && count(/** @scrutinizer ignore-type */ preg_split('/\s*,\s*/', $this->in, -1, PREG_SPLIT_NO_EMPTY)) <= 1) {
Loading history...
83
            throw new InvalidConfigException('The `in` parameter must have at least 2 attributes.');
84
        }
85
		if (!isset(Yii::$app->get('i18n')->translations['message*'])) {
86
			Yii::$app->get('i18n')->translations['message*'] = [
87
				'class' => PhpMessageSource::className(),
0 ignored issues
show
Deprecated Code introduced by
The function yii\base\BaseObject::className() has been deprecated: since 2.0.14. On PHP >=5.5, use `::class` instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

87
				'class' => /** @scrutinizer ignore-deprecated */ PhpMessageSource::className(),

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
88
				'basePath' => __DIR__ . '/messages',
89
				'sourceLanguage' => 'en-US'
90
			];
91
		}
92
        if ($this->message === null) {
93
            $this->message = Yii::t('messages', 'You must fill at least {min} of the attributes {attributes}.');
94
        }
95
    }
96
97
    /**
98
     * @inheritdoc
99
     */
100
    public function validateAttribute($model, $attribute)
101
    {
102
        $attributes = is_array($this->in) ? $this->in : preg_split('/\s*,\s*/', $this->in, -1, PREG_SPLIT_NO_EMPTY);
103
        $chosen = 0;
104
105
        foreach ($attributes as $attributeName) {
106
            $value = $model->$attributeName;
107
            $attributesListLabels[] = '"' . $model->getAttributeLabel($attributeName). '"';
108
            $chosen += !empty($value) ? 1 : 0;
109
        }
110
111
        if (!$chosen || $chosen < $this->min) {
112
            $attributesList = implode(', ', $attributesListLabels);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $attributesListLabels seems to be defined by a foreach iteration on line 105. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
113
            $message = strtr($this->message, [
114
                '{min}' => $this->min,
115
                '{attributes}' => $attributesList,
116
            ]);
117
            $model->addError($attribute, $message);
118
        }
119
    }
120
121
    /**
122
     * @inheritdoc
123
     * @since: 1.1
124
     */
125
    public function clientValidateAttribute($model, $attribute, $view)
126
    {
127
        $attributes = is_array($this->in) ? $this->in : preg_split('/\s*,\s*/', $this->in, -1, PREG_SPLIT_NO_EMPTY);
128
        $attributes = array_map('strtolower',$attributes); // yii lowercases attributes
0 ignored issues
show
It seems like $attributes can also be of type false; however, parameter $arr1 of array_map() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

128
        $attributes = array_map('strtolower',/** @scrutinizer ignore-type */ $attributes); // yii lowercases attributes
Loading history...
129
        $attributesJson = json_encode($attributes);
130
131
        $attributesLabels = [];
132
        foreach ($attributes as $attr) {
133
            $attributesLabels[] = '"' . $model->getAttributeLabel($attr) . '"';
134
        }
135
        $message = strtr($this->message, [
136
            '{min}' => $this->min,
137
            '{attributes}' => implode(Yii::t('messages', ' or '), $attributesLabels),
138
        ]);
139
140
        $form = $model->formName();
141
142
        return <<<JS
143
            function atLeastValidator() {
144
                var atributes = $attributesJson;
145
                var formName = '$form';
146
                var chosen = 0; 
147
                $.each(atributes, function(key, attr){
148
                    var obj = $('#' + formName.toLowerCase() + '-' + attr);
149
                    if(obj.length == 0){
150
                        obj = $("[name=\""+formName + '[' + attr + ']'+"\"]");
151
                    }
152
153
                    var val = obj.val();
154
                    chosen += val ? 1 : 0;
155
                });
156
                if (!chosen || chosen < $this->min) {
157
                    messages.push('$message');
158
                } else {
159
                    $.each(atributes, function(key, attr){
160
                        var attrId = formName.toLowerCase() + '-' + attr;
161
                        if($('#' + attrId).length == 0){
162
                            attrId = $("[name=\""+formName + '[' + attr + ']'+"\"]").attr('id');
163
                        }
164
165
                        \$form.yiiActiveForm('updateAttribute', attrId, '');
166
                    });
167
                }
168
            }
169
            atLeastValidator();
170
JS;
171
    }
172
}
173
174