Passed
Pull Request — master (#20134)
by Wilmer
14:25 queued 05:46
created

ActiveForm::getClientOptions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 32
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 2.0001

Importance

Changes 0
Metric Value
cc 2
eloc 26
nc 2
nop 0
dl 0
loc 32
ccs 27
cts 28
cp 0.9643
crap 2.0001
rs 9.504
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @link https://www.yiiframework.com/
5
 * @copyright Copyright (c) 2008 Yii Software LLC
6
 * @license https://www.yiiframework.com/license/
7
 */
8
9
namespace yii\widgets;
10
11
use Yii;
12
use yii\base\InvalidCallException;
13
use yii\base\Model;
14
use yii\base\Widget;
15
use yii\helpers\ArrayHelper;
16
use yii\helpers\Html;
17
use yii\helpers\Json;
18
use yii\helpers\Url;
19
20
/**
21
 * ActiveForm is a widget that builds an interactive HTML form for one or multiple data models.
22
 *
23
 * For more details and usage information on ActiveForm, see the [guide article on forms](guide:input-forms).
24
 *
25
 * @author Qiang Xue <[email protected]>
26
 * @since 2.0
27
 */
28
class ActiveForm extends Widget
29
{
30
    /**
31
     * Add validation state class to container tag
32
     * @since 2.0.14
33
     */
34
    const VALIDATION_STATE_ON_CONTAINER = 'container';
35
    /**
36
     * Add validation state class to input tag
37
     * @since 2.0.14
38
     */
39
    const VALIDATION_STATE_ON_INPUT = 'input';
40
41
    /**
42
     * @var array|string the form action URL. This parameter will be processed by [[\yii\helpers\Url::to()]].
43
     * @see method for specifying the HTTP method for this form.
44
     */
45
    public $action = '';
46
    /**
47
     * @var string the form submission method. This should be either `post` or `get`. Defaults to `post`.
48
     *
49
     * When you set this to `get` you may see the url parameters repeated on each request.
50
     * This is because the default value of [[action]] is set to be the current request url and each submit
51
     * will add new parameters instead of replacing existing ones.
52
     * You may set [[action]] explicitly to avoid this:
53
     *
54
     * ```php
55
     * $form = ActiveForm::begin([
56
     *     'method' => 'get',
57
     *     'action' => ['controller/action'],
58
     * ]);
59
     * ```
60
     */
61
    public $method = 'post';
62
    /**
63
     * @var array the HTML attributes (name-value pairs) for the form tag.
64
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
65
     */
66
    public $options = [];
67
    /**
68
     * @var string the default field class name when calling [[field()]] to create a new field.
69
     * @see fieldConfig
70
     */
71
    public $fieldClass = 'yii\widgets\ActiveField';
72
    /**
73
     * @var array|\Closure the default configuration used by [[field()]] when creating a new field object.
74
     * This can be either a configuration array or an anonymous function returning a configuration array.
75
     * If the latter, the signature should be as follows:
76
     *
77
     * ```php
78
     * function ($model, $attribute)
79
     * ```
80
     *
81
     * The value of this property will be merged recursively with the `$options` parameter passed to [[field()]].
82
     *
83
     * @see fieldClass
84
     */
85
    public $fieldConfig = [];
86
    /**
87
     * @var bool whether to perform encoding on the error summary.
88
     */
89
    public $encodeErrorSummary = true;
90
    /**
91
     * @var string the default CSS class for the error summary container.
92
     * @see errorSummary()
93
     */
94
    public $errorSummaryCssClass = 'error-summary';
95
    /**
96
     * @var string the CSS class that is added to a field container when the associated attribute is required.
97
     */
98
    public $requiredCssClass = 'required';
99
    /**
100
     * @var string the CSS class that is added to a field container when the associated attribute has validation error.
101
     */
102
    public $errorCssClass = 'has-error';
103
    /**
104
     * @var string the CSS class that is added to a field container when the associated attribute is successfully validated.
105
     */
106
    public $successCssClass = 'has-success';
107
    /**
108
     * @var string the CSS class that is added to a field container when the associated attribute is being validated.
109
     */
110
    public $validatingCssClass = 'validating';
111
    /**
112
     * @var string where to render validation state class
113
     * Could be either "container" or "input".
114
     * Default is "container".
115
     * @since 2.0.14
116
     */
117
    public $validationStateOn = self::VALIDATION_STATE_ON_CONTAINER;
118
    /**
119
     * @var bool whether to enable client-side data validation.
120
     * If [[ActiveField::enableClientValidation]] is set, its value will take precedence for that input field.
121
     */
122
    public $enableClientValidation = true;
123
    /**
124
     * @var bool whether to enable AJAX-based data validation.
125
     * If [[ActiveField::enableAjaxValidation]] is set, its value will take precedence for that input field.
126
     */
127
    public $enableAjaxValidation = false;
128
    /**
129
     * @var bool whether to hook up `yii.activeForm` JavaScript plugin.
130
     * This property must be set `true` if you want to support client validation and/or AJAX validation, or if you
131
     * want to take advantage of the `yii.activeForm` plugin. When this is `false`, the form will not generate
132
     * any JavaScript.
133
     * @see registerClientScript
134
     */
135
    public $enableClientScript = true;
136
    /**
137
     * @var array|string|null the URL for performing AJAX-based validation. This property will be processed by
138
     * [[Url::to()]]. Please refer to [[Url::to()]] for more details on how to configure this property.
139
     * If this property is not set, it will take the value of the form's action attribute.
140
     */
141
    public $validationUrl;
142
    /**
143
     * @var bool whether to perform validation when the form is submitted.
144
     */
145
    public $validateOnSubmit = true;
146
    /**
147
     * @var bool whether to perform validation when the value of an input field is changed.
148
     * If [[ActiveField::validateOnChange]] is set, its value will take precedence for that input field.
149
     */
150
    public $validateOnChange = true;
151
    /**
152
     * @var bool whether to perform validation when an input field loses focus.
153
     * If [[ActiveField::$validateOnBlur]] is set, its value will take precedence for that input field.
154
     */
155
    public $validateOnBlur = true;
156
    /**
157
     * @var bool whether to perform validation while the user is typing in an input field.
158
     * If [[ActiveField::validateOnType]] is set, its value will take precedence for that input field.
159
     * @see validationDelay
160
     */
161
    public $validateOnType = false;
162
    /**
163
     * @var int number of milliseconds that the validation should be delayed when the user types in the field
164
     * and [[validateOnType]] is set `true`.
165
     * If [[ActiveField::validationDelay]] is set, its value will take precedence for that input field.
166
     */
167
    public $validationDelay = 500;
168
    /**
169
     * @var string the name of the GET parameter indicating the validation request is an AJAX request.
170
     */
171
    public $ajaxParam = 'ajax';
172
    /**
173
     * @var string the type of data that you're expecting back from the server.
174
     */
175
    public $ajaxDataType = 'json';
176
    /**
177
     * @var bool whether to scroll to the first error after validation.
178
     * @since 2.0.6
179
     */
180
    public $scrollToError = true;
181
    /**
182
     * @var int offset in pixels that should be added when scrolling to the first error.
183
     * @since 2.0.11
184
     */
185
    public $scrollToErrorOffset = 0;
186
    /**
187
     * @var array the client validation options for individual attributes. Each element of the array
188
     * represents the validation options for a particular attribute.
189
     * @internal
190
     */
191
    public $attributes = [];
192
193
    /**
194
     * @var ActiveField[] the ActiveField objects that are currently active
195
     */
196
    private $_fields = [];
197
198
199
    /**
200
     * Initializes the widget.
201
     * This renders the form open tag.
202
     */
203 42
    public function init()
204
    {
205 42
        parent::init();
206 42
        if (!isset($this->options['id'])) {
207 42
            $this->options['id'] = $this->getId();
208
        }
209 42
        ob_start();
210 42
        ob_implicit_flush(false);
211
    }
212
213
    /**
214
     * Runs the widget.
215
     * This registers the necessary JavaScript code and renders the form open and close tags.
216
     * @throws InvalidCallException if `beginField()` and `endField()` calls are not matching.
217
     */
218 42
    public function run()
219
    {
220 42
        if (!empty($this->_fields)) {
221
            throw new InvalidCallException('Each beginField() should have a matching endField() call.');
222
        }
223
224 42
        $content = ob_get_clean();
225 42
        $html = Html::beginForm($this->action, $this->method, $this->options);
226 42
        $html .= $content;
227
228 42
        if ($this->enableClientScript) {
229 1
            $this->registerClientScript();
230
        }
231
232 42
        $html .= Html::endForm();
233 42
        return $html;
234
    }
235
236
    /**
237
     * This registers the necessary JavaScript code.
238
     * @since 2.0.12
239
     */
240 1
    public function registerClientScript()
241
    {
242 1
        $id = $this->options['id'];
243 1
        $options = Json::htmlEncode($this->getClientOptions());
244 1
        $attributes = Json::htmlEncode($this->attributes);
245 1
        $view = $this->getView();
246 1
        ActiveFormAsset::register($view);
247 1
        $view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
248
    }
249
250
    /**
251
     * Returns the options for the form JS widget.
252
     * @return array the options.
253
     */
254 1
    protected function getClientOptions()
255
    {
256 1
        $options = [
257 1
            'encodeErrorSummary' => $this->encodeErrorSummary,
258 1
            'errorSummary' => '.' . implode('.', preg_split('/\s+/', $this->errorSummaryCssClass, -1, PREG_SPLIT_NO_EMPTY)),
259 1
            'validateOnSubmit' => $this->validateOnSubmit,
260 1
            'errorCssClass' => $this->errorCssClass,
261 1
            'successCssClass' => $this->successCssClass,
262 1
            'validatingCssClass' => $this->validatingCssClass,
263 1
            'ajaxParam' => $this->ajaxParam,
264 1
            'ajaxDataType' => $this->ajaxDataType,
265 1
            'scrollToError' => $this->scrollToError,
266 1
            'scrollToErrorOffset' => $this->scrollToErrorOffset,
267 1
            'validationStateOn' => $this->validationStateOn,
268 1
        ];
269 1
        if ($this->validationUrl !== null) {
270
            $options['validationUrl'] = Url::to($this->validationUrl);
271
        }
272
273
        // only get the options that are different from the default ones (set in yii.activeForm.js)
274 1
        return array_diff_assoc($options, [
275 1
            'encodeErrorSummary' => true,
276 1
            'errorSummary' => '.error-summary',
277 1
            'validateOnSubmit' => true,
278 1
            'errorCssClass' => 'has-error',
279 1
            'successCssClass' => 'has-success',
280 1
            'validatingCssClass' => 'validating',
281 1
            'ajaxParam' => 'ajax',
282 1
            'ajaxDataType' => 'json',
283 1
            'scrollToError' => true,
284 1
            'scrollToErrorOffset' => 0,
285 1
            'validationStateOn' => self::VALIDATION_STATE_ON_CONTAINER,
286 1
        ]);
287
    }
288
289
    /**
290
     * Generates a summary of the validation errors.
291
     * If there is no validation error, an empty error summary markup will still be generated, but it will be hidden.
292
     * @param Model|Model[] $models the model(s) associated with this form.
293
     * @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
294
     *
295
     * - `header`: string, the header HTML for the error summary. If not set, a default prompt string will be used.
296
     * - `footer`: string, the footer HTML for the error summary.
297
     *
298
     * The rest of the options will be rendered as the attributes of the container tag. The values will
299
     * be HTML-encoded using [[\yii\helpers\Html::encode()]]. If a value is `null`, the corresponding attribute will not be rendered.
300
     * @return string the generated error summary.
301
     * @see errorSummaryCssClass
302
     */
303
    public function errorSummary($models, $options = [])
304
    {
305
        Html::addCssClass($options, $this->errorSummaryCssClass);
306
        $options['encode'] = $this->encodeErrorSummary;
307
        return Html::errorSummary($models, $options);
308
    }
309
310
    /**
311
     * Generates a form field.
312
     * A form field is associated with a model and an attribute. It contains a label, an input and an error message
313
     * and use them to interact with end users to collect their inputs for the attribute.
314
     * @param Model $model the data model.
315
     * @param string $attribute the attribute name or expression. See [[Html::getAttributeName()]] for the format
316
     * about attribute expression.
317
     * @param array $options the additional configurations for the field object. These are properties of [[ActiveField]]
318
     * or a subclass, depending on the value of [[fieldClass]].
319
     * @return ActiveField the created ActiveField object.
320
     * @see fieldConfig
321
     */
322 5
    public function field($model, $attribute, $options = [])
323
    {
324 5
        $config = $this->fieldConfig;
325 5
        if ($config instanceof \Closure) {
326
            $config = call_user_func($config, $model, $attribute);
327
        }
328 5
        if (!isset($config['class'])) {
329 5
            $config['class'] = $this->fieldClass;
330
        }
331
332 5
        return Yii::createObject(ArrayHelper::merge($config, $options, [
333 5
            'model' => $model,
334 5
            'attribute' => $attribute,
335 5
            'form' => $this,
336 5
        ]));
337
    }
338
339
    /**
340
     * Begins a form field.
341
     * This method will create a new form field and returns its opening tag.
342
     * You should call [[endField()]] afterwards.
343
     * @param Model $model the data model.
344
     * @param string $attribute the attribute name or expression. See [[Html::getAttributeName()]] for the format
345
     * about attribute expression.
346
     * @param array $options the additional configurations for the field object.
347
     * @return string the opening tag.
348
     * @see endField()
349
     * @see field()
350
     */
351
    public function beginField($model, $attribute, $options = [])
352
    {
353
        $field = $this->field($model, $attribute, $options);
354
        $this->_fields[] = $field;
355
        return $field->begin();
356
    }
357
358
    /**
359
     * Ends a form field.
360
     * This method will return the closing tag of an active form field started by [[beginField()]].
361
     * @return string the closing tag of the form field.
362
     * @throws InvalidCallException if this method is called without a prior [[beginField()]] call.
363
     */
364
    public function endField()
365
    {
366
        $field = array_pop($this->_fields);
367
        if ($field instanceof ActiveField) {
368
            return $field->end();
369
        }
370
371
        throw new InvalidCallException('Mismatching endField() call.');
372
    }
373
374
    /**
375
     * Validates one or several models and returns an error message array indexed by the attribute IDs.
376
     * This is a helper method that simplifies the way of writing AJAX validation code.
377
     *
378
     * For example, you may use the following code in a controller action to respond
379
     * to an AJAX validation request:
380
     *
381
     * ```php
382
     * $model = new Post;
383
     * $model->load(Yii::$app->request->post());
384
     * if (Yii::$app->request->isAjax) {
385
     *     Yii::$app->response->format = Response::FORMAT_JSON;
386
     *     return ActiveForm::validate($model);
387
     * }
388
     * // ... respond to non-AJAX request ...
389
     * ```
390
     *
391
     * To validate multiple models, simply pass each model as a parameter to this method, like
392
     * the following:
393
     *
394
     * ```php
395
     * ActiveForm::validate($model1, $model2, ...);
396
     * ```
397
     *
398
     * @param Model $model the model to be validated.
399
     * @param mixed $attributes list of attributes that should be validated.
400
     * If this parameter is empty, it means any attribute listed in the applicable
401
     * validation rules should be validated.
402
     *
403
     * When this method is used to validate multiple models, this parameter will be interpreted
404
     * as a model.
405
     *
406
     * @return array the error message array indexed by the attribute IDs.
407
     */
408
    public static function validate($model, $attributes = null)
409
    {
410
        $result = [];
411
        if ($attributes instanceof Model) {
412
            // validating multiple models
413
            $models = func_get_args();
414
            $attributes = null;
415
        } else {
416
            $models = [$model];
417
        }
418
        /* @var $model Model */
419
        foreach ($models as $model) {
0 ignored issues
show
introduced by
$model is overwriting one of the parameters of this function.
Loading history...
420
            $model->validate($attributes);
421
            foreach ($model->getErrors() as $attribute => $errors) {
422
                $result[Html::getInputId($model, $attribute)] = $errors;
423
            }
424
        }
425
426
        return $result;
427
    }
428
429
    /**
430
     * Validates an array of model instances and returns an error message array indexed by the attribute IDs.
431
     * This is a helper method that simplifies the way of writing AJAX validation code for tabular input.
432
     *
433
     * For example, you may use the following code in a controller action to respond
434
     * to an AJAX validation request:
435
     *
436
     * ```php
437
     * // ... load $models ...
438
     * if (Yii::$app->request->isAjax) {
439
     *     Yii::$app->response->format = Response::FORMAT_JSON;
440
     *     return ActiveForm::validateMultiple($models);
441
     * }
442
     * // ... respond to non-AJAX request ...
443
     * ```
444
     *
445
     * @param array $models an array of models to be validated.
446
     * @param mixed $attributes list of attributes that should be validated.
447
     * If this parameter is empty, it means any attribute listed in the applicable
448
     * validation rules should be validated.
449
     * @return array the error message array indexed by the attribute IDs.
450
     */
451
    public static function validateMultiple($models, $attributes = null)
452
    {
453
        $result = [];
454
        /* @var $model Model */
455
        foreach ($models as $i => $model) {
456
            $model->validate($attributes);
457
            foreach ($model->getErrors() as $attribute => $errors) {
458
                $result[Html::getInputId($model, "[$i]" . $attribute)] = $errors;
459
            }
460
        }
461
462
        return $result;
463
    }
464
}
465