This commit is contained in:
2020-10-06 14:27:47 +07:00
commit 586be80cf6
16613 changed files with 3274099 additions and 0 deletions

View File

@@ -0,0 +1,940 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Yii;
use yii\base\Component;
use yii\base\ErrorHandler;
use yii\base\Model;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\web\JsExpression;
/**
* ActiveField represents a form input field within an [[ActiveForm]].
*
* For more details and usage information on ActiveField, see the [guide article on forms](guide:input-forms).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActiveField extends Component
{
/**
* @var ActiveForm the form that this field is associated with.
*/
public $form;
/**
* @var Model the data model that this field is associated with.
*/
public $model;
/**
* @var string the model attribute that this field is associated with.
*/
public $attribute;
/**
* @var array the HTML attributes (name-value pairs) for the field container tag.
* The values will be HTML-encoded using [[Html::encode()]].
* If a value is `null`, the corresponding attribute will not be rendered.
* The following special options are recognized:
*
* - `tag`: the tag name of the container element. Defaults to `div`. Setting it to `false` will not render a container tag.
* See also [[\yii\helpers\Html::tag()]].
*
* If you set a custom `id` for the container element, you may need to adjust the [[$selectors]] accordingly.
*
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = ['class' => 'form-group'];
/**
* @var string the template that is used to arrange the label, the input field, the error message and the hint text.
* The following tokens will be replaced when [[render()]] is called: `{label}`, `{input}`, `{error}` and `{hint}`.
*/
public $template = "{label}\n{input}\n{hint}\n{error}";
/**
* @var array the default options for the input tags. The parameter passed to individual input methods
* (e.g. [[textInput()]]) will be merged with this property when rendering the input tag.
*
* If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
*
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $inputOptions = ['class' => 'form-control'];
/**
* @var array the default options for the error tags. The parameter passed to [[error()]] will be
* merged with this property when rendering the error tag.
* The following special options are recognized:
*
* - `tag`: the tag name of the container element. Defaults to `div`. Setting it to `false` will not render a container tag.
* See also [[\yii\helpers\Html::tag()]].
* - `encode`: whether to encode the error output. Defaults to `true`.
*
* If you set a custom `id` for the error element, you may need to adjust the [[$selectors]] accordingly.
*
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $errorOptions = ['class' => 'help-block'];
/**
* @var array the default options for the label tags. The parameter passed to [[label()]] will be
* merged with this property when rendering the label tag.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $labelOptions = ['class' => 'control-label'];
/**
* @var array the default options for the hint tags. The parameter passed to [[hint()]] will be
* merged with this property when rendering the hint tag.
* The following special options are recognized:
*
* - `tag`: the tag name of the container element. Defaults to `div`. Setting it to `false` will not render a container tag.
* See also [[\yii\helpers\Html::tag()]].
*
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $hintOptions = ['class' => 'hint-block'];
/**
* @var bool whether to enable client-side data validation.
* If not set, it will take the value of [[ActiveForm::enableClientValidation]].
*/
public $enableClientValidation;
/**
* @var bool whether to enable AJAX-based data validation.
* If not set, it will take the value of [[ActiveForm::enableAjaxValidation]].
*/
public $enableAjaxValidation;
/**
* @var bool whether to perform validation when the value of the input field is changed.
* If not set, it will take the value of [[ActiveForm::validateOnChange]].
*/
public $validateOnChange;
/**
* @var bool whether to perform validation when the input field loses focus.
* If not set, it will take the value of [[ActiveForm::validateOnBlur]].
*/
public $validateOnBlur;
/**
* @var bool whether to perform validation while the user is typing in the input field.
* If not set, it will take the value of [[ActiveForm::validateOnType]].
* @see validationDelay
*/
public $validateOnType;
/**
* @var int number of milliseconds that the validation should be delayed when the user types in the field
* and [[validateOnType]] is set `true`.
* If not set, it will take the value of [[ActiveForm::validationDelay]].
*/
public $validationDelay;
/**
* @var array the jQuery selectors for selecting the container, input and error tags.
* The array keys should be `container`, `input`, and/or `error`, and the array values
* are the corresponding selectors. For example, `['input' => '#my-input']`.
*
* The container selector is used under the context of the form, while the input and the error
* selectors are used under the context of the container.
*
* You normally do not need to set this property as the default selectors should work well for most cases.
*/
public $selectors = [];
/**
* @var array different parts of the field (e.g. input, label). This will be used together with
* [[template]] to generate the final field HTML code. The keys are the token names in [[template]],
* while the values are the corresponding HTML code. Valid tokens include `{input}`, `{label}` and `{error}`.
* Note that you normally don't need to access this property directly as
* it is maintained by various methods of this class.
*/
public $parts = [];
/**
* @var bool adds aria HTML attributes `aria-required` and `aria-invalid` for inputs
* @since 2.0.11
*/
public $addAriaAttributes = true;
/**
* @var string this property holds a custom input id if it was set using [[inputOptions]] or in one of the
* `$options` parameters of the `input*` methods.
*/
private $_inputId;
/**
* @var bool if "for" field label attribute should be skipped.
*/
private $_skipLabelFor = false;
/**
* PHP magic method that returns the string representation of this object.
* @return string the string representation of this object.
*/
public function __toString()
{
// __toString cannot throw exception
// use trigger_error to bypass this limitation
try {
return $this->render();
} catch (\Exception $e) {
ErrorHandler::convertExceptionToError($e);
return '';
}
}
/**
* Renders the whole field.
* This method will generate the label, error tag, input tag and hint tag (if any), and
* assemble them into HTML according to [[template]].
* @param string|callable $content the content within the field container.
* If `null` (not set), the default methods will be called to generate the label, error tag and input tag,
* and use them as the content.
* If a callable, it will be called to generate the content. The signature of the callable should be:
*
* ```php
* function ($field) {
* return $html;
* }
* ```
*
* @return string the rendering result.
*/
public function render($content = null)
{
if ($content === null) {
if (!isset($this->parts['{input}'])) {
$this->textInput();
}
if (!isset($this->parts['{label}'])) {
$this->label();
}
if (!isset($this->parts['{error}'])) {
$this->error();
}
if (!isset($this->parts['{hint}'])) {
$this->hint(null);
}
$content = strtr($this->template, $this->parts);
} elseif (!is_string($content)) {
$content = call_user_func($content, $this);
}
return $this->begin() . "\n" . $content . "\n" . $this->end();
}
/**
* Renders the opening tag of the field container.
* @return string the rendering result.
*/
public function begin()
{
if ($this->form->enableClientScript) {
$clientOptions = $this->getClientOptions();
if (!empty($clientOptions)) {
$this->form->attributes[] = $clientOptions;
}
}
$inputID = $this->getInputId();
$attribute = Html::getAttributeName($this->attribute);
$options = $this->options;
$class = isset($options['class']) ? (array) $options['class'] : [];
$class[] = "field-$inputID";
if ($this->model->isAttributeRequired($attribute)) {
$class[] = $this->form->requiredCssClass;
}
$options['class'] = implode(' ', $class);
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_CONTAINER) {
$this->addErrorClassIfNeeded($options);
}
$tag = ArrayHelper::remove($options, 'tag', 'div');
return Html::beginTag($tag, $options);
}
/**
* Renders the closing tag of the field container.
* @return string the rendering result.
*/
public function end()
{
return Html::endTag(ArrayHelper::keyExists('tag', $this->options) ? $this->options['tag'] : 'div');
}
/**
* Generates a label tag for [[attribute]].
* @param null|string|false $label the label to use. If `null`, the label will be generated via [[Model::getAttributeLabel()]].
* If `false`, the generated field will not contain the label part.
* Note that this will NOT be [[Html::encode()|encoded]].
* @param null|array $options the tag options in terms of name-value pairs. It will be merged with [[labelOptions]].
* The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded
* using [[Html::encode()]]. If a value is `null`, the corresponding attribute will not be rendered.
* @return $this the field object itself.
*/
public function label($label = null, $options = [])
{
if ($label === false) {
$this->parts['{label}'] = '';
return $this;
}
$options = array_merge($this->labelOptions, $options);
if ($label !== null) {
$options['label'] = $label;
}
if ($this->_skipLabelFor) {
$options['for'] = null;
}
$this->parts['{label}'] = Html::activeLabel($this->model, $this->attribute, $options);
return $this;
}
/**
* Generates a tag that contains the first validation error of [[attribute]].
* Note that even if there is no validation error, this method will still return an empty error tag.
* @param array|false $options the tag options in terms of name-value pairs. It will be merged with [[errorOptions]].
* The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded
* using [[Html::encode()]]. If this parameter is `false`, no error tag will be rendered.
*
* The following options are specially handled:
*
* - `tag`: this specifies the tag name. If not set, `div` will be used.
* See also [[\yii\helpers\Html::tag()]].
*
* If you set a custom `id` for the error element, you may need to adjust the [[$selectors]] accordingly.
* @see $errorOptions
* @return $this the field object itself.
*/
public function error($options = [])
{
if ($options === false) {
$this->parts['{error}'] = '';
return $this;
}
$options = array_merge($this->errorOptions, $options);
$this->parts['{error}'] = Html::error($this->model, $this->attribute, $options);
return $this;
}
/**
* Renders the hint tag.
* @param string|bool $content the hint content.
* If `null`, the hint will be generated via [[Model::getAttributeHint()]].
* If `false`, the generated field will not contain the hint part.
* Note that this will NOT be [[Html::encode()|encoded]].
* @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the hint tag. The values will be HTML-encoded using [[Html::encode()]].
*
* The following options are specially handled:
*
* - `tag`: this specifies the tag name. If not set, `div` will be used.
* See also [[\yii\helpers\Html::tag()]].
*
* @return $this the field object itself.
*/
public function hint($content, $options = [])
{
if ($content === false) {
$this->parts['{hint}'] = '';
return $this;
}
$options = array_merge($this->hintOptions, $options);
if ($content !== null) {
$options['hint'] = $content;
}
$this->parts['{hint}'] = Html::activeHint($this->model, $this->attribute, $options);
return $this;
}
/**
* Renders an input tag.
* @param string $type the input type (e.g. `text`, `password`)
* @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
*
* If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
*
* @return $this the field object itself.
*/
public function input($type, $options = [])
{
$options = array_merge($this->inputOptions, $options);
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeInput($type, $this->model, $this->attribute, $options);
return $this;
}
/**
* Renders a text input.
* This method will generate the `name` and `value` tag attributes automatically for the model attribute
* unless they are explicitly specified in `$options`.
* @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
*
* The following special options are recognized:
*
* - `maxlength`: int|bool, when `maxlength` is set `true` and the model attribute is validated
* by a string validator, the `maxlength` option will take the value of [[\yii\validators\StringValidator::max]].
* This is available since version 2.0.3.
*
* Note that if you set a custom `id` for the input element, you may need to adjust the value of [[selectors]] accordingly.
*
* @return $this the field object itself.
*/
public function textInput($options = [])
{
$options = array_merge($this->inputOptions, $options);
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeTextInput($this->model, $this->attribute, $options);
return $this;
}
/**
* Renders a hidden input.
*
* Note that this method is provided for completeness. In most cases because you do not need
* to validate a hidden input, you should not need to use this method. Instead, you should
* use [[\yii\helpers\Html::activeHiddenInput()]].
*
* This method will generate the `name` and `value` tag attributes automatically for the model attribute
* unless they are explicitly specified in `$options`.
* @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
*
* If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
*
* @return $this the field object itself.
*/
public function hiddenInput($options = [])
{
$options = array_merge($this->inputOptions, $options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeHiddenInput($this->model, $this->attribute, $options);
return $this;
}
/**
* Renders a password input.
* This method will generate the `name` and `value` tag attributes automatically for the model attribute
* unless they are explicitly specified in `$options`.
* @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
*
* If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
*
* @return $this the field object itself.
*/
public function passwordInput($options = [])
{
$options = array_merge($this->inputOptions, $options);
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activePasswordInput($this->model, $this->attribute, $options);
return $this;
}
/**
* Renders a file input.
* This method will generate the `name` and `value` tag attributes automatically for the model attribute
* unless they are explicitly specified in `$options`.
* @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
*
* If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
*
* @return $this the field object itself.
*/
public function fileInput($options = [])
{
// https://github.com/yiisoft/yii2/pull/795
if ($this->inputOptions !== ['class' => 'form-control']) {
$options = array_merge($this->inputOptions, $options);
}
// https://github.com/yiisoft/yii2/issues/8779
if (!isset($this->form->options['enctype'])) {
$this->form->options['enctype'] = 'multipart/form-data';
}
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeFileInput($this->model, $this->attribute, $options);
return $this;
}
/**
* Renders a text area.
* The model attribute value will be used as the content in the textarea.
* @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]].
*
* If you set a custom `id` for the textarea element, you may need to adjust the [[$selectors]] accordingly.
*
* @return $this the field object itself.
*/
public function textarea($options = [])
{
$options = array_merge($this->inputOptions, $options);
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeTextarea($this->model, $this->attribute, $options);
return $this;
}
/**
* Renders a radio button.
* This method will generate the `checked` tag attribute according to the model attribute value.
* @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
*
* - `uncheck`: string, the value associated with the uncheck state of the radio button. If not set,
* it will take the default value `0`. This method will render a hidden input so that if the radio button
* is not checked and is submitted, the value of this attribute will still be submitted to the server
* via the hidden input. If you do not want any hidden input, you should explicitly set this option as `null`.
* - `label`: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass
* in HTML code such as an image tag. If this is coming from end users, you should [[Html::encode()|encode]] it to prevent XSS attacks.
* When this option is specified, the radio button will be enclosed by a label tag. If you do not want any label, you should
* explicitly set this option as `null`.
* - `labelOptions`: array, the HTML attributes for the label tag. This is only used when the `label` option is specified.
*
* The rest of the options will be rendered as the attributes of the resulting tag. The values will
* be HTML-encoded using [[Html::encode()]]. If a value is `null`, the corresponding attribute will not be rendered.
*
* If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
*
* @param bool $enclosedByLabel whether to enclose the radio within the label.
* If `true`, the method will still use [[template]] to layout the radio button and the error message
* except that the radio is enclosed by the label tag.
* @return $this the field object itself.
*/
public function radio($options = [], $enclosedByLabel = true)
{
if ($enclosedByLabel) {
$this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
$this->parts['{label}'] = '';
} else {
if (isset($options['label']) && !isset($this->parts['{label}'])) {
$this->parts['{label}'] = $options['label'];
if (!empty($options['labelOptions'])) {
$this->labelOptions = $options['labelOptions'];
}
}
unset($options['labelOptions']);
$options['label'] = null;
$this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options);
}
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
return $this;
}
/**
* Renders a checkbox.
* This method will generate the `checked` tag attribute according to the model attribute value.
* @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
*
* - `uncheck`: string, the value associated with the uncheck state of the radio button. If not set,
* it will take the default value `0`. This method will render a hidden input so that if the radio button
* is not checked and is submitted, the value of this attribute will still be submitted to the server
* via the hidden input. If you do not want any hidden input, you should explicitly set this option as `null`.
* - `label`: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass
* in HTML code such as an image tag. If this is coming from end users, you should [[Html::encode()|encode]] it to prevent XSS attacks.
* When this option is specified, the checkbox will be enclosed by a label tag. If you do not want any label, you should
* explicitly set this option as `null`.
* - `labelOptions`: array, the HTML attributes for the label tag. This is only used when the `label` option is specified.
*
* The rest of the options will be rendered as the attributes of the resulting tag. The values will
* be HTML-encoded using [[Html::encode()]]. If a value is `null`, the corresponding attribute will not be rendered.
*
* If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
*
* @param bool $enclosedByLabel whether to enclose the checkbox within the label.
* If `true`, the method will still use [[template]] to layout the checkbox and the error message
* except that the checkbox is enclosed by the label tag.
* @return $this the field object itself.
*/
public function checkbox($options = [], $enclosedByLabel = true)
{
if ($enclosedByLabel) {
$this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
$this->parts['{label}'] = '';
} else {
if (isset($options['label']) && !isset($this->parts['{label}'])) {
$this->parts['{label}'] = $options['label'];
if (!empty($options['labelOptions'])) {
$this->labelOptions = $options['labelOptions'];
}
}
unset($options['labelOptions']);
$options['label'] = null;
$this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options);
}
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
return $this;
}
/**
* Renders a drop-down list.
* The selection of the drop-down list is taken from the value of the model attribute.
* @param array $items the option data items. The array keys are option values, and the array values
* are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
* For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
* If you have a list of data models, you may convert them into the format described above using
* [[ArrayHelper::map()]].
*
* Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
* the labels will also be HTML-encoded.
* @param array $options the tag options in terms of name-value pairs.
*
* For the list of available options please refer to the `$options` parameter of [[\yii\helpers\Html::activeDropDownList()]].
*
* If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
*
* @return $this the field object itself.
*/
public function dropDownList($items, $options = [])
{
$options = array_merge($this->inputOptions, $options);
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeDropDownList($this->model, $this->attribute, $items, $options);
return $this;
}
/**
* Renders a list box.
* The selection of the list box is taken from the value of the model attribute.
* @param array $items the option data items. The array keys are option values, and the array values
* are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
* For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
* If you have a list of data models, you may convert them into the format described above using
* [[\yii\helpers\ArrayHelper::map()]].
*
* Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
* the labels will also be HTML-encoded.
* @param array $options the tag options in terms of name-value pairs.
*
* For the list of available options please refer to the `$options` parameter of [[\yii\helpers\Html::activeListBox()]].
*
* If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
*
* @return $this the field object itself.
*/
public function listBox($items, $options = [])
{
$options = array_merge($this->inputOptions, $options);
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->parts['{input}'] = Html::activeListBox($this->model, $this->attribute, $items, $options);
return $this;
}
/**
* Renders a list of checkboxes.
* A checkbox list allows multiple selection, like [[listBox()]].
* As a result, the corresponding submitted value is an array.
* The selection of the checkbox list is taken from the value of the model attribute.
* @param array $items the data item used to generate the checkboxes.
* The array values are the labels, while the array keys are the corresponding checkbox values.
* @param array $options options (name => config) for the checkbox list.
* For the list of available options please refer to the `$options` parameter of [[\yii\helpers\Html::activeCheckboxList()]].
* @return $this the field object itself.
*/
public function checkboxList($items, $options = [])
{
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->_skipLabelFor = true;
$this->parts['{input}'] = Html::activeCheckboxList($this->model, $this->attribute, $items, $options);
return $this;
}
/**
* Renders a list of radio buttons.
* A radio button list is like a checkbox list, except that it only allows single selection.
* The selection of the radio buttons is taken from the value of the model attribute.
* @param array $items the data item used to generate the radio buttons.
* The array values are the labels, while the array keys are the corresponding radio values.
* @param array $options options (name => config) for the radio button list.
* For the list of available options please refer to the `$options` parameter of [[\yii\helpers\Html::activeRadioList()]].
* @return $this the field object itself.
*/
public function radioList($items, $options = [])
{
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($options);
}
$this->addAriaAttributes($options);
$this->adjustLabelFor($options);
$this->_skipLabelFor = true;
$this->parts['{input}'] = Html::activeRadioList($this->model, $this->attribute, $items, $options);
return $this;
}
/**
* Renders a widget as the input of the field.
*
* Note that the widget must have both `model` and `attribute` properties. They will
* be initialized with [[model]] and [[attribute]] of this field, respectively.
*
* If you want to use a widget that does not have `model` and `attribute` properties,
* please use [[render()]] instead.
*
* For example to use the [[MaskedInput]] widget to get some date input, you can use
* the following code, assuming that `$form` is your [[ActiveForm]] instance:
*
* ```php
* $form->field($model, 'date')->widget(\yii\widgets\MaskedInput::className(), [
* 'mask' => '99/99/9999',
* ]);
* ```
*
* If you set a custom `id` for the input element, you may need to adjust the [[$selectors]] accordingly.
*
* @param string $class the widget class name.
* @param array $config name-value pairs that will be used to initialize the widget.
* @return $this the field object itself.
*/
public function widget($class, $config = [])
{
/* @var $class \yii\base\Widget */
$config['model'] = $this->model;
$config['attribute'] = $this->attribute;
$config['view'] = $this->form->getView();
if (is_subclass_of($class, 'yii\widgets\InputWidget')) {
$config['field'] = $this;
if (isset($config['options'])) {
if ($this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_INPUT) {
$this->addErrorClassIfNeeded($config['options']);
}
$this->addAriaAttributes($config['options']);
$this->adjustLabelFor($config['options']);
}
}
$this->parts['{input}'] = $class::widget($config);
return $this;
}
/**
* Adjusts the `for` attribute for the label based on the input options.
* @param array $options the input options.
*/
protected function adjustLabelFor($options)
{
if (!isset($options['id'])) {
return;
}
$this->_inputId = $options['id'];
if (!isset($this->labelOptions['for'])) {
$this->labelOptions['for'] = $options['id'];
}
}
/**
* Returns the JS options for the field.
* @return array the JS options.
*/
protected function getClientOptions()
{
$attribute = Html::getAttributeName($this->attribute);
if (!in_array($attribute, $this->model->activeAttributes(), true)) {
return [];
}
$clientValidation = $this->isClientValidationEnabled();
$ajaxValidation = $this->isAjaxValidationEnabled();
if ($clientValidation) {
$validators = [];
foreach ($this->model->getActiveValidators($attribute) as $validator) {
/* @var $validator \yii\validators\Validator */
$js = $validator->clientValidateAttribute($this->model, $attribute, $this->form->getView());
if ($validator->enableClientValidation && $js != '') {
if ($validator->whenClient !== null) {
$js = "if (({$validator->whenClient})(attribute, value)) { $js }";
}
$validators[] = $js;
}
}
}
if (!$ajaxValidation && (!$clientValidation || empty($validators))) {
return [];
}
$options = [];
$inputID = $this->getInputId();
$options['id'] = Html::getInputId($this->model, $this->attribute);
$options['name'] = $this->attribute;
$options['container'] = isset($this->selectors['container']) ? $this->selectors['container'] : ".field-$inputID";
$options['input'] = isset($this->selectors['input']) ? $this->selectors['input'] : "#$inputID";
if (isset($this->selectors['error'])) {
$options['error'] = $this->selectors['error'];
} elseif (isset($this->errorOptions['class'])) {
$options['error'] = '.' . implode('.', preg_split('/\s+/', $this->errorOptions['class'], -1, PREG_SPLIT_NO_EMPTY));
} else {
$options['error'] = isset($this->errorOptions['tag']) ? $this->errorOptions['tag'] : 'span';
}
$options['encodeError'] = !isset($this->errorOptions['encode']) || $this->errorOptions['encode'];
if ($ajaxValidation) {
$options['enableAjaxValidation'] = true;
}
foreach (['validateOnChange', 'validateOnBlur', 'validateOnType', 'validationDelay'] as $name) {
$options[$name] = $this->$name === null ? $this->form->$name : $this->$name;
}
if (!empty($validators)) {
$options['validate'] = new JsExpression('function (attribute, value, messages, deferred, $form) {' . implode('', $validators) . '}');
}
if ($this->addAriaAttributes === false) {
$options['updateAriaInvalid'] = false;
}
// only get the options that are different from the default ones (set in yii.activeForm.js)
return array_diff_assoc($options, [
'validateOnChange' => true,
'validateOnBlur' => true,
'validateOnType' => false,
'validationDelay' => 500,
'encodeError' => true,
'error' => '.help-block',
'updateAriaInvalid' => true,
]);
}
/**
* Checks if client validation enabled for the field.
* @return bool
* @since 2.0.11
*/
protected function isClientValidationEnabled()
{
return $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation;
}
/**
* Checks if ajax validation enabled for the field.
* @return bool
* @since 2.0.11
*/
protected function isAjaxValidationEnabled()
{
return $this->enableAjaxValidation || $this->enableAjaxValidation === null && $this->form->enableAjaxValidation;
}
/**
* Returns the HTML `id` of the input element of this form field.
* @return string the input id.
* @since 2.0.7
*/
protected function getInputId()
{
return $this->_inputId ?: Html::getInputId($this->model, $this->attribute);
}
/**
* Adds aria attributes to the input options.
* @param $options array input options
* @since 2.0.11
*/
protected function addAriaAttributes(&$options)
{
if ($this->addAriaAttributes) {
if (!isset($options['aria-required']) && $this->model->isAttributeRequired($this->attribute)) {
$options['aria-required'] = 'true';
}
if (!isset($options['aria-invalid'])) {
if ($this->model->hasErrors($this->attribute)) {
$options['aria-invalid'] = 'true';
}
}
}
}
/**
* Adds validation class to the input options if needed.
* @param $options array input options
* @since 2.0.14
*/
protected function addErrorClassIfNeeded(&$options)
{
// Get proper attribute name when attribute name is tabular.
$attributeName = Html::getAttributeName($this->attribute);
if ($this->model->hasErrors($attributeName)) {
Html::addCssClass($options, $this->form->errorCssClass);
}
}
}

