Completed
Push — php7-travis-apcu ( 9bbcee...fd63c3 )
by Alexander
14:47
created

ActiveForm   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 416
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 57.5%

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 10
dl 0
loc 416
ccs 46
cts 80
cp 0.575
rs 10
c 0
b 0
f 0

10 Methods

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