View File

@@ -0,0 +1,462 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Yii;
use yii\base\InvalidCallException;
use yii\base\Model;
use yii\base\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\helpers\Url;
/**
* ActiveForm is a widget that builds an interactive HTML form for one or multiple data models.
*
* For more details and usage information on ActiveForm, see the [guide article on forms](guide:input-forms).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActiveForm extends Widget
{
/**
* Add validation state class to container tag
* @since 2.0.14
*/
const VALIDATION_STATE_ON_CONTAINER = 'container';
/**
* Add validation state class to input tag
* @since 2.0.14
*/
const VALIDATION_STATE_ON_INPUT = 'input';
/**
* @var array|string the form action URL. This parameter will be processed by [[\yii\helpers\Url::to()]].
* @see method for specifying the HTTP method for this form.
*/
public $action = '';
/**
* @var string the form submission method. This should be either `post` or `get`. Defaults to `post`.
*
* When you set this to `get` you may see the url parameters repeated on each request.
* This is because the default value of [[action]] is set to be the current request url and each submit
* will add new parameters instead of replacing existing ones.
* You may set [[action]] explicitly to avoid this:
*
* ```php
* $form = ActiveForm::begin([
* 'method' => 'get',
* 'action' => ['controller/action'],
* ]);
* ```
*/
public $method = 'post';
/**
* @var array the HTML attributes (name-value pairs) for the form tag.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = [];
/**
* @var string the default field class name when calling [[field()]] to create a new field.
* @see fieldConfig
*/
public $fieldClass = 'yii\widgets\ActiveField';
/**
* @var array|\Closure the default configuration used by [[field()]] when creating a new field object.
* This can be either a configuration array or an anonymous function returning a configuration array.
* If the latter, the signature should be as follows:
*
* ```php
* function ($model, $attribute)
* ```
*
* The value of this property will be merged recursively with the `$options` parameter passed to [[field()]].
*
* @see fieldClass
*/
public $fieldConfig = [];
/**
* @var bool whether to perform encoding on the error summary.
*/
public $encodeErrorSummary = true;
/**
* @var string the default CSS class for the error summary container.
* @see errorSummary()
*/
public $errorSummaryCssClass = 'error-summary';
/**
* @var string the CSS class that is added to a field container when the associated attribute is required.
*/
public $requiredCssClass = 'required';
/**
* @var string the CSS class that is added to a field container when the associated attribute has validation error.
*/
public $errorCssClass = 'has-error';
/**
* @var string the CSS class that is added to a field container when the associated attribute is successfully validated.
*/
public $successCssClass = 'has-success';
/**
* @var string the CSS class that is added to a field container when the associated attribute is being validated.
*/
public $validatingCssClass = 'validating';
/**
* @var string where to render validation state class
* Could be either "container" or "input".
* Default is "container".
* @since 2.0.14
*/
public $validationStateOn = self::VALIDATION_STATE_ON_CONTAINER;
/**
* @var bool whether to enable client-side data validation.
* If [[ActiveField::enableClientValidation]] is set, its value will take precedence for that input field.
*/
public $enableClientValidation = true;
/**
* @var bool whether to enable AJAX-based data validation.
* If [[ActiveField::enableAjaxValidation]] is set, its value will take precedence for that input field.
*/
public $enableAjaxValidation = false;
/**
* @var bool whether to hook up `yii.activeForm` JavaScript plugin.
* This property must be set `true` if you want to support client validation and/or AJAX validation, or if you
* want to take advantage of the `yii.activeForm` plugin. When this is `false`, the form will not generate
* any JavaScript.
* @see registerClientScript
*/
public $enableClientScript = true;
/**
* @var array|string the URL for performing AJAX-based validation. This property will be processed by
* [[Url::to()]]. Please refer to [[Url::to()]] for more details on how to configure this property.
* If this property is not set, it will take the value of the form's action attribute.
*/
public $validationUrl;
/**
* @var bool whether to perform validation when the form is submitted.
*/
public $validateOnSubmit = true;
/**
* @var bool whether to perform validation when the value of an input field is changed.
* If [[ActiveField::validateOnChange]] is set, its value will take precedence for that input field.
*/
public $validateOnChange = true;
/**
* @var bool whether to perform validation when an input field loses focus.
* If [[ActiveField::$validateOnBlur]] is set, its value will take precedence for that input field.
*/
public $validateOnBlur = true;
/**
* @var bool whether to perform validation while the user is typing in an input field.
* If [[ActiveField::validateOnType]] is set, its value will take precedence for that input field.
* @see validationDelay
*/
public $validateOnType = false;
/**
* @var int number of milliseconds that the validation should be delayed when the user types in the field
* and [[validateOnType]] is set `true`.
* If [[ActiveField::validationDelay]] is set, its value will take precedence for that input field.
*/
public $validationDelay = 500;
/**
* @var string the name of the GET parameter indicating the validation request is an AJAX request.
*/
public $ajaxParam = 'ajax';
/**
* @var string the type of data that you're expecting back from the server.
*/
public $ajaxDataType = 'json';
/**
* @var bool whether to scroll to the first error after validation.
* @since 2.0.6
*/
public $scrollToError = true;
/**
* @var int offset in pixels that should be added when scrolling to the first error.
* @since 2.0.11
*/
public $scrollToErrorOffset = 0;
/**
* @var array the client validation options for individual attributes. Each element of the array
* represents the validation options for a particular attribute.
* @internal
*/
public $attributes = [];
/**
* @var ActiveField[] the ActiveField objects that are currently active
*/
private $_fields = [];
/**
* Initializes the widget.
* This renders the form open tag.
*/
public function init()
{
parent::init();
if (!isset($this->options['id'])) {
$this->options['id'] = $this->getId();
}
ob_start();
ob_implicit_flush(false);
}
/**
* Runs the widget.
* This registers the necessary JavaScript code and renders the form open and close tags.
* @throws InvalidCallException if `beginField()` and `endField()` calls are not matching.
*/
public function run()
{
if (!empty($this->_fields)) {
throw new InvalidCallException('Each beginField() should have a matching endField() call.');
}
$content = ob_get_clean();
echo Html::beginForm($this->action, $this->method, $this->options);
echo $content;
if ($this->enableClientScript) {
$this->registerClientScript();
}
echo Html::endForm();
}
/**
* This registers the necessary JavaScript code.
* @since 2.0.12
*/
public function registerClientScript()
{
$id = $this->options['id'];
$options = Json::htmlEncode($this->getClientOptions());
$attributes = Json::htmlEncode($this->attributes);
$view = $this->getView();
ActiveFormAsset::register($view);
$view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
}
/**
* Returns the options for the form JS widget.
* @return array the options.
*/
protected function getClientOptions()
{
$options = [
'encodeErrorSummary' => $this->encodeErrorSummary,
'errorSummary' => '.' . implode('.', preg_split('/\s+/', $this->errorSummaryCssClass, -1, PREG_SPLIT_NO_EMPTY)),
'validateOnSubmit' => $this->validateOnSubmit,
'errorCssClass' => $this->errorCssClass,
'successCssClass' => $this->successCssClass,
'validatingCssClass' => $this->validatingCssClass,
'ajaxParam' => $this->ajaxParam,
'ajaxDataType' => $this->ajaxDataType,
'scrollToError' => $this->scrollToError,
'scrollToErrorOffset' => $this->scrollToErrorOffset,
'validationStateOn' => $this->validationStateOn,
];
if ($this->validationUrl !== null) {
$options['validationUrl'] = Url::to($this->validationUrl);
}
// only get the options that are different from the default ones (set in yii.activeForm.js)
return array_diff_assoc($options, [
'encodeErrorSummary' => true,
'errorSummary' => '.error-summary',
'validateOnSubmit' => true,
'errorCssClass' => 'has-error',
'successCssClass' => 'has-success',
'validatingCssClass' => 'validating',
'ajaxParam' => 'ajax',
'ajaxDataType' => 'json',
'scrollToError' => true,
'scrollToErrorOffset' => 0,
'validationStateOn' => self::VALIDATION_STATE_ON_CONTAINER,
]);
}
/**
* Generates a summary of the validation errors.
* If there is no validation error, an empty error summary markup will still be generated, but it will be hidden.
* @param Model|Model[] $models the model(s) associated with this form.
* @param array $options the tag options in terms of name-value pairs. The following options are specially handled:
*
* - `header`: string, the header HTML for the error summary. If not set, a default prompt string will be used.
* - `footer`: string, the footer HTML for the error summary.
*
* The rest of the options will be rendered as the attributes of the container tag. The values will
* be HTML-encoded using [[\yii\helpers\Html::encode()]]. If a value is `null`, the corresponding attribute will not be rendered.
* @return string the generated error summary.
* @see errorSummaryCssClass
*/
public function errorSummary($models, $options = [])
{
Html::addCssClass($options, $this->errorSummaryCssClass);
$options['encode'] = $this->encodeErrorSummary;
return Html::errorSummary($models, $options);
}
/**
* Generates a form field.
* A form field is associated with a model and an attribute. It contains a label, an input and an error message
* and use them to interact with end users to collect their inputs for the attribute.
* @param Model $model the data model.
* @param string $attribute the attribute name or expression. See [[Html::getAttributeName()]] for the format
* about attribute expression.
* @param array $options the additional configurations for the field object. These are properties of [[ActiveField]]
* or a subclass, depending on the value of [[fieldClass]].
* @return ActiveField the created ActiveField object.
* @see fieldConfig
*/
public function field($model, $attribute, $options = [])
{
$config = $this->fieldConfig;
if ($config instanceof \Closure) {
$config = call_user_func($config, $model, $attribute);
}
if (!isset($config['class'])) {
$config['class'] = $this->fieldClass;
}
return Yii::createObject(ArrayHelper::merge($config, $options, [
'model' => $model,
'attribute' => $attribute,
'form' => $this,
]));
}
/**
* Begins a form field.
* This method will create a new form field and returns its opening tag.
* You should call [[endField()]] afterwards.
* @param Model $model the data model.
* @param string $attribute the attribute name or expression. See [[Html::getAttributeName()]] for the format
* about attribute expression.
* @param array $options the additional configurations for the field object.
* @return string the opening tag.
* @see endField()
* @see field()
*/
public function beginField($model, $attribute, $options = [])
{
$field = $this->field($model, $attribute, $options);
$this->_fields[] = $field;
return $field->begin();
}
/**
* Ends a form field.
* This method will return the closing tag of an active form field started by [[beginField()]].
* @return string the closing tag of the form field.
* @throws InvalidCallException if this method is called without a prior [[beginField()]] call.
*/
public function endField()
{
$field = array_pop($this->_fields);
if ($field instanceof ActiveField) {
return $field->end();
}
throw new InvalidCallException('Mismatching endField() call.');
}
/**
* Validates one or several models and returns an error message array indexed by the attribute IDs.
* This is a helper method that simplifies the way of writing AJAX validation code.
*
* For example, you may use the following code in a controller action to respond
* to an AJAX validation request:
*
* ```php
* $model = new Post;
* $model->load(Yii::$app->request->post());
* if (Yii::$app->request->isAjax) {
* Yii::$app->response->format = Response::FORMAT_JSON;
* return ActiveForm::validate($model);
* }
* // ... respond to non-AJAX request ...
* ```
*
* To validate multiple models, simply pass each model as a parameter to this method, like
* the following:
*
* ```php
* ActiveForm::validate($model1, $model2, ...);
* ```
*
* @param Model $model the model to be validated.
* @param mixed $attributes list of attributes that should be validated.
* If this parameter is empty, it means any attribute listed in the applicable
* validation rules should be validated.
*
* When this method is used to validate multiple models, this parameter will be interpreted
* as a model.
*
* @return array the error message array indexed by the attribute IDs.
*/
public static function validate($model, $attributes = null)
{
$result = [];
if ($attributes instanceof Model) {
// validating multiple models
$models = func_get_args();
$attributes = null;
} else {
$models = [$model];
}
/* @var $model Model */
foreach ($models as $model) {
$model->validate($attributes);
foreach ($model->getErrors() as $attribute => $errors) {
$result[Html::getInputId($model, $attribute)] = $errors;
}
}
return $result;
}
/**
* Validates an array of model instances and returns an error message array indexed by the attribute IDs.
* This is a helper method that simplifies the way of writing AJAX validation code for tabular input.
*
* For example, you may use the following code in a controller action to respond
* to an AJAX validation request:
*
* ```php
* // ... load $models ...
* if (Yii::$app->request->isAjax) {
* Yii::$app->response->format = Response::FORMAT_JSON;
* return ActiveForm::validateMultiple($models);
* }
* // ... respond to non-AJAX request ...
* ```
*
* @param array $models an array of models to be validated.
* @param mixed $attributes list of attributes that should be validated.
* If this parameter is empty, it means any attribute listed in the applicable
* validation rules should be validated.
* @return array the error message array indexed by the attribute IDs.
*/
public static function validateMultiple($models, $attributes = null)
{
$result = [];
/* @var $model Model */
foreach ($models as $i => $model) {
$model->validate($attributes);
foreach ($model->getErrors() as $attribute => $errors) {
$result[Html::getInputId($model, "[$i]" . $attribute)] = $errors;
}
}
return $result;
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use yii\web\AssetBundle;
/**
* The asset bundle for the [[ActiveForm]] widget.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActiveFormAsset extends AssetBundle
{
public $sourcePath = '@yii/assets';
public $js = [
'yii.activeForm.js',
];
public $depends = [
'yii\web\YiiAsset',
];
}

View File

@@ -0,0 +1,277 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* BaseListView is a base class for widgets displaying data from data provider
* such as ListView and GridView.
*
* It provides features like sorting, paging and also filtering the data.
*
* For more details and usage information on BaseListView, see the [guide article on data widgets](guide:output-data-widgets).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
abstract class BaseListView extends Widget
{
/**
* @var array the HTML attributes for the container tag of the list view.
* The "tag" element specifies the tag name of the container element and defaults to "div".
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = [];
/**
* @var \yii\data\DataProviderInterface the data provider for the view. This property is required.
*/
public $dataProvider;
/**
* @var array the configuration for the pager widget. By default, [[LinkPager]] will be
* used to render the pager. You can use a different widget class by configuring the "class" element.
* Note that the widget must support the `pagination` property which will be populated with the
* [[\yii\data\BaseDataProvider::pagination|pagination]] value of the [[dataProvider]] and will overwrite this value.
*/
public $pager = [];
/**
* @var array the configuration for the sorter widget. By default, [[LinkSorter]] will be
* used to render the sorter. You can use a different widget class by configuring the "class" element.
* Note that the widget must support the `sort` property which will be populated with the
* [[\yii\data\BaseDataProvider::sort|sort]] value of the [[dataProvider]] and will overwrite this value.
*/
public $sorter = [];
/**
* @var string the HTML content to be displayed as the summary of the list view.
* If you do not want to show the summary, you may set it with an empty string.
*
* The following tokens will be replaced with the corresponding values:
*
* - `{begin}`: the starting row number (1-based) currently being displayed
* - `{end}`: the ending row number (1-based) currently being displayed
* - `{count}`: the number of rows currently being displayed
* - `{totalCount}`: the total number of rows available
* - `{page}`: the page number (1-based) current being displayed
* - `{pageCount}`: the number of pages available
*/
public $summary;
/**
* @var array the HTML attributes for the summary of the list view.
* The "tag" element specifies the tag name of the summary element and defaults to "div".
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $summaryOptions = ['class' => 'summary'];
/**
* @var bool whether to show an empty list view if [[dataProvider]] returns no data.
* The default value is false which displays an element according to the [[emptyText]]
* and [[emptyTextOptions]] properties.
*/
public $showOnEmpty = false;
/**
* @var string|false the HTML content to be displayed when [[dataProvider]] does not have any data.
* When this is set to `false` no extra HTML content will be generated.
* The default value is the text "No results found." which will be translated to the current application language.
* @see showOnEmpty
* @see emptyTextOptions
*/
public $emptyText;
/**
* @var array the HTML attributes for the emptyText of the list view.
* The "tag" element specifies the tag name of the emptyText element and defaults to "div".
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $emptyTextOptions = ['class' => 'empty'];
/**
* @var string the layout that determines how different sections of the list view should be organized.
* The following tokens will be replaced with the corresponding section contents:
*
* - `{summary}`: the summary section. See [[renderSummary()]].
* - `{items}`: the list items. See [[renderItems()]].
* - `{sorter}`: the sorter. See [[renderSorter()]].
* - `{pager}`: the pager. See [[renderPager()]].
*/
public $layout = "{summary}\n{items}\n{pager}";
/**
* Renders the data models.
* @return string the rendering result.
*/
abstract public function renderItems();
/**
* Initializes the view.
*/
public function init()
{
parent::init();
if ($this->dataProvider === null) {
throw new InvalidConfigException('The "dataProvider" property must be set.');
}
if ($this->emptyText === null) {
$this->emptyText = Yii::t('yii', 'No results found.');
}
if (!isset($this->options['id'])) {
$this->options['id'] = $this->getId();
}
}
/**
* Runs the widget.
*/
public function run()
{
if ($this->showOnEmpty || $this->dataProvider->getCount() > 0) {
$content = preg_replace_callback('/{\\w+}/', function ($matches) {
$content = $this->renderSection($matches[0]);
return $content === false ? $matches[0] : $content;
}, $this->layout);
} else {
$content = $this->renderEmpty();
}
$options = $this->options;
$tag = ArrayHelper::remove($options, 'tag', 'div');
echo Html::tag($tag, $content, $options);
}
/**
* Renders a section of the specified name.
* If the named section is not supported, false will be returned.
* @param string $name the section name, e.g., `{summary}`, `{items}`.
* @return string|bool the rendering result of the section, or false if the named section is not supported.
*/
public function renderSection($name)
{
switch ($name) {
case '{summary}':
return $this->renderSummary();
case '{items}':
return $this->renderItems();
case '{pager}':
return $this->renderPager();
case '{sorter}':
return $this->renderSorter();
default:
return false;
}
}
/**
* Renders the HTML content indicating that the list view has no data.
* @return string the rendering result
* @see emptyText
*/
public function renderEmpty()
{
if ($this->emptyText === false) {
return '';
}
$options = $this->emptyTextOptions;
$tag = ArrayHelper::remove($options, 'tag', 'div');
return Html::tag($tag, $this->emptyText, $options);
}
/**
* Renders the summary text.
*/
public function renderSummary()
{
$count = $this->dataProvider->getCount();
if ($count <= 0) {
return '';
}
$summaryOptions = $this->summaryOptions;
$tag = ArrayHelper::remove($summaryOptions, 'tag', 'div');
if (($pagination = $this->dataProvider->getPagination()) !== false) {
$totalCount = $this->dataProvider->getTotalCount();
$begin = $pagination->getPage() * $pagination->pageSize + 1;
$end = $begin + $count - 1;
if ($begin > $end) {
$begin = $end;
}
$page = $pagination->getPage() + 1;
$pageCount = $pagination->pageCount;
if (($summaryContent = $this->summary) === null) {
return Html::tag($tag, Yii::t('yii', 'Showing <b>{begin, number}-{end, number}</b> of <b>{totalCount, number}</b> {totalCount, plural, one{item} other{items}}.', [
'begin' => $begin,
'end' => $end,
'count' => $count,
'totalCount' => $totalCount,
'page' => $page,
'pageCount' => $pageCount,
]), $summaryOptions);
}
} else {
$begin = $page = $pageCount = 1;
$end = $totalCount = $count;
if (($summaryContent = $this->summary) === null) {
return Html::tag($tag, Yii::t('yii', 'Total <b>{count, number}</b> {count, plural, one{item} other{items}}.', [
'begin' => $begin,
'end' => $end,
'count' => $count,
'totalCount' => $totalCount,
'page' => $page,
'pageCount' => $pageCount,
]), $summaryOptions);
}
}
return Yii::$app->getI18n()->format($summaryContent, [
'begin' => $begin,
'end' => $end,
'count' => $count,
'totalCount' => $totalCount,
'page' => $page,
'pageCount' => $pageCount,
], Yii::$app->language);
}
/**
* Renders the pager.
* @return string the rendering result
*/
public function renderPager()
{
$pagination = $this->dataProvider->getPagination();
if ($pagination === false || $this->dataProvider->getCount() <= 0) {
return '';
}
/* @var $class LinkPager */
$pager = $this->pager;
$class = ArrayHelper::remove($pager, 'class', LinkPager::className());
$pager['pagination'] = $pagination;
$pager['view'] = $this->getView();
return $class::widget($pager);
}
/**
* Renders the sorter.
* @return string the rendering result
*/
public function renderSorter()
{
$sort = $this->dataProvider->getSort();
if ($sort === false || empty($sort->attributes) || $this->dataProvider->getCount() <= 0) {
return '';
}
/* @var $class LinkSorter */
$sorter = $this->sorter;
$class = ArrayHelper::remove($sorter, 'class', LinkSorter::className());
$sorter['sort'] = $sort;
$sorter['view'] = $this->getView();
return $class::widget($sorter);
}
}

71
vendor/yiisoft/yii2/widgets/Block.php vendored Normal file
View File

@@ -0,0 +1,71 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use yii\base\Widget;
/**
* Block records all output between [[begin()]] and [[end()]] calls and stores it in [[\yii\base\View::$blocks]].
* for later use.
*
* [[\yii\base\View]] component contains two methods [[\yii\base\View::beginBlock()]] and [[\yii\base\View::endBlock()]].
* The general idea is that you're defining block default in a view or layout:
*
* ```php
* <?php $this->beginBlock('messages', true) ?>
* Nothing.
* <?php $this->endBlock() ?>
* ```
*
* And then overriding default in sub-views:
*
* ```php
* <?php $this->beginBlock('username') ?>
* Umm... hello?
* <?php $this->endBlock() ?>
* ```
*
* Second parameter defines if block content should be outputted which is desired when rendering its content but isn't
* desired when redefining it in subviews.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Block extends Widget
{
/**
* @var bool whether to render the block content in place. Defaults to false,
* meaning the captured block content will not be displayed.
*/
public $renderInPlace = false;
/**
* Starts recording a block.
*/
public function init()
{
parent::init();
ob_start();
ob_implicit_flush(false);
}
/**
* Ends recording a block.
* This method stops output buffering and saves the rendering result as a named block in the view.
*/
public function run()
{
$block = ob_get_clean();
if ($this->renderInPlace) {
echo $block;
}
$this->view->blocks[$this->getId()] = $block;
}
}

View File

@@ -0,0 +1,181 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* Breadcrumbs displays a list of links indicating the position of the current page in the whole site hierarchy.
*
* For example, breadcrumbs like "Home / Sample Post / Edit" means the user is viewing an edit page
* for the "Sample Post". He can click on "Sample Post" to view that page, or he can click on "Home"
* to return to the homepage.
*
* To use Breadcrumbs, you need to configure its [[links]] property, which specifies the links to be displayed. For example,
*
* ```php
* // $this is the view object currently being used
* echo Breadcrumbs::widget([
* 'itemTemplate' => "<li><i>{link}</i></li>\n", // template for all links
* 'links' => [
* [
* 'label' => 'Post Category',
* 'url' => ['post-category/view', 'id' => 10],
* 'template' => "<li><b>{link}</b></li>\n", // template for this link only
* ],
* ['label' => 'Sample Post', 'url' => ['post/edit', 'id' => 1]],
* 'Edit',
* ],
* ]);
* ```
*
* Because breadcrumbs usually appears in nearly every page of a website, you may consider placing it in a layout view.
* You can use a view parameter (e.g. `$this->params['breadcrumbs']`) to configure the links in different
* views. In the layout view, you assign this view parameter to the [[links]] property like the following:
*
* ```php
* // $this is the view object currently being used
* echo Breadcrumbs::widget([
* 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
* ]);
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Breadcrumbs extends Widget
{
/**
* @var string the name of the breadcrumb container tag.
*/
public $tag = 'ul';
/**
* @var array the HTML attributes for the breadcrumb container tag.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = ['class' => 'breadcrumb'];
/**
* @var bool whether to HTML-encode the link labels.
*/
public $encodeLabels = true;
/**
* @var array the first hyperlink in the breadcrumbs (called home link).
* Please refer to [[links]] on the format of the link.
* If this property is not set, it will default to a link pointing to [[\yii\web\Application::homeUrl]]
* with the label 'Home'. If this property is false, the home link will not be rendered.
*/
public $homeLink;
/**
* @var array list of links to appear in the breadcrumbs. If this property is empty,
* the widget will not render anything. Each array element represents a single link in the breadcrumbs
* with the following structure:
*
* ```php
* [
* 'label' => 'label of the link', // required
* 'url' => 'url of the link', // optional, will be processed by Url::to()
* 'template' => 'own template of the item', // optional, if not set $this->itemTemplate will be used
* ]
* ```
*
* If a link is active, you only need to specify its "label", and instead of writing `['label' => $label]`,
* you may simply use `$label`.
*
* Since version 2.0.1, any additional array elements for each link will be treated as the HTML attributes
* for the hyperlink tag. For example, the following link specification will generate a hyperlink
* with CSS class `external`:
*
* ```php
* [
* 'label' => 'demo',
* 'url' => 'http://example.com',
* 'class' => 'external',
* ]
* ```
*
* Since version 2.0.3 each individual link can override global [[encodeLabels]] param like the following:
*
* ```php
* [
* 'label' => '<strong>Hello!</strong>',
* 'encode' => false,
* ]
* ```
*/
public $links = [];
/**
* @var string the template used to render each inactive item in the breadcrumbs. The token `{link}`
* will be replaced with the actual HTML link for each inactive item.
*/
public $itemTemplate = "<li>{link}</li>\n";
/**
* @var string the template used to render each active item in the breadcrumbs. The token `{link}`
* will be replaced with the actual HTML link for each active item.
*/
public $activeItemTemplate = "<li class=\"active\">{link}</li>\n";
/**
* Renders the widget.
*/
public function run()
{
if (empty($this->links)) {
return;
}
$links = [];
if ($this->homeLink === null) {
$links[] = $this->renderItem([
'label' => Yii::t('yii', 'Home'),
'url' => Yii::$app->homeUrl,
], $this->itemTemplate);
} elseif ($this->homeLink !== false) {
$links[] = $this->renderItem($this->homeLink, $this->itemTemplate);
}
foreach ($this->links as $link) {
if (!is_array($link)) {
$link = ['label' => $link];
}
$links[] = $this->renderItem($link, isset($link['url']) ? $this->itemTemplate : $this->activeItemTemplate);
}
echo Html::tag($this->tag, implode('', $links), $this->options);
}
/**
* Renders a single breadcrumb item.
* @param array $link the link to be rendered. It must contain the "label" element. The "url" element is optional.
* @param string $template the template to be used to rendered the link. The token "{link}" will be replaced by the link.
* @return string the rendering result
* @throws InvalidConfigException if `$link` does not have "label" element.
*/
protected function renderItem($link, $template)
{
$encodeLabel = ArrayHelper::remove($link, 'encode', $this->encodeLabels);
if (array_key_exists('label', $link)) {
$label = $encodeLabel ? Html::encode($link['label']) : $link['label'];
} else {
throw new InvalidConfigException('The "label" element is required for each link.');
}
if (isset($link['template'])) {
$template = $link['template'];
}
if (isset($link['url'])) {
$options = $link;
unset($options['template'], $options['label'], $options['url']);
$link = Html::a($label, $link['url'], $options);
} else {
$link = $label;
}
return strtr($template, ['{link}' => $link]);
}
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use yii\base\InvalidConfigException;
use yii\base\Widget;
/**
* ContentDecorator records all output between [[begin()]] and [[end()]] calls, passes it to the given view file
* as `$content` and then echoes rendering result.
*
* ```php
* <?php ContentDecorator::begin([
* 'viewFile' => '@app/views/layouts/base.php',
* 'params' => [],
* 'view' => $this,
* ]) ?>
*
* some content here
*
* <?php ContentDecorator::end() ?>
* ```
*
* There are [[\yii\base\View::beginContent()]] and [[\yii\base\View::endContent()]] wrapper methods in the
* [[\yii\base\View]] component to make syntax more friendly. In the view these could be used as follows:
*
* ```php
* <?php $this->beginContent('@app/views/layouts/base.php') ?>
*
* some content here
*
* <?php $this->endContent() ?>
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ContentDecorator extends Widget
{
/**
* @var string the view file that will be used to decorate the content enclosed by this widget.
* This can be specified as either the view file path or [path alias](guide:concept-aliases).
*/
public $viewFile;
/**
* @var array the parameters (name => value) to be extracted and made available in the decorative view.
*/
public $params = [];
/**
* Starts recording a clip.
*/
public function init()
{
parent::init();
if ($this->viewFile === null) {
throw new InvalidConfigException('ContentDecorator::viewFile must be set.');
}
ob_start();
ob_implicit_flush(false);
}
/**
* Ends recording a clip.
* This method stops output buffering and saves the rendering result as a named clip in the controller.
*/
public function run()
{
$params = $this->params;
$params['content'] = ob_get_clean();
// render under the existing context
echo $this->view->renderFile($this->viewFile, $params);
}
}

View File

@@ -0,0 +1,251 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Yii;
use yii\base\Arrayable;
use yii\base\InvalidConfigException;
use yii\base\Model;
use yii\base\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\helpers\Inflector;
use yii\i18n\Formatter;
/**
* DetailView displays the detail of a single data [[model]].
*
* DetailView is best used for displaying a model in a regular format (e.g. each model attribute
* is displayed as a row in a table.) The model can be either an instance of [[Model]]
* or an associative array.
*
* DetailView uses the [[attributes]] property to determines which model attributes
* should be displayed and how they should be formatted.
*
* A typical usage of DetailView is as follows:
*
* ```php
* echo DetailView::widget([
* 'model' => $model,
* 'attributes' => [
* 'title', // title attribute (in plain text)
* 'description:html', // description attribute in HTML
* [ // the owner name of the model
* 'label' => 'Owner',
* 'value' => $model->owner->name,
* ],
* 'created_at:datetime', // creation date formatted as datetime
* ],
* ]);
* ```
*
* For more details and usage information on DetailView, see the [guide article on data widgets](guide:output-data-widgets).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class DetailView extends Widget
{
/**
* @var array|object the data model whose details are to be displayed. This can be a [[Model]] instance,
* an associative array, an object that implements [[Arrayable]] interface or simply an object with defined
* public accessible non-static properties.
*/
public $model;
/**
* @var array a list of attributes to be displayed in the detail view. Each array element
* represents the specification for displaying one particular attribute.
*
* An attribute can be specified as a string in the format of `attribute`, `attribute:format` or `attribute:format:label`,
* where `attribute` refers to the attribute name, and `format` represents the format of the attribute. The `format`
* is passed to the [[Formatter::format()]] method to format an attribute value into a displayable text.
* Please refer to [[Formatter]] for the supported types. Both `format` and `label` are optional.
* They will take default values if absent.
*
* An attribute can also be specified in terms of an array with the following elements:
*
* - `attribute`: the attribute name. This is required if either `label` or `value` is not specified.
* - `label`: the label associated with the attribute. If this is not specified, it will be generated from the attribute name.
* - `value`: the value to be displayed. If this is not specified, it will be retrieved from [[model]] using the attribute name
* by calling [[ArrayHelper::getValue()]]. Note that this value will be formatted into a displayable text
* according to the `format` option. Since version 2.0.11 it can be defined as closure with the following
* parameters:
*
* ```php
* function ($model, $widget)
* ```
*
* `$model` refers to displayed model and `$widget` is an instance of `DetailView` widget.
*
* - `format`: the type of the value that determines how the value would be formatted into a displayable text.
* Please refer to [[Formatter]] for supported types and [[Formatter::format()]] on how to specify this value.
* - `visible`: whether the attribute is visible. If set to `false`, the attribute will NOT be displayed.
* - `contentOptions`: the HTML attributes to customize value tag. For example: `['class' => 'bg-red']`.
* Please refer to [[\yii\helpers\BaseHtml::renderTagAttributes()]] for the supported syntax.
* - `captionOptions`: the HTML attributes to customize label tag. For example: `['class' => 'bg-red']`.
* Please refer to [[\yii\helpers\BaseHtml::renderTagAttributes()]] for the supported syntax.
*/
public $attributes;
/**
* @var string|callable the template used to render a single attribute. If a string, the token `{label}`
* and `{value}` will be replaced with the label and the value of the corresponding attribute.
* If a callback (e.g. an anonymous function), the signature must be as follows:
*
* ```php
* function ($attribute, $index, $widget)
* ```
*
* where `$attribute` refer to the specification of the attribute being rendered, `$index` is the zero-based
* index of the attribute in the [[attributes]] array, and `$widget` refers to this widget instance.
*
* Since Version 2.0.10, the tokens `{captionOptions}` and `{contentOptions}` are available, which will represent
* HTML attributes of HTML container elements for the label and value.
*/
public $template = '<tr><th{captionOptions}>{label}</th><td{contentOptions}>{value}</td></tr>';
/**
* @var array the HTML attributes for the container tag of this widget. The `tag` option specifies
* what container tag should be used. It defaults to `table` if not set.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = ['class' => 'table table-striped table-bordered detail-view'];
/**
* @var array|Formatter the formatter used to format model attribute values into displayable texts.
* This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]]
* instance. If this property is not set, the `formatter` application component will be used.
*/
public $formatter;
/**
* Initializes the detail view.
* This method will initialize required property values.
*/
public function init()
{
parent::init();
if ($this->model === null) {
throw new InvalidConfigException('Please specify the "model" property.');
}
if ($this->formatter === null) {
$this->formatter = Yii::$app->getFormatter();
} elseif (is_array($this->formatter)) {
$this->formatter = Yii::createObject($this->formatter);
}
if (!$this->formatter instanceof Formatter) {
throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.');
}
$this->normalizeAttributes();
if (!isset($this->options['id'])) {
$this->options['id'] = $this->getId();
}
}
/**
* Renders the detail view.
* This is the main entry of the whole detail view rendering.
*/
public function run()
{
$rows = [];
$i = 0;
foreach ($this->attributes as $attribute) {
$rows[] = $this->renderAttribute($attribute, $i++);
}
$options = $this->options;
$tag = ArrayHelper::remove($options, 'tag', 'table');
echo Html::tag($tag, implode("\n", $rows), $options);
}
/**
* Renders a single attribute.
* @param array $attribute the specification of the attribute to be rendered.
* @param int $index the zero-based index of the attribute in the [[attributes]] array
* @return string the rendering result
*/
protected function renderAttribute($attribute, $index)
{
if (is_string($this->template)) {
$captionOptions = Html::renderTagAttributes(ArrayHelper::getValue($attribute, 'captionOptions', []));
$contentOptions = Html::renderTagAttributes(ArrayHelper::getValue($attribute, 'contentOptions', []));
return strtr($this->template, [
'{label}' => $attribute['label'],
'{value}' => $this->formatter->format($attribute['value'], $attribute['format']),
'{captionOptions}' => $captionOptions,
'{contentOptions}' => $contentOptions,
]);
}
return call_user_func($this->template, $attribute, $index, $this);
}
/**
* Normalizes the attribute specifications.
* @throws InvalidConfigException
*/
protected function normalizeAttributes()
{
if ($this->attributes === null) {
if ($this->model instanceof Model) {
$this->attributes = $this->model->attributes();
} elseif (is_object($this->model)) {
$this->attributes = $this->model instanceof Arrayable ? array_keys($this->model->toArray()) : array_keys(get_object_vars($this->model));
} elseif (is_array($this->model)) {
$this->attributes = array_keys($this->model);
} else {
throw new InvalidConfigException('The "model" property must be either an array or an object.');
}
sort($this->attributes);
}
foreach ($this->attributes as $i => $attribute) {
if (is_string($attribute)) {
if (!preg_match('/^([^:]+)(:(\w*))?(:(.*))?$/', $attribute, $matches)) {
throw new InvalidConfigException('The attribute must be specified in the format of "attribute", "attribute:format" or "attribute:format:label"');
}
$attribute = [
'attribute' => $matches[1],
'format' => isset($matches[3]) ? $matches[3] : 'text',
'label' => isset($matches[5]) ? $matches[5] : null,
];
}
if (!is_array($attribute)) {
throw new InvalidConfigException('The attribute configuration must be an array.');
}
if (isset($attribute['visible']) && !$attribute['visible']) {
unset($this->attributes[$i]);
continue;
}
if (!isset($attribute['format'])) {
$attribute['format'] = 'text';
}
if (isset($attribute['attribute'])) {
$attributeName = $attribute['attribute'];
if (!isset($attribute['label'])) {
$attribute['label'] = $this->model instanceof Model ? $this->model->getAttributeLabel($attributeName) : Inflector::camel2words($attributeName, true);
}
if (!array_key_exists('value', $attribute)) {
$attribute['value'] = ArrayHelper::getValue($this->model, $attributeName);
}
} elseif (!isset($attribute['label']) || !array_key_exists('value', $attribute)) {
throw new InvalidConfigException('The attribute configuration requires the "attribute" element to determine the value and display label.');
}
if ($attribute['value'] instanceof \Closure) {
$attribute['value'] = call_user_func($attribute['value'], $this->model, $this);
}
$this->attributes[$i] = $attribute;
}
}
}

View File

@@ -0,0 +1,166 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Yii;
use yii\base\DynamicContentAwareInterface;
use yii\base\DynamicContentAwareTrait;
use yii\base\Widget;
use yii\caching\CacheInterface;
use yii\caching\Dependency;
use yii\di\Instance;
/**
* FragmentCache is used by [[\yii\base\View]] to provide caching of page fragments.
*
* @property string|false $cachedContent The cached content. False is returned if valid content is not found
* in the cache. This property is read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class FragmentCache extends Widget implements DynamicContentAwareInterface
{
use DynamicContentAwareTrait;
/**
* @var CacheInterface|array|string the cache object or the application component ID of the cache object.
* After the FragmentCache object is created, if you want to change this property,
* you should only assign it with a cache object.
* Starting from version 2.0.2, this can also be a configuration array for creating the object.
*/
public $cache = 'cache';
/**
* @var int number of seconds that the data can remain valid in cache.
* Use 0 to indicate that the cached data will never expire.
*/
public $duration = 60;
/**
* @var array|Dependency the dependency that the cached content depends on.
* This can be either a [[Dependency]] object or a configuration array for creating the dependency object.
* For example,
*
* ```php
* [
* 'class' => 'yii\caching\DbDependency',
* 'sql' => 'SELECT MAX(updated_at) FROM post',
* ]
* ```
*
* would make the output cache depends on the last modified time of all posts.
* If any post has its modification time changed, the cached content would be invalidated.
*/
public $dependency;
/**
* @var string[]|string list of factors that would cause the variation of the content being cached.
* Each factor is a string representing a variation (e.g. the language, a GET parameter).
* The following variation setting will cause the content to be cached in different versions
* according to the current application language:
*
* ```php
* [
* Yii::$app->language,
* ]
* ```
*/
public $variations;
/**
* @var bool whether to enable the fragment cache. You may use this property to turn on and off
* the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests).
*/
public $enabled = true;
/**
* Initializes the FragmentCache object.
*/
public function init()
{
parent::init();
$this->cache = $this->enabled ? Instance::ensure($this->cache, 'yii\caching\CacheInterface') : null;
if ($this->cache instanceof CacheInterface && $this->getCachedContent() === false) {
$this->getView()->pushDynamicContent($this);
ob_start();
ob_implicit_flush(false);
}
}
/**
* Marks the end of content to be cached.
* Content displayed before this method call and after [[init()]]
* will be captured and saved in cache.
* This method does nothing if valid content is already found in cache.
*/
public function run()
{
if (($content = $this->getCachedContent()) !== false) {
echo $content;
} elseif ($this->cache instanceof CacheInterface) {
$this->getView()->popDynamicContent();
$content = ob_get_clean();
if ($content === false || $content === '') {
return;
}
if (is_array($this->dependency)) {
$this->dependency = Yii::createObject($this->dependency);
}
$data = [$content, $this->getDynamicPlaceholders()];
$this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
echo $this->updateDynamicContent($content, $this->getDynamicPlaceholders());
}
}
/**
* @var string|bool the cached content. False if the content is not cached.
*/
private $_content;
/**
* Returns the cached content if available.
* @return string|false the cached content. False is returned if valid content is not found in the cache.
*/
public function getCachedContent()
{
if ($this->_content !== null) {
return $this->_content;
}
$this->_content = false;
if (!($this->cache instanceof CacheInterface)) {
return $this->_content;
}
$key = $this->calculateKey();
$data = $this->cache->get($key);
if (!is_array($data) || count($data) !== 2) {
return $this->_content;
}
list($this->_content, $placeholders) = $data;
if (!is_array($placeholders) || count($placeholders) === 0) {
return $this->_content;
}
$this->_content = $this->updateDynamicContent($this->_content, $placeholders, true);
return $this->_content;
}
/**
* Generates a unique key used for storing the content in cache.
* The key generated depends on both [[id]] and [[variations]].
* @return mixed a valid cache key
*/
protected function calculateKey()
{
return array_merge([__CLASS__, $this->getId()], (array)$this->variations);
}
}

View File

@@ -0,0 +1,110 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\Model;
use yii\base\Widget;
use yii\helpers\Html;
/**
* InputWidget is the base class for widgets that collect user inputs.
*
* An input widget can be associated with a data [[model]] and an [[attribute]],
* or a [[name]] and a [[value]]. If the former, the name and the value will
* be generated automatically (subclasses may call [[renderInputHtml()]] to follow this behavior).
*
* Classes extending from this widget can be used in an [[\yii\widgets\ActiveForm|ActiveForm]]
* using the [[\yii\widgets\ActiveField::widget()|widget()]] method, for example like this:
*
* ```php
* <?= $form->field($model, 'from_date')->widget('WidgetClassName', [
* // configure additional widget properties here
* ]) ?>
* ```
*
* For more details and usage information on InputWidget, see the [guide article on forms](guide:input-forms).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class InputWidget extends Widget
{
/**
* @var \yii\widgets\ActiveField active input field, which triggers this widget rendering.
* This field will be automatically filled up in case widget instance is created via [[\yii\widgets\ActiveField::widget()]].
* @since 2.0.11
*/
public $field;
/**
* @var Model the data model that this widget is associated with.
*/
public $model;
/**
* @var string the model attribute that this widget is associated with.
*/
public $attribute;
/**
* @var string the input name. This must be set if [[model]] and [[attribute]] are not set.
*/
public $name;
/**
* @var string the input value.
*/
public $value;
/**
* @var array the HTML attributes for the input tag.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = [];
/**
* Initializes the widget.
* If you override this method, make sure you call the parent implementation first.
*/
public function init()
{
if ($this->name === null && !$this->hasModel()) {
throw new InvalidConfigException("Either 'name', or 'model' and 'attribute' properties must be specified.");
}
if (!isset($this->options['id'])) {
$this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId();
}
parent::init();
}
/**
* @return bool whether this widget is associated with a data model.
*/
protected function hasModel()
{
return $this->model instanceof Model && $this->attribute !== null;
}
/**
* Render a HTML input tag.
*
* This will call [[Html::activeInput()]] if the input widget is [[hasModel()|tied to a model]],
* or [[Html::input()]] if not.
*
* @param string $type the type of the input to create.
* @return string the HTML of the input field.
* @since 2.0.13
* @see Html::activeInput()
* @see Html::input()
*/
protected function renderInputHtml($type)
{
if ($this->hasModel()) {
return Html::activeInput($type, $this->model, $this->attribute, $this->options);
}
return Html::input($type, $this->name, $this->value, $this->options);
}
}

View File

@@ -0,0 +1,275 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\Widget;
use yii\data\Pagination;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* LinkPager displays a list of hyperlinks that lead to different pages of target.
*
* LinkPager works with a [[Pagination]] object which specifies the total number
* of pages and the current page number.
*
* Note that LinkPager only generates the necessary HTML markups. In order for it
* to look like a real pager, you should provide some CSS styles for it.
* With the default configuration, LinkPager should look good using Twitter Bootstrap CSS framework.
*
* For more details and usage information on LinkPager, see the [guide article on pagination](guide:output-pagination).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class LinkPager extends Widget
{
/**
* @var Pagination the pagination object that this pager is associated with.
* You must set this property in order to make LinkPager work.
*/
public $pagination;
/**
* @var array HTML attributes for the pager container tag.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = ['class' => 'pagination'];
/**
* @var array HTML attributes which will be applied to all link containers
* @since 2.0.13
*/
public $linkContainerOptions = [];
/**
* @var array HTML attributes for the link in a pager container tag.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $linkOptions = [];
/**
* @var string the CSS class for the each page button.
* @since 2.0.7
*/
public $pageCssClass;
/**
* @var string the CSS class for the "first" page button.
*/
public $firstPageCssClass = 'first';
/**
* @var string the CSS class for the "last" page button.
*/
public $lastPageCssClass = 'last';
/**
* @var string the CSS class for the "previous" page button.
*/
public $prevPageCssClass = 'prev';
/**
* @var string the CSS class for the "next" page button.
*/
public $nextPageCssClass = 'next';
/**
* @var string the CSS class for the active (currently selected) page button.
*/
public $activePageCssClass = 'active';
/**
* @var string the CSS class for the disabled page buttons.
*/
public $disabledPageCssClass = 'disabled';
/**
* @var array the options for the disabled tag to be generated inside the disabled list element.
* In order to customize the html tag, please use the tag key.
*
* ```php
* $disabledListItemSubTagOptions = ['tag' => 'div', 'class' => 'disabled-div'];
* ```
* @since 2.0.11
*/
public $disabledListItemSubTagOptions = [];
/**
* @var int maximum number of page buttons that can be displayed. Defaults to 10.
*/
public $maxButtonCount = 10;
/**
* @var string|bool the label for the "next" page button. Note that this will NOT be HTML-encoded.
* If this property is false, the "next" page button will not be displayed.
*/
public $nextPageLabel = '&raquo;';
/**
* @var string|bool the text label for the previous page button. Note that this will NOT be HTML-encoded.
* If this property is false, the "previous" page button will not be displayed.
*/
public $prevPageLabel = '&laquo;';
/**
* @var string|bool the text label for the "first" page button. Note that this will NOT be HTML-encoded.
* If it's specified as true, page number will be used as label.
* Default is false that means the "first" page button will not be displayed.
*/
public $firstPageLabel = false;
/**
* @var string|bool the text label for the "last" page button. Note that this will NOT be HTML-encoded.
* If it's specified as true, page number will be used as label.
* Default is false that means the "last" page button will not be displayed.
*/
public $lastPageLabel = false;
/**
* @var bool whether to register link tags in the HTML header for prev, next, first and last page.
* Defaults to `false` to avoid conflicts when multiple pagers are used on one page.
* @see http://www.w3.org/TR/html401/struct/links.html#h-12.1.2
* @see registerLinkTags()
*/
public $registerLinkTags = false;
/**
* @var bool Hide widget when only one page exist.
*/
public $hideOnSinglePage = true;
/**
* @var bool whether to render current page button as disabled.
* @since 2.0.12
*/
public $disableCurrentPageButton = false;
/**
* Initializes the pager.
*/
public function init()
{
parent::init();
if ($this->pagination === null) {
throw new InvalidConfigException('The "pagination" property must be set.');
}
}
/**
* Executes the widget.
* This overrides the parent implementation by displaying the generated page buttons.
*/
public function run()
{
if ($this->registerLinkTags) {
$this->registerLinkTags();
}
echo $this->renderPageButtons();
}
/**
* Registers relational link tags in the html header for prev, next, first and last page.
* These links are generated using [[\yii\data\Pagination::getLinks()]].
* @see http://www.w3.org/TR/html401/struct/links.html#h-12.1.2
*/
protected function registerLinkTags()
{
$view = $this->getView();
foreach ($this->pagination->getLinks() as $rel => $href) {
$view->registerLinkTag(['rel' => $rel, 'href' => $href], $rel);
}
}
/**
* Renders the page buttons.
* @return string the rendering result
*/
protected function renderPageButtons()
{
$pageCount = $this->pagination->getPageCount();
if ($pageCount < 2 && $this->hideOnSinglePage) {
return '';
}
$buttons = [];
$currentPage = $this->pagination->getPage();
// first page
$firstPageLabel = $this->firstPageLabel === true ? '1' : $this->firstPageLabel;
if ($firstPageLabel !== false) {
$buttons[] = $this->renderPageButton($firstPageLabel, 0, $this->firstPageCssClass, $currentPage <= 0, false);
}
// prev page
if ($this->prevPageLabel !== false) {
if (($page = $currentPage - 1) < 0) {
$page = 0;
}
$buttons[] = $this->renderPageButton($this->prevPageLabel, $page, $this->prevPageCssClass, $currentPage <= 0, false);
}
// internal pages
list($beginPage, $endPage) = $this->getPageRange();
for ($i = $beginPage; $i <= $endPage; ++$i) {
$buttons[] = $this->renderPageButton($i + 1, $i, null, $this->disableCurrentPageButton && $i == $currentPage, $i == $currentPage);
}
// next page
if ($this->nextPageLabel !== false) {
if (($page = $currentPage + 1) >= $pageCount - 1) {
$page = $pageCount - 1;
}
$buttons[] = $this->renderPageButton($this->nextPageLabel, $page, $this->nextPageCssClass, $currentPage >= $pageCount - 1, false);
}
// last page
$lastPageLabel = $this->lastPageLabel === true ? $pageCount : $this->lastPageLabel;
if ($lastPageLabel !== false) {
$buttons[] = $this->renderPageButton($lastPageLabel, $pageCount - 1, $this->lastPageCssClass, $currentPage >= $pageCount - 1, false);
}
$options = $this->options;
$tag = ArrayHelper::remove($options, 'tag', 'ul');
return Html::tag($tag, implode("\n", $buttons), $options);
}
/**
* Renders a page button.
* You may override this method to customize the generation of page buttons.
* @param string $label the text label for the button
* @param int $page the page number
* @param string $class the CSS class for the page button.
* @param bool $disabled whether this page button is disabled
* @param bool $active whether this page button is active
* @return string the rendering result
*/
protected function renderPageButton($label, $page, $class, $disabled, $active)
{
$options = $this->linkContainerOptions;
$linkWrapTag = ArrayHelper::remove($options, 'tag', 'li');
Html::addCssClass($options, empty($class) ? $this->pageCssClass : $class);
if ($active) {
Html::addCssClass($options, $this->activePageCssClass);
}
if ($disabled) {
Html::addCssClass($options, $this->disabledPageCssClass);
$disabledItemOptions = $this->disabledListItemSubTagOptions;
$tag = ArrayHelper::remove($disabledItemOptions, 'tag', 'span');
return Html::tag($linkWrapTag, Html::tag($tag, $label, $disabledItemOptions), $options);
}
$linkOptions = $this->linkOptions;
$linkOptions['data-page'] = $page;
return Html::tag($linkWrapTag, Html::a($label, $this->pagination->createUrl($page), $linkOptions), $options);
}
/**
* @return array the begin and end pages that need to be displayed.
*/
protected function getPageRange()
{
$currentPage = $this->pagination->getPage();
$pageCount = $this->pagination->getPageCount();
$beginPage = max(0, $currentPage - (int) ($this->maxButtonCount / 2));
if (($endPage = $beginPage + $this->maxButtonCount - 1) >= $pageCount) {
$endPage = $pageCount - 1;
$beginPage = max(0, $endPage - $this->maxButtonCount + 1);
}
return [$beginPage, $endPage];
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\Widget;
use yii\data\Sort;
use yii\helpers\Html;
/**
* LinkSorter renders a list of sort links for the given sort definition.
*
* LinkSorter will generate a hyperlink for every attribute declared in [[sort]].
*
* For more details and usage information on LinkSorter, see the [guide article on sorting](guide:output-sorting).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class LinkSorter extends Widget
{
/**
* @var Sort the sort definition
*/
public $sort;
/**
* @var array list of the attributes that support sorting. If not set, it will be determined
* using [[Sort::attributes]].
*/
public $attributes;
/**
* @var array HTML attributes for the sorter container tag.
* @see \yii\helpers\Html::ul() for special attributes.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = ['class' => 'sorter'];
/**
* @var array HTML attributes for the link in a sorter container tag which are passed to [[Sort::link()]].
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
* @since 2.0.6
*/
public $linkOptions = [];
/**
* Initializes the sorter.
*/
public function init()
{
parent::init();
if ($this->sort === null) {
throw new InvalidConfigException('The "sort" property must be set.');
}
}
/**
* Executes the widget.
* This method renders the sort links.
*/
public function run()
{
echo $this->renderSortLinks();
}
/**
* Renders the sort links.
* @return string the rendering result
*/
protected function renderSortLinks()
{
$attributes = empty($this->attributes) ? array_keys($this->sort->attributes) : $this->attributes;
$links = [];
foreach ($attributes as $name) {
$links[] = $this->sort->link($name, $this->linkOptions);
}
return Html::ul($links, array_merge($this->options, ['encode' => false]));
}
}

205
vendor/yiisoft/yii2/widgets/ListView.php vendored Normal file
View File

@@ -0,0 +1,205 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Closure;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
/**
* The ListView widget is used to display data from data
* provider. Each data model is rendered using the view
* specified.
*
* For more details and usage information on ListView, see the [guide article on data widgets](guide:output-data-widgets).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ListView extends BaseListView
{
/**
* @var array|Closure the HTML attributes for the container of the rendering result of each data model.
* This can be either an array specifying the common HTML attributes for rendering each data item,
* or an anonymous function that returns an array of the HTML attributes. The anonymous function will be
* called once for every data model returned by [[dataProvider]].
* The "tag" element specifies the tag name of the container element and defaults to "div".
* If "tag" is false, it means no container element will be rendered.
*
* If this property is specified as an anonymous function, it should have the following signature:
*
* ```php
* function ($model, $key, $index, $widget)
* ```
*
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $itemOptions = [];
/**
* @var string|callable the name of the view for rendering each data item, or a callback (e.g. an anonymous function)
* for rendering each data item. If it specifies a view name, the following variables will
* be available in the view:
*
* - `$model`: mixed, the data model
* - `$key`: mixed, the key value associated with the data item
* - `$index`: integer, the zero-based index of the data item in the items array returned by [[dataProvider]].
* - `$widget`: ListView, this widget instance
*
* Note that the view name is resolved into the view file by the current context of the [[view]] object.
*
* If this property is specified as a callback, it should have the following signature:
*
* ```php
* function ($model, $key, $index, $widget)
* ```
*/
public $itemView;
/**
* @var array additional parameters to be passed to [[itemView]] when it is being rendered.
* This property is used only when [[itemView]] is a string representing a view name.
*/
public $viewParams = [];
/**
* @var string the HTML code to be displayed between any two consecutive items.
*/
public $separator = "\n";
/**
* @var array the HTML attributes for the container tag of the list view.
* The "tag" element specifies the tag name of the container element and defaults to "div".
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = ['class' => 'list-view'];
/**
* @var Closure an anonymous function that is called once BEFORE rendering each data model.
* It should have the following signature:
*
* ```php
* function ($model, $key, $index, $widget)
* ```
*
* - `$model`: the current data model being rendered
* - `$key`: the key value associated with the current data model
* - `$index`: the zero-based index of the data model in the model array returned by [[dataProvider]]
* - `$widget`: the ListView object
*
* The return result of the function will be rendered directly.
* Note: If the function returns `null`, nothing will be rendered before the item.
* @see renderBeforeItem
* @since 2.0.11
*/
public $beforeItem;
/**
* @var Closure an anonymous function that is called once AFTER rendering each data model.
*
* It should have the same signature as [[beforeItem]].
*
* The return result of the function will be rendered directly.
* Note: If the function returns `null`, nothing will be rendered after the item.
* @see renderAfterItem
* @since 2.0.11
*/
public $afterItem;
/**
* Renders all data models.
* @return string the rendering result
*/
public function renderItems()
{
$models = $this->dataProvider->getModels();
$keys = $this->dataProvider->getKeys();
$rows = [];
foreach (array_values($models) as $index => $model) {
$key = $keys[$index];
if (($before = $this->renderBeforeItem($model, $key, $index)) !== null) {
$rows[] = $before;
}
$rows[] = $this->renderItem($model, $key, $index);
if (($after = $this->renderAfterItem($model, $key, $index)) !== null) {
$rows[] = $after;
}
}
return implode($this->separator, $rows);
}
/**
* Calls [[beforeItem]] closure, returns execution result.
* If [[beforeItem]] is not a closure, `null` will be returned.
*
* @param mixed $model the data model to be rendered
* @param mixed $key the key value associated with the data model
* @param int $index the zero-based index of the data model in the model array returned by [[dataProvider]].
* @return string|null [[beforeItem]] call result or `null` when [[beforeItem]] is not a closure
* @see beforeItem
* @since 2.0.11
*/
protected function renderBeforeItem($model, $key, $index)
{
if ($this->beforeItem instanceof Closure) {
return call_user_func($this->beforeItem, $model, $key, $index, $this);
}
return null;
}
/**
* Calls [[afterItem]] closure, returns execution result.
* If [[afterItem]] is not a closure, `null` will be returned.
*
* @param mixed $model the data model to be rendered
* @param mixed $key the key value associated with the data model
* @param int $index the zero-based index of the data model in the model array returned by [[dataProvider]].
* @return string|null [[afterItem]] call result or `null` when [[afterItem]] is not a closure
* @see afterItem
* @since 2.0.11
*/
protected function renderAfterItem($model, $key, $index)
{
if ($this->afterItem instanceof Closure) {
return call_user_func($this->afterItem, $model, $key, $index, $this);
}
return null;
}
/**
* Renders a single data model.
* @param mixed $model the data model to be rendered
* @param mixed $key the key value associated with the data model
* @param int $index the zero-based index of the data model in the model array returned by [[dataProvider]].
* @return string the rendering result
*/
public function renderItem($model, $key, $index)
{
if ($this->itemView === null) {
$content = $key;
} elseif (is_string($this->itemView)) {
$content = $this->getView()->render($this->itemView, array_merge([
'model' => $model,
'key' => $key,
'index' => $index,
'widget' => $this,
], $this->viewParams));
} else {
$content = call_user_func($this->itemView, $model, $key, $index, $this);
}
if ($this->itemOptions instanceof Closure) {
$options = call_user_func($this->itemOptions, $model, $key, $index, $this);
} else {
$options = $this->itemOptions;
}
$tag = ArrayHelper::remove($options, 'tag', 'div');
$options['data-key'] = is_array($key) ? json_encode($key, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : (string) $key;
return Html::tag($tag, $content, $options);
}
}

View File

@@ -0,0 +1,193 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\web\JsExpression;
use yii\web\View;
/**
* MaskedInput generates a masked text input.
*
* MaskedInput is similar to [[Html::textInput()]] except that an input mask will be used to force users to enter
* properly formatted data, such as phone numbers, social security numbers.
*
* To use MaskedInput, you must set the [[mask]] property. The following example
* shows how to use MaskedInput to collect phone numbers:
*
* ```php
* echo MaskedInput::widget([
* 'name' => 'phone',
* 'mask' => '999-999-9999',
* ]);
* ```
*
* You can also use this widget in an [[ActiveForm]] using the [[ActiveField::widget()|widget()]]
* method, for example like this:
*
* ```php
* <?= $form->field($model, 'from_date')->widget(\yii\widgets\MaskedInput::className(), [
* 'mask' => '999-999-9999',
* ]) ?>
* ```
*
* The masked text field is implemented based on the
* [jQuery input masked plugin](https://github.com/RobinHerbots/Inputmask).
*
* @author Kartik Visweswaran <kartikv2@gmail.com>
* @since 2.0
*/
class MaskedInput extends InputWidget
{
/**
* The name of the jQuery plugin to use for this widget.
*/
const PLUGIN_NAME = 'inputmask';
/**
* @var string|array|JsExpression the input mask (e.g. '99/99/9999' for date input). The following characters
* can be used in the mask and are predefined:
*
* - `a`: represents an alpha character (A-Z, a-z)
* - `9`: represents a numeric character (0-9)
* - `*`: represents an alphanumeric character (A-Z, a-z, 0-9)
* - `[` and `]`: anything entered between the square brackets is considered optional user input. This is
* based on the `optionalmarker` setting in [[clientOptions]].
*
* Additional definitions can be set through the [[definitions]] property.
*/
public $mask;
/**
* @var array custom mask definitions to use. Should be configured as `maskSymbol => settings`, where
*
* - `maskSymbol` is a string, containing a character to identify your mask definition and
* - `settings` is an array, consisting of the following entries:
* - `validator`: string, a JS regular expression or a JS function.
* - `cardinality`: int, specifies how many characters are represented and validated for the definition.
* - `prevalidator`: array, validate the characters before the definition cardinality is reached.
* - `definitionSymbol`: string, allows shifting values from other definitions, with this `definitionSymbol`.
*/
public $definitions;
/**
* @var array custom aliases to use. Should be configured as `maskAlias => settings`, where
*
* - `maskAlias` is a string containing a text to identify your mask alias definition (e.g. 'phone') and
* - `settings` is an array containing settings for the mask symbol, exactly similar to parameters as passed in [[clientOptions]].
*/
public $aliases;
/**
* @var array the JQuery plugin options for the input mask plugin.
* @see https://github.com/RobinHerbots/Inputmask
*/
public $clientOptions = [];
/**
* @var array the HTML attributes for the input tag.
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = ['class' => 'form-control'];
/**
* @var string the type of the input tag. Currently only 'text' and 'tel' are supported.
* @see https://github.com/RobinHerbots/Inputmask
* @since 2.0.6
*/
public $type = 'text';
/**
* @var string the hashed variable to store the pluginOptions
*/
protected $_hashVar;
/**
* Initializes the widget.
*
* @throws InvalidConfigException if the "mask" property is not set.
*/
public function init()
{
parent::init();
if (empty($this->mask) && empty($this->clientOptions['alias'])) {
throw new InvalidConfigException("Either the 'mask' property or the 'clientOptions[\"alias\"]' property must be set.");
}
}
/**
* {@inheritdoc}
*/
public function run()
{
$this->registerClientScript();
echo $this->renderInputHtml($this->type);
}
/**
* Generates a hashed variable to store the plugin `clientOptions`.
*
* Helps in reusing the variable for similar
* options passed for other widgets on the same page. The following special data attribute will also be
* added to the input field to allow accessing the client options via javascript:
*
* - 'data-plugin-inputmask' will store the hashed variable storing the plugin options.
*
* @param View $view the view instance
* @author [Thiago Talma](https://github.com/thiagotalma)
*/
protected function hashPluginOptions($view)
{
$encOptions = empty($this->clientOptions) ? '{}' : Json::htmlEncode($this->clientOptions);
$this->_hashVar = self::PLUGIN_NAME . '_' . hash('crc32', $encOptions);
$this->options['data-plugin-' . self::PLUGIN_NAME] = $this->_hashVar;
$view->registerJs("var {$this->_hashVar} = {$encOptions};", View::POS_HEAD);
}
/**
* Initializes client options.
*/
protected function initClientOptions()
{
$options = $this->clientOptions;
foreach ($options as $key => $value) {
if (
!$value instanceof JsExpression
&& in_array($key, [
'oncomplete', 'onincomplete', 'oncleared', 'onKeyUp', 'onKeyDown', 'onBeforeMask',
'onBeforePaste', 'onUnMask', 'isComplete', 'determineActiveMasksetIndex',
], true)
) {
$options[$key] = new JsExpression($value);
}
}
$this->clientOptions = $options;
}
/**
* Registers the needed client script and options.
*/
public function registerClientScript()
{
$js = '';
$view = $this->getView();
$this->initClientOptions();
if (!empty($this->mask)) {
$this->clientOptions['mask'] = $this->mask;
}
$this->hashPluginOptions($view);
if (is_array($this->definitions) && !empty($this->definitions)) {
$js .= ucfirst(self::PLUGIN_NAME) . '.extendDefinitions(' . Json::htmlEncode($this->definitions) . ');';
}
if (is_array($this->aliases) && !empty($this->aliases)) {
$js .= ucfirst(self::PLUGIN_NAME) . '.extendAliases(' . Json::htmlEncode($this->aliases) . ');';
}
$id = $this->options['id'];
$js .= 'jQuery("#' . $id . '").' . self::PLUGIN_NAME . '(' . $this->_hashVar . ');';
MaskedInputAsset::register($view);
$view->registerJs($js);
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use yii\web\AssetBundle;
/**
* The asset bundle for the [[MaskedInput]] widget.
*
* Includes client assets of [jQuery input mask plugin](https://github.com/RobinHerbots/Inputmask).
*
* @author Kartik Visweswaran <kartikv2@gmail.com>
* @since 2.0
*/
class MaskedInputAsset extends AssetBundle
{
public $sourcePath = '@bower/inputmask/dist';
public $js = [
'jquery.inputmask.bundle.js',
];
public $depends = [
'yii\web\YiiAsset',
];
}

331
vendor/yiisoft/yii2/widgets/Menu.php vendored Normal file
View File

@@ -0,0 +1,331 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Closure;
use Yii;
use yii\base\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\helpers\Url;
/**
* Menu displays a multi-level menu using nested HTML lists.
*
* The main property of Menu is [[items]], which specifies the possible items in the menu.
* A menu item can contain sub-items which specify the sub-menu under that menu item.
*
* Menu checks the current route and request parameters to toggle certain menu items
* with active state.
*
* Note that Menu only renders the HTML tags about the menu. It does do any styling.
* You are responsible to provide CSS styles to make it look like a real menu.
*
* The following example shows how to use Menu:
*
* ```php
* echo Menu::widget([
* 'items' => [
* // Important: you need to specify url as 'controller/action',
* // not just as 'controller' even if default action is used.
* ['label' => 'Home', 'url' => ['site/index']],
* // 'Products' menu item will be selected as long as the route is 'product/index'
* ['label' => 'Products', 'url' => ['product/index'], 'items' => [
* ['label' => 'New Arrivals', 'url' => ['product/index', 'tag' => 'new']],
* ['label' => 'Most Popular', 'url' => ['product/index', 'tag' => 'popular']],
* ]],
* ['label' => 'Login', 'url' => ['site/login'], 'visible' => Yii::$app->user->isGuest],
* ],
* ]);
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Menu extends Widget
{
/**
* @var array list of menu items. Each menu item should be an array of the following structure:
*
* - label: string, optional, specifies the menu item label. When [[encodeLabels]] is true, the label
* will be HTML-encoded. If the label is not specified, an empty string will be used.
* - encode: boolean, optional, whether this item`s label should be HTML-encoded. This param will override
* global [[encodeLabels]] param.
* - url: string or array, optional, specifies the URL of the menu item. It will be processed by [[Url::to]].
* When this is set, the actual menu item content will be generated using [[linkTemplate]];
* otherwise, [[labelTemplate]] will be used.
* - visible: boolean, optional, whether this menu item is visible. Defaults to true.
* - items: array, optional, specifies the sub-menu items. Its format is the same as the parent items.
* - active: boolean or Closure, optional, whether this menu item is in active state (currently selected).
* When using a closure, its signature should be `function ($item, $hasActiveChild, $isItemActive, $widget)`.
* Closure must return `true` if item should be marked as `active`, otherwise - `false`.
* If a menu item is active, its CSS class will be appended with [[activeCssClass]].
* If this option is not set, the menu item will be set active automatically when the current request
* is triggered by `url`. For more details, please refer to [[isItemActive()]].
* - template: string, optional, the template used to render the content of this menu item.
* The token `{url}` will be replaced by the URL associated with this menu item,
* and the token `{label}` will be replaced by the label of the menu item.
* If this option is not set, [[linkTemplate]] or [[labelTemplate]] will be used instead.
* - submenuTemplate: string, optional, the template used to render the list of sub-menus.
* The token `{items}` will be replaced with the rendered sub-menu items.
* If this option is not set, [[submenuTemplate]] will be used instead.
* - options: array, optional, the HTML attributes for the menu container tag.
*/
public $items = [];
/**
* @var array list of HTML attributes shared by all menu [[items]]. If any individual menu item
* specifies its `options`, it will be merged with this property before being used to generate the HTML
* attributes for the menu item tag. The following special options are recognized:
*
* - tag: string, defaults to "li", the tag name of the item container tags.
* Set to false to disable container tag.
* See also [[\yii\helpers\Html::tag()]].
*
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $itemOptions = [];
/**
* @var string the template used to render the body of a menu which is a link.
* In this template, the token `{url}` will be replaced with the corresponding link URL;
* while `{label}` will be replaced with the link text.
* This property will be overridden by the `template` option set in individual menu items via [[items]].
*/
public $linkTemplate = '<a href="{url}">{label}</a>';
/**
* @var string the template used to render the body of a menu which is NOT a link.
* In this template, the token `{label}` will be replaced with the label of the menu item.
* This property will be overridden by the `template` option set in individual menu items via [[items]].
*/
public $labelTemplate = '{label}';
/**
* @var string the template used to render a list of sub-menus.
* In this template, the token `{items}` will be replaced with the rendered sub-menu items.
*/
public $submenuTemplate = "\n<ul>\n{items}\n</ul>\n";
/**
* @var bool whether the labels for menu items should be HTML-encoded.
*/
public $encodeLabels = true;
/**
* @var string the CSS class to be appended to the active menu item.
*/
public $activeCssClass = 'active';
/**
* @var bool whether to automatically activate items according to whether their route setting
* matches the currently requested route.
* @see isItemActive()
*/
public $activateItems = true;
/**
* @var bool whether to activate parent menu items when one of the corresponding child menu items is active.
* The activated parent menu items will also have its CSS classes appended with [[activeCssClass]].
*/
public $activateParents = false;
/**
* @var bool whether to hide empty menu items. An empty menu item is one whose `url` option is not
* set and which has no visible child menu items.
*/
public $hideEmptyItems = true;
/**
* @var array the HTML attributes for the menu's container tag. The following special options are recognized:
*
* - tag: string, defaults to "ul", the tag name of the item container tags. Set to false to disable container tag.
* See also [[\yii\helpers\Html::tag()]].
*
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = [];
/**
* @var string the CSS class that will be assigned to the first item in the main menu or each submenu.
* Defaults to null, meaning no such CSS class will be assigned.
*/
public $firstItemCssClass;
/**
* @var string the CSS class that will be assigned to the last item in the main menu or each submenu.
* Defaults to null, meaning no such CSS class will be assigned.
*/
public $lastItemCssClass;
/**
* @var string the route used to determine if a menu item is active or not.
* If not set, it will use the route of the current request.
* @see params
* @see isItemActive()
*/
public $route;
/**
* @var array the parameters used to determine if a menu item is active or not.
* If not set, it will use `$_GET`.
* @see route
* @see isItemActive()
*/
public $params;
/**
* Renders the menu.
*/
public function run()
{
if ($this->route === null && Yii::$app->controller !== null) {
$this->route = Yii::$app->controller->getRoute();
}
if ($this->params === null) {
$this->params = Yii::$app->request->getQueryParams();
}
$items = $this->normalizeItems($this->items, $hasActiveChild);
if (!empty($items)) {
$options = $this->options;
$tag = ArrayHelper::remove($options, 'tag', 'ul');
echo Html::tag($tag, $this->renderItems($items), $options);
}
}
/**
* Recursively renders the menu items (without the container tag).
* @param array $items the menu items to be rendered recursively
* @return string the rendering result
*/
protected function renderItems($items)
{
$n = count($items);
$lines = [];
foreach ($items as $i => $item) {
$options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', []));
$tag = ArrayHelper::remove($options, 'tag', 'li');
$class = [];
if ($item['active']) {
$class[] = $this->activeCssClass;
}
if ($i === 0 && $this->firstItemCssClass !== null) {
$class[] = $this->firstItemCssClass;
}
if ($i === $n - 1 && $this->lastItemCssClass !== null) {
$class[] = $this->lastItemCssClass;
}
Html::addCssClass($options, $class);
$menu = $this->renderItem($item);
if (!empty($item['items'])) {
$submenuTemplate = ArrayHelper::getValue($item, 'submenuTemplate', $this->submenuTemplate);
$menu .= strtr($submenuTemplate, [
'{items}' => $this->renderItems($item['items']),
]);
}
$lines[] = Html::tag($tag, $menu, $options);
}
return implode("\n", $lines);
}
/**
* Renders the content of a menu item.
* Note that the container and the sub-menus are not rendered here.
* @param array $item the menu item to be rendered. Please refer to [[items]] to see what data might be in the item.
* @return string the rendering result
*/
protected function renderItem($item)
{
if (isset($item['url'])) {
$template = ArrayHelper::getValue($item, 'template', $this->linkTemplate);
return strtr($template, [
'{url}' => Html::encode(Url::to($item['url'])),
'{label}' => $item['label'],
]);
}
$template = ArrayHelper::getValue($item, 'template', $this->labelTemplate);
return strtr($template, [
'{label}' => $item['label'],
]);
}
/**
* Normalizes the [[items]] property to remove invisible items and activate certain items.
* @param array $items the items to be normalized.
* @param bool $active whether there is an active child menu item.
* @return array the normalized menu items
*/
protected function normalizeItems($items, &$active)
{
foreach ($items as $i => $item) {
if (isset($item['visible']) && !$item['visible']) {
unset($items[$i]);
continue;
}
if (!isset($item['label'])) {
$item['label'] = '';
}
$encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels;
$items[$i]['label'] = $encodeLabel ? Html::encode($item['label']) : $item['label'];
$hasActiveChild = false;
if (isset($item['items'])) {
$items[$i]['items'] = $this->normalizeItems($item['items'], $hasActiveChild);
if (empty($items[$i]['items']) && $this->hideEmptyItems) {
unset($items[$i]['items']);
if (!isset($item['url'])) {
unset($items[$i]);
continue;
}
}
}
if (!isset($item['active'])) {
if ($this->activateParents && $hasActiveChild || $this->activateItems && $this->isItemActive($item)) {
$active = $items[$i]['active'] = true;
} else {
$items[$i]['active'] = false;
}
} elseif ($item['active'] instanceof Closure) {
$active = $items[$i]['active'] = call_user_func($item['active'], $item, $hasActiveChild, $this->isItemActive($item), $this);
} elseif ($item['active']) {
$active = true;
}
}
return array_values($items);
}
/**
* Checks whether a menu item is active.
* This is done by checking if [[route]] and [[params]] match that specified in the `url` option of the menu item.
* When the `url` option of a menu item is specified in terms of an array, its first element is treated
* as the route for the item and the rest of the elements are the associated parameters.
* Only when its route and parameters match [[route]] and [[params]], respectively, will a menu item
* be considered active.
* @param array $item the menu item to be checked
* @return bool whether the menu item is active
*/
protected function isItemActive($item)
{
if (isset($item['url']) && is_array($item['url']) && isset($item['url'][0])) {
$route = Yii::getAlias($item['url'][0]);
if ($route[0] !== '/' && Yii::$app->controller) {
$route = Yii::$app->controller->module->getUniqueId() . '/' . $route;
}
if (ltrim($route, '/') !== $this->route) {
return false;
}
unset($item['url']['#']);
if (count($item['url']) > 1) {
$params = $item['url'];
unset($params[0]);
foreach ($params as $name => $value) {
if ($value !== null && (!isset($this->params[$name]) || $this->params[$name] != $value)) {
return false;
}
}
}
return true;
}
return false;
}
}

217
vendor/yiisoft/yii2/widgets/Pjax.php vendored Normal file
View File

@@ -0,0 +1,217 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use Yii;
use yii\base\Widget;
use yii\helpers\ArrayHelper;
use yii\helpers\Html;
use yii\helpers\Json;
use yii\web\Response;
/**
* Pjax is a widget integrating the [pjax](https://github.com/yiisoft/jquery-pjax) jQuery plugin.
*
* Pjax only deals with the content enclosed between its [[begin()]] and [[end()]] calls, called the *body content* of the widget.
* By default, any link click or form submission (for those forms with `data-pjax` attribute) within the body content
* will trigger an AJAX request. In responding to the AJAX request, Pjax will send the updated body content (based
* on the AJAX request) to the client which will replace the old content with the new one. The browser's URL will then
* be updated using pushState. The whole process requires no reloading of the layout or resources (js, css).
*
* You may configure [[linkSelector]] to specify which links should trigger pjax, and configure [[formSelector]]
* to specify which form submission may trigger pjax.
*
* You may disable pjax for a specific link inside the container by adding `data-pjax="0"` attribute to this link.
*
* The following example shows how to use Pjax with the [[\yii\grid\GridView]] widget so that the grid pagination,
* sorting and filtering can be done via pjax:
*
* ```php
* use yii\widgets\Pjax;
*
* Pjax::begin();
* echo GridView::widget([...]);
* Pjax::end();
* ```
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Pjax extends Widget
{
/**
* @var array the HTML attributes for the widget container tag. The following special options are recognized:
*
* - `tag`: string, the tag name for the container. Defaults to `div`
* This option is available since version 2.0.7.
* See also [[\yii\helpers\Html::tag()]].
*
* @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
*/
public $options = [];
/**
* @var string|false the jQuery selector of the links that should trigger pjax requests.
* If not set, all links within the enclosed content of Pjax will trigger pjax requests.
* If set to false, no code will be registered to handle links.
* Note that if the response to the pjax request is a full page, a normal request will be sent again.
*/
public $linkSelector;
/**
* @var string|false the jQuery selector of the forms whose submissions should trigger pjax requests.
* If not set, all forms with `data-pjax` attribute within the enclosed content of Pjax will trigger pjax requests.
* If set to false, no code will be registered to handle forms.
* Note that if the response to the pjax request is a full page, a normal request will be sent again.
*/
public $formSelector;
/**
* @var string The jQuery event that will trigger form handler. Defaults to "submit".
* @since 2.0.9
*/
public $submitEvent = 'submit';
/**
* @var bool whether to enable push state.
*/
public $enablePushState = true;
/**
* @var bool whether to enable replace state.
*/
public $enableReplaceState = false;
/**
* @var int pjax timeout setting (in milliseconds). This timeout is used when making AJAX requests.
* Use a bigger number if your server is slow. If the server does not respond within the timeout,
* a full page load will be triggered.
*/
public $timeout = 1000;
/**
* @var bool|int how to scroll the page when pjax response is received. If false, no page scroll will be made.
* Use a number if you want to scroll to a particular place.
*/
public $scrollTo = false;
/**
* @var array additional options to be passed to the pjax JS plugin. Please refer to the
* [pjax project page](https://github.com/yiisoft/jquery-pjax) for available options.
*/
public $clientOptions;
/**
* {@inheritdoc}
* @internal
*/
public static $counter = 0;
/**
* {@inheritdoc}
*/
public static $autoIdPrefix = 'p';
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if (!isset($this->options['id'])) {
$this->options['id'] = $this->getId();
}
if ($this->requiresPjax()) {
ob_start();
ob_implicit_flush(false);
$view = $this->getView();
$view->clear();
$view->beginPage();
$view->head();
$view->beginBody();
if ($view->title !== null) {
echo Html::tag('title', Html::encode($view->title));
}
} else {
$options = $this->options;
$tag = ArrayHelper::remove($options, 'tag', 'div');
echo Html::beginTag($tag, array_merge([
'data-pjax-container' => '',
'data-pjax-push-state' => $this->enablePushState,
'data-pjax-replace-state' => $this->enableReplaceState,
'data-pjax-timeout' => $this->timeout,
'data-pjax-scrollto' => $this->scrollTo,
], $options));
}
}
/**
* {@inheritdoc}
*/
public function run()
{
if (!$this->requiresPjax()) {
echo Html::endTag(ArrayHelper::remove($this->options, 'tag', 'div'));
$this->registerClientScript();
return;
}
$view = $this->getView();
$view->endBody();
$view->endPage(true);
$content = ob_get_clean();
// only need the content enclosed within this widget
$response = Yii::$app->getResponse();
$response->clearOutputBuffers();
$response->setStatusCode(200);
$response->format = Response::FORMAT_HTML;
$response->content = $content;
$response->headers->setDefault('X-Pjax-Url', Yii::$app->request->url);
$response->send();
Yii::$app->end();
}
/**
* @return bool whether the current request requires pjax response from this widget
*/
protected function requiresPjax()
{
$headers = Yii::$app->getRequest()->getHeaders();
return $headers->get('X-Pjax') && explode(' ', $headers->get('X-Pjax-Container'))[0] === '#' . $this->options['id'];
}
/**
* Registers the needed JavaScript.
*/
public function registerClientScript()
{
$id = $this->options['id'];
$this->clientOptions['push'] = $this->enablePushState;
$this->clientOptions['replace'] = $this->enableReplaceState;
$this->clientOptions['timeout'] = $this->timeout;
$this->clientOptions['scrollTo'] = $this->scrollTo;
if (!isset($this->clientOptions['container'])) {
$this->clientOptions['container'] = "#$id";
}
$options = Json::htmlEncode($this->clientOptions);
$js = '';
if ($this->linkSelector !== false) {
$linkSelector = Json::htmlEncode($this->linkSelector !== null ? $this->linkSelector : '#' . $id . ' a');
$js .= "jQuery(document).pjax($linkSelector, $options);";
}
if ($this->formSelector !== false) {
$formSelector = Json::htmlEncode($this->formSelector !== null ? $this->formSelector : '#' . $id . ' form[data-pjax]');
$submitEvent = Json::htmlEncode($this->submitEvent);
$js .= "\njQuery(document).on($submitEvent, $formSelector, function (event) {jQuery.pjax.submit(event, $options);});";
}
$view = $this->getView();
PjaxAsset::register($view);
if ($js !== '') {
$view->registerJs($js);
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use yii\web\AssetBundle;
/**
* This asset bundle provides the javascript files required by [[Pjax]] widget.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class PjaxAsset extends AssetBundle
{
public $sourcePath = '@bower/yii2-pjax';
public $js = [
'jquery.pjax.js',
];
public $depends = [
'yii\web\YiiAsset',
];
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\widgets;
use yii\base\Widget;
/**
* Spaceless widget removes whitespace characters between HTML tags. Whitespaces within HTML tags
* or in a plain text are always left untouched.
*
* Usage example:
*
* ```php
* <body>
* <?php Spaceless::begin(); ?>
* <div class="nav-bar">
* <!-- tags -->
* </div>
* <div class="content">
* <!-- tags -->
* </div>
* <?php Spaceless::end(); ?>
* </body>
* ```
*
* This example will generate the following HTML:
*
* ```html
* <body>
* <div class="nav-bar"><!-- tags --></div><div class="content"><!-- tags --></div></body>
* ```
*
* This method is not designed for content compression (you should use `gzip` output compression to
* achieve it). Main intention is to strip out extra whitespace characters between HTML tags in order
* to avoid browser rendering quirks in some circumstances (e.g. newlines between inline-block elements).
*
* Note, never use this method with `pre` or `textarea` tags. It's not that trivial to deal with such tags
* as it may seem at first sight. For this case you should consider using
* [HTML Tidy Project](http://tidy.sourceforge.net/) instead.
*
* @see http://tidy.sourceforge.net/
* @author resurtm <resurtm@gmail.com>
* @since 2.0
*/
class Spaceless extends Widget
{
/**
* Starts capturing an output to be cleaned from whitespace characters between HTML tags.
*/
public function init()
{
parent::init();
ob_start();
ob_implicit_flush(false);
}
/**
* Marks the end of content to be cleaned from whitespace characters between HTML tags.
* Stops capturing an output and echoes cleaned result.
*/
public function run()
{
echo trim(preg_replace('/>\s+</', '><', ob_get_clean()));
}
}