diff --git a/framework/base/Application.php b/framework/base/Application.php index 3c1a1fb..a60bf90 100644 --- a/framework/base/Application.php +++ b/framework/base/Application.php @@ -382,6 +382,15 @@ class Application extends Module } /** + * Returns the URL manager for this application. + * @return \yii\web\UrlManager the URL manager for this application. + */ + public function getUrlManager() + { + return $this->getComponent('urlManager'); + } + + /** * Returns the internationalization (i18n) component * @return \yii\i18n\I18N the internationalization component */ @@ -411,8 +420,8 @@ class Application extends Module 'i18n' => array( 'class' => 'yii\i18n\I18N', ), - 'securityManager' => array( - 'class' => 'yii\base\SecurityManager', + 'urlManager' => array( + 'class' => 'yii\web\UrlManager', ), )); } diff --git a/framework/db/ActiveRelation.php b/framework/db/ActiveRelation.php index e1793bd..f1b198b 100644 --- a/framework/db/ActiveRelation.php +++ b/framework/db/ActiveRelation.php @@ -55,16 +55,16 @@ class ActiveRelation extends ActiveQuery /** * Specifies the relation associated with the pivot table. * @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]]. - * @param callback $callback a PHP callback for customizing the relation associated with the pivot table. + * @param callable $callable a PHP callback for customizing the relation associated with the pivot table. * Its signature should be `function($query)`, where `$query` is the query to be customized. * @return ActiveRelation the relation object itself. */ - public function via($relationName, $callback = null) + public function via($relationName, $callable = null) { $relation = $this->primaryModel->getRelation($relationName); $this->via = array($relationName, $relation); - if ($callback !== null) { - call_user_func($callback, $relation); + if ($callable !== null) { + call_user_func($callable, $relation); } return $this; } @@ -75,11 +75,11 @@ class ActiveRelation extends ActiveQuery * @param array $link the link between the pivot table and the table associated with [[primaryModel]]. * The keys of the array represent the columns in the pivot table, and the values represent the columns * in the [[primaryModel]] table. - * @param callback $callback a PHP callback for customizing the relation associated with the pivot table. + * @param callable $callable a PHP callback for customizing the relation associated with the pivot table. * Its signature should be `function($query)`, where `$query` is the query to be customized. * @return ActiveRelation */ - public function viaTable($tableName, $link, $callback = null) + public function viaTable($tableName, $link, $callable = null) { $relation = new ActiveRelation(array( 'modelClass' => get_class($this->primaryModel), @@ -89,8 +89,8 @@ class ActiveRelation extends ActiveQuery 'asArray' => true, )); $this->via = $relation; - if ($callback !== null) { - call_user_func($callback, $relation); + if ($callable !== null) { + call_user_func($callable, $relation); } return $this; } diff --git a/framework/util/ArrayHelper.php b/framework/util/ArrayHelper.php index fdcd691..ebf4b23 100644 --- a/framework/util/ArrayHelper.php +++ b/framework/util/ArrayHelper.php @@ -281,33 +281,59 @@ class ArrayHelper call_user_func_array('array_multisort', $args); } - /** * Encodes special characters in an array of strings into HTML entities. - * Both the array keys and values will be encoded if needed. + * Both the array keys and values will be encoded. * If a value is an array, this method will also encode it recursively. * @param array $data data to be encoded + * @param boolean $valuesOnly whether to encode array values only. If false, + * both the array keys and array values will be encoded. * @param string $charset the charset that the data is using. If not set, * [[\yii\base\Application::charset]] will be used. * @return array the encoded data * @see http://www.php.net/manual/en/function.htmlspecialchars.php */ - public static function htmlEncode($data, $charset = null) + public static function htmlEncode($data, $valuesOnly = false, $charset = null) { if ($charset === null) { $charset = Yii::$app->charset; } $d = array(); foreach ($data as $key => $value) { - if (is_string($key)) { + if (!$valuesOnly && is_string($key)) { $key = htmlspecialchars($key, ENT_QUOTES, $charset); } if (is_string($value)) { - $value = htmlspecialchars($value, ENT_QUOTES, $charset); + $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset); + } elseif (is_array($value)) { + $d[$key] = static::htmlEncode($value, $charset); + } + } + return $d; + } + + /** + * Decodes HTML entities into the corresponding characters in an array of strings. + * Both the array keys and values will be decoded. + * If a value is an array, this method will also decode it recursively. + * @param array $data data to be decoded + * @param boolean $valuesOnly whether to decode array values only. If false, + * both the array keys and array values will be decoded. + * @return array the decoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + */ + public static function htmlDecode($data, $valuesOnly = false) + { + $d = array(); + foreach ($data as $key => $value) { + if (!$valuesOnly && is_string($key)) { + $key = htmlspecialchars_decode($key, ENT_QUOTES); + } + if (is_string($value)) { + $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES); } elseif (is_array($value)) { - $value = static::htmlEncode($value); + $d[$key] = static::htmlDecode($value); } - $d[$key] = $value; } return $d; } diff --git a/framework/util/Html.php b/framework/util/Html.php index 3a3ee9c..774a733 100644 --- a/framework/util/Html.php +++ b/framework/util/Html.php @@ -8,8 +8,11 @@ namespace yii\util; use Yii; +use yii\base\InvalidParamException; /** + * Html provides a set of static methods for generating commonly used HTML tags. + * * @author Qiang Xue * @since 2.0 */ @@ -22,6 +25,7 @@ class Html public static $closeVoidElements = true; /** * @var array list of void elements (element name => 1) + * @see closeVoidElements * @see http://www.w3.org/TR/html-markup/syntax.html#void-element */ public static $voidElements = array( @@ -43,9 +47,78 @@ class Html 'wbr' => 1, ); /** - * @var boolean whether to render special attributes value. Defaults to true. Can be set to false for HTML5. - */ - public static $renderSpecialAttributesValue = true; + * @var boolean whether to show the values of boolean attributes in element tags. + * If false, only the attribute names will be generated. + * @see booleanAttributes + */ + public static $showBooleanAttributeValues = true; + /** + * @var array list of boolean attributes. The presence of a boolean attribute on + * an element represents the true value, and the absence of the attribute represents the false value. + * @see showBooleanAttributeValues + * @see http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes + */ + public static $booleanAttributes = array( + 'async' => 1, + 'autofocus' => 1, + 'autoplay' => 1, + 'checked' => 1, + 'controls' => 1, + 'declare' => 1, + 'default' => 1, + 'defer' => 1, + 'disabled' => 1, + 'formnovalidate' => 1, + 'hidden' => 1, + 'ismap' => 1, + 'loop' => 1, + 'multiple' => 1, + 'muted' => 1, + 'nohref' => 1, + 'noresize' => 1, + 'novalidate' => 1, + 'open' => 1, + 'readonly' => 1, + 'required' => 1, + 'reversed' => 1, + 'scoped' => 1, + 'seamless' => 1, + 'selected' => 1, + 'typemustmatch' => 1, + ); + /** + * @var array the preferred order of attributes in a tag. This mainly affects the order of the attributes + * that are rendered by [[renderAttributes()]]. + */ + public static $attributeOrder = array( + 'type', + 'id', + 'class', + 'name', + 'value', + + 'href', + 'src', + 'action', + 'method', + + 'selected', + 'checked', + 'readonly', + 'disabled', + + 'size', + 'maxlength', + 'width', + 'height', + 'rows', + 'cols', + + 'alt', + 'title', + 'rel', + 'media', + ); /** @@ -78,6 +151,7 @@ class Html * Generates a complete HTML tag. * @param string $name the tag name * @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded. + * If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks. * @param array $attributes the element attributes. The values will be HTML-encoded using [[encode()]]. * Attributes whose value is null will be ignored and not put in the tag returned. * @return string the generated HTML tag @@ -117,7 +191,7 @@ class Html */ public static function endTag($name) { - return !static::$closeVoidElements && isset(static::$voidElements[strtolower($name)]) ? '' : ""; + return ""; } /** @@ -143,9 +217,7 @@ class Html if (!isset($attributes['type'])) { $attributes['type'] = 'text/css'; } - return static::beginTag('style', $attributes) - . "\n/**/\n" - . static::endTag('style'); + return static::tag('style', "\n/**/\n", $attributes); } /** @@ -161,110 +233,77 @@ class Html if (!isset($attributes['type'])) { $attributes['type'] = 'text/javascript'; } - return static::beginTag('script', $attributes) - . "\n/**/\n" - . static::endTag('script'); + return static::tag('script', "\n/**/\n", $attributes); } /** - * Registers a 'refresh' meta tag. - * This method can be invoked anywhere in a view. It will register a 'refresh' - * meta tag with {@link CClientScript} so that the page can be refreshed in - * the specified seconds. - * @param integer $seconds the number of seconds to wait before refreshing the page - * @param string $url the URL to which the page should be redirected to. If empty, it means the current page. - * @since 1.1.1 - */ - public static function refresh($seconds, $url = '') - { - $content = "$seconds"; - if ($url !== '') { - $content .= ';' . static::normalizeUrl($url); - } - Yii::app()->clientScript->registerMetaTag($content, null, 'refresh'); - } - - /** - * Links to the specified CSS file. - * @param string $url the CSS URL - * @param string $media the media that this CSS should apply to. - * @return string the CSS link. + * Generates a link tag that refers to an external CSS file. + * @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]]. + * @param array $attributes the attributes of the link tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated link tag + * @see url */ - public static function cssFile($url, $media = '') + public static function cssFile($url, $attributes = array()) { - return CHtml::linkTag('stylesheet', 'text/css', $url, $media !== '' ? $media : null); + $attributes['rel'] = 'stylesheet'; + $attributes['type'] = 'text/css'; + $attributes['href'] = static::url($url); + return static::tag('link', '', $attributes); } /** - * Encloses the given JavaScript within a script tag. - * @param string $text the JavaScript to be enclosed - * @return string the enclosed JavaScript + * Generates a script tag that refers to an external JavaScript file. + * @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]]. + * @param array $attributes the attributes of the script tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated script tag + * @see url */ - public static function script($text) + public static function jsFile($url, $attributes = array()) { - return ""; + $attributes['type'] = 'text/javascript'; + $attributes['src'] = static::url($url); + return static::tag('script', '', $attributes); } /** - * Includes a JavaScript file. - * @param string $url URL for the JavaScript file - * @return string the JavaScript file tag + * Generates a form start tag. + * @param array|string $action the form action URL. This parameter will be processed by [[url()]]. + * @param string $method form method, either "post" or "get" (case-insensitive) + * @param array $attributes the attributes of the form tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated form start tag. + * @see endForm */ - public static function scriptFile($url) + public static function beginForm($action = '', $method = 'post', $attributes = array()) { - return ''; - } + $attributes['action'] = $url = static::url($action); + $attributes['method'] = $method; - /** - * Generates an opening form tag. - * This is a shortcut to {@link beginForm}. - * @param mixed $action the form action URL (see {@link normalizeUrl} for details about this parameter.) - * @param string $method form method (e.g. post, get) - * @param array $htmlOptions additional HTML attributes (see {@link tag}). - * @return string the generated form tag. - */ - public static function form($action = '', $method = 'post', $htmlOptions = array()) - { - return static::beginForm($action, $method, $htmlOptions); - } + $form = static::beginTag('form', $attributes); - /** - * Generates an opening form tag. - * Note, only the open tag is generated. A close tag should be placed manually - * at the end of the form. - * @param mixed $action the form action URL (see {@link normalizeUrl} for details about this parameter.) - * @param string $method form method (e.g. post, get) - * @param array $htmlOptions additional HTML attributes (see {@link tag}). - * @return string the generated form tag. - * @see endForm - */ - public static function beginForm($action = '', $method = 'post', $htmlOptions = array()) - { - $htmlOptions['action'] = $url = static::normalizeUrl($action); - $htmlOptions['method'] = $method; - $form = static::tag('form', $htmlOptions, false, false); + // query parameters in the action are ignored for GET method + // we use hidden fields to add them back $hiddens = array(); if (!strcasecmp($method, 'get') && ($pos = strpos($url, '?')) !== false) { foreach (explode('&', substr($url, $pos + 1)) as $pair) { if (($pos = strpos($pair, '=')) !== false) { - $hiddens[] = static::hiddenField(urldecode(substr($pair, 0, $pos)), urldecode(substr($pair, $pos + 1)), array('id' => false)); + $hiddens[] = static::hiddenInput(urldecode(substr($pair, 0, $pos)), urldecode(substr($pair, $pos + 1))); } else { - $hiddens[] = static::hiddenField(urldecode($pair), '', array('id' => false)); + $hiddens[] = static::hiddenInput(urldecode($pair), ''); } } } - $request = Yii::app()->request; - if ($request->enableCsrfValidation && !strcasecmp($method, 'post')) { - $hiddens[] = static::hiddenField($request->csrfTokenName, $request->getCsrfToken(), array('id' => false)); - } if ($hiddens !== array()) { - $form .= "\n" . static::tag('div', array('style' => 'display:none'), implode("\n", $hiddens)); + $form .= "\n" . implode("\n", $hiddens); } + return $form; } /** - * Generates a closing form tag. + * Generates a form end tag. * @return string the generated tag * @see beginForm */ @@ -275,854 +314,599 @@ class Html /** * Generates a hyperlink tag. - * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code such as an image tag. - * @param mixed $url a URL or an action route that can be used to create a URL. - * See {@link normalizeUrl} for more details about how to specify this parameter. - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is is coming from end users, you should consider [[encode()]] + * it to prevent XSS attacks. + * @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]] + * and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute + * will not be generated. + * @param array $attributes the attributes of the hyperlink tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. * @return string the generated hyperlink - * @see normalizeUrl - * @see clientChange + * @see url */ - public static function link($text, $url = '#', $htmlOptions = array()) + public static function a($text, $url = null, $attributes = array()) { - if ($url !== '') { - $htmlOptions['href'] = static::normalizeUrl($url); + if ($url !== null) { + $attributes['href'] = static::url($url); } - static::clientChange('click', $htmlOptions); - return static::tag('a', $htmlOptions, $text); + return static::tag('a', $text, $attributes); } /** - * Generates a mailto link. - * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code such as an image tag. - * @param string $email email address. If this is empty, the first parameter (link body) will be treated as the email address. - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * Generates a mailto hyperlink. + * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is is coming from end users, you should consider [[encode()]] + * it to prevent XSS attacks. + * @param string $email email address. If this is null, the first parameter (link body) will be treated + * as the email address and used. + * @param array $attributes the attributes of the hyperlink tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. * @return string the generated mailto link - * @see clientChange */ - public static function mailto($text, $email = '', $htmlOptions = array()) + public static function mailto($text, $email = null, $attributes = array()) { - if ($email === '') { - $email = $text; - } - return static::link($text, 'mailto:' . $email, $htmlOptions); + return static::a($text, 'mailto:' . ($email === null ? $text : $email), $attributes); } /** * Generates an image tag. - * @param string $src the image URL - * @param string $alt the alternative text display - * @param array $htmlOptions additional HTML attributes (see {@link tag}). + * @param string $src the image URL. This parameter will be processed by [[url()]]. + * @param array $attributes the attributes of the image tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. * @return string the generated image tag */ - public static function image($src, $alt = '', $htmlOptions = array()) + public static function img($src, $attributes = array()) { - $htmlOptions['src'] = $src; - $htmlOptions['alt'] = $alt; - return static::tag('img', $htmlOptions); + $attributes['src'] = static::url($src); + if (!isset($attributes['alt'])) { + $attributes['alt'] = ''; + } + return static::tag('img', null, $attributes); } /** - * Generates a button. - * @param string $label the button label - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) - * @return string the generated button tag - * @see clientChange + * Generates a label tag. + * @param string $content label text. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is is coming from end users, you should consider [[encode()]] + * it to prevent XSS attacks. + * @param string $for the ID of the HTML element that this label is associated with. + * If this is null, the "for" attribute will not be generated. + * @param array $attributes the attributes of the label tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated label tag */ - public static function button($name, $label = 'button', $htmlOptions = array()) + public static function label($content, $for = null, $attributes = array()) { - if (!isset($htmlOptions['name'])) { - if (!array_key_exists('name', $htmlOptions)) { - $htmlOptions['name'] = static::ID_PREFIX . static::$count++; - } - } - if (!isset($htmlOptions['type'])) { - $htmlOptions['type'] = 'button'; - } - if (!isset($htmlOptions['value'])) { - $htmlOptions['value'] = $label; - } - static::clientChange('click', $htmlOptions); - return static::tag('input', $htmlOptions); + $attributes['for'] = $for; + return static::tag('label', $content, $attributes); } /** - * Generates a button using HTML button tag. - * This method is similar to {@link button} except that it generates a 'button' - * tag instead of 'input' tag. - * @param string $label the button label. Note that this value will be directly inserted in the button element - * without being HTML-encoded. - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * Generates a button tag. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * If the attributes does not contain "type", a default one with value "button" will be used. * @return string the generated button tag - * @see clientChange */ - public static function htmlButton($label = 'button', $htmlOptions = array()) + public static function button($name = null, $value = null, $content = 'Button', $attributes = array()) { - if (!isset($htmlOptions['name'])) { - $htmlOptions['name'] = static::ID_PREFIX . static::$count++; - } - if (!isset($htmlOptions['type'])) { - $htmlOptions['type'] = 'button'; + $attributes['name'] = $name; + $attributes['value'] = $value; + if (!isset($attributes['type'])) { + $attributes['type'] = 'button'; } - static::clientChange('click', $htmlOptions); - return static::tag('button', $htmlOptions, $label); + return static::tag('button', $content, $attributes); } /** - * Generates a submit button. - * @param string $label the button label - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) - * @return string the generated button tag - * @see clientChange + * Generates a submit button tag. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated submit button tag */ - public static function submitButton($label = 'submit', $htmlOptions = array()) + public static function submitButton($name = null, $value = null, $content = 'Submit', $attributes = array()) { - $htmlOptions['type'] = 'submit'; - return static::button($label, $htmlOptions); + $attributes['type'] = 'submit'; + return static::button($name, $value, $content, $attributes); } /** - * Generates a reset button. - * @param string $label the button label - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) - * @return string the generated button tag - * @see clientChange + * Generates a reset button tag. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated reset button tag */ - public static function resetButton($label = 'reset', $htmlOptions = array()) + public static function resetButton($name = null, $value = null, $content = 'Reset', $attributes = array()) { - $htmlOptions['type'] = 'reset'; - return static::button($label, $htmlOptions); + $attributes['type'] = 'reset'; + return static::button($name, $value, $content, $attributes); } /** - * Generates an image submit button. - * @param string $src the image URL - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * Generates an input type of the given type. + * @param string $type the type attribute. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated input tag + */ + public static function input($type, $name = null, $value = null, $attributes = array()) + { + $attributes['type'] = $type; + $attributes['name'] = $name; + $attributes['value'] = $value; + return static::tag('input', null, $attributes); + } + + /** + * Generates an input button. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. * @return string the generated button tag - * @see clientChange */ - public static function imageButton($src, $htmlOptions = array()) + public static function buttonInput($name, $value = 'Button', $attributes = array()) { - $htmlOptions['src'] = $src; - $htmlOptions['type'] = 'image'; - return static::button('submit', $htmlOptions); + return static::input('button', $name, $value, $attributes); } /** - * Generates a link submit button. - * @param string $label the button label - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) + * Generates a submit input button. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. * @return string the generated button tag - * @see clientChange */ - public static function linkButton($label = 'submit', $htmlOptions = array()) + public static function submitInput($name = null, $value = 'Submit', $attributes = array()) { - if (!isset($htmlOptions['submit'])) { - $htmlOptions['submit'] = isset($htmlOptions['href']) ? $htmlOptions['href'] : ''; - } - return static::link($label, '#', $htmlOptions); + return static::input('submit', $name, $value, $attributes); } /** - * Generates a label tag. - * @param string $label label text. Note, you should HTML-encode the text if needed. - * @param string $for the ID of the HTML element that this label is associated with. - * If this is false, the 'for' attribute for the label tag will not be rendered. - * @param array $htmlOptions additional HTML attributes. - * The following HTML option is recognized: - * - * @return string the generated label tag + * Generates a reset input button. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated button tag */ - public static function label($label, $for, $htmlOptions = array()) + public static function resetInput($name = null, $value = 'Reset', $attributes = array()) { - if ($for === false) { - unset($htmlOptions['for']); - } else { - $htmlOptions['for'] = $for; - } - if (isset($htmlOptions['required'])) { - if ($htmlOptions['required']) { - if (isset($htmlOptions['class'])) { - $htmlOptions['class'] .= ' ' . static::$requiredCss; - } else { - $htmlOptions['class'] = static::$requiredCss; - } - $label = static::$beforeRequiredLabel . $label . static::$afterRequiredLabel; - } - unset($htmlOptions['required']); - } - return static::tag('label', $htmlOptions, $label); + return static::input('reset', $name, $value, $attributes); } /** - * Generates a text field input. - * @param string $name the input name - * @param string $value the input value - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) - * @return string the generated input field - * @see clientChange - * @see inputField + * Generates a text input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated button tag */ - public static function textField($name, $value = '', $htmlOptions = array()) + public static function textInput($name, $value = null, $attributes = array()) { - static::clientChange('change', $htmlOptions); - return static::inputField('text', $name, $value, $htmlOptions); + return static::input('text', $name, $value, $attributes); } /** - * Generates a hidden input. - * @param string $name the input name - * @param string $value the input value - * @param array $htmlOptions additional HTML attributes (see {@link tag}). - * @return string the generated input field - * @see inputField + * Generates a hidden input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated button tag */ - public static function hiddenField($name, $value = '', $htmlOptions = array()) + public static function hiddenInput($name, $value = null, $attributes = array()) { - return static::inputField('hidden', $name, $value, $htmlOptions); + return static::input('hidden', $name, $value, $attributes); } /** - * Generates a password field input. - * @param string $name the input name - * @param string $value the input value - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) - * @return string the generated input field - * @see clientChange - * @see inputField + * Generates a password input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated button tag */ - public static function passwordField($name, $value = '', $htmlOptions = array()) + public static function passwordInput($name, $value = null, $attributes = array()) { - static::clientChange('change', $htmlOptions); - return static::inputField('password', $name, $value, $htmlOptions); + return static::input('password', $name, $value, $attributes); } /** - * Generates a file input. - * Note, you have to set the enclosing form's 'enctype' attribute to be 'multipart/form-data'. - * After the form is submitted, the uploaded file information can be obtained via $_FILES[$name] (see - * PHP documentation). - * @param string $name the input name - * @param string $value the input value - * @param array $htmlOptions additional HTML attributes (see {@link tag}). - * @return string the generated input field - * @see inputField + * Generates a file input field. + * To use a file input field, you should set the enclosing form's "enctype" attribute to + * be "multipart/form-data". After the form is submitted, the uploaded file information + * can be obtained via $_FILES[$name] (see PHP documentation). + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated button tag */ - public static function fileField($name, $value = '', $htmlOptions = array()) + public static function fileInput($name, $value = null, $attributes = array()) { - return static::inputField('file', $name, $value, $htmlOptions); + return static::input('file', $name, $value, $attributes); } /** * Generates a text area input. * @param string $name the input name - * @param string $value the input value - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) - * @return string the generated text area - * @see clientChange - * @see inputField + * @param string $value the input value. Note that it will be encoded using [[encode()]]. + * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * @return string the generated text area tag */ - public static function textArea($name, $value = '', $htmlOptions = array()) + public static function textarea($name, $value = '', $attributes = array()) { - $htmlOptions['name'] = $name; - if (!isset($htmlOptions['id'])) { - $htmlOptions['id'] = static::getIdByName($name); - } elseif ($htmlOptions['id'] === false) { - unset($htmlOptions['id']); - } - static::clientChange('change', $htmlOptions); - return static::tag('textarea', $htmlOptions, isset($htmlOptions['encode']) && !$htmlOptions['encode'] ? $value : static::encode($value)); + $attributes['name'] = $name; + return static::tag('textarea', static::encode($value), $attributes); } /** - * Generates a radio button. - * @param string $name the input name - * @param boolean $checked whether the radio button is checked - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) - * Since version 1.1.2, a special option named 'uncheckValue' is available that can be used to specify - * the value returned when the radio button is not checked. When set, a hidden field is rendered so that - * when the radio button is not checked, we can still obtain the posted uncheck value. - * If 'uncheckValue' is not set or set to NULL, the hidden field will not be rendered. - * @return string the generated radio button - * @see clientChange - * @see inputField + * Generates a radio button input. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param boolean $checked whether the radio button should be checked. + * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. The following attribute + * will be specially handled and not put in the resulting tag: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute + * is present, a hidden input will be generated 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. + * + * @return string the generated radio button tag */ - public static function radioButton($name, $checked = false, $htmlOptions = array()) + public static function radio($name, $value = '1', $checked = false, $attributes = array()) { - if ($checked) { - $htmlOptions['checked'] = 'checked'; - } else { - unset($htmlOptions['checked']); - } - $value = isset($htmlOptions['value']) ? $htmlOptions['value'] : 1; - static::clientChange('click', $htmlOptions); - - if (array_key_exists('uncheckValue', $htmlOptions)) { - $uncheck = $htmlOptions['uncheckValue']; - unset($htmlOptions['uncheckValue']); - } else { - $uncheck = null; - } - - if ($uncheck !== null) { + $attributes['checked'] = $checked; + if (isset($attributes['uncheck'])) { // add a hidden field so that if the radio button is not selected, it still submits a value - if (isset($htmlOptions['id']) && $htmlOptions['id'] !== false) { - $uncheckOptions = array('id' => static::ID_PREFIX . $htmlOptions['id']); - } else { - $uncheckOptions = array('id' => false); - } - $hidden = static::hiddenField($name, $uncheck, $uncheckOptions); + $hidden = static::hiddenInput($name, $attributes['uncheck']); + unset($attributes['uncheck']); } else { $hidden = ''; } - - // add a hidden field so that if the radio button is not selected, it still submits a value - return $hidden . static::inputField('radio', $name, $value, $htmlOptions); + return $hidden . static::input('radio', $name, $value, $attributes); } /** - * Generates a check box. - * @param string $name the input name - * @param boolean $checked whether the check box is checked - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.) - * Since version 1.1.2, a special option named 'uncheckValue' is available that can be used to specify - * the value returned when the checkbox is not checked. When set, a hidden field is rendered so that - * when the checkbox is not checked, we can still obtain the posted uncheck value. - * If 'uncheckValue' is not set or set to NULL, the hidden field will not be rendered. - * @return string the generated check box - * @see clientChange - * @see inputField + * Generates a checkbox input. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param boolean $checked whether the checkbox should be checked. + * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. The following attribute + * will be specially handled and not put in the resulting tag: + * + * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute + * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted, + * the value of this attribute will still be submitted to the server via the hidden input. + * + * @return string the generated checkbox tag */ - public static function checkBox($name, $checked = false, $htmlOptions = array()) + public static function checkbox($name, $value = '1', $checked = false, $attributes = array()) { - if ($checked) { - $htmlOptions['checked'] = 'checked'; - } else { - unset($htmlOptions['checked']); - } - $value = isset($htmlOptions['value']) ? $htmlOptions['value'] : 1; - static::clientChange('click', $htmlOptions); - - if (array_key_exists('uncheckValue', $htmlOptions)) { - $uncheck = $htmlOptions['uncheckValue']; - unset($htmlOptions['uncheckValue']); - } else { - $uncheck = null; - } - - if ($uncheck !== null) { - // add a hidden field so that if the check box is not checked, it still submits a value - if (isset($htmlOptions['id']) && $htmlOptions['id'] !== false) { - $uncheckOptions = array('id' => static::ID_PREFIX . $htmlOptions['id']); - } else { - $uncheckOptions = array('id' => false); - } - $hidden = static::hiddenField($name, $uncheck, $uncheckOptions); + $attributes['checked'] = $checked; + if (isset($attributes['uncheck'])) { + // add a hidden field so that if the checkbox is not selected, it still submits a value + $hidden = static::hiddenInput($name, $attributes['uncheck']); + unset($attributes['uncheck']); } else { $hidden = ''; } - - // add a hidden field so that if the check box is not checked, it still submits a value - return $hidden . static::inputField('checkbox', $name, $value, $htmlOptions); + return $hidden . static::input('checkbox', $name, $value, $attributes); } /** - * Generates a drop down list. + * Generates a drop-down list. * @param string $name the input name - * @param string $select the selected value - * @param array $data data for generating the list options (value=>display). - * You may use {@link listData} to generate this data. - * Please refer to {@link listOptions} on how this data is used to generate the list options. - * Note, the values and labels will be automatically HTML-encoded by this method. - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are recognized. See {@link clientChange} and {@link tag} for more details. - * In addition, the following options are also supported specifically for dropdown list: - * - * Since 1.1.13, a special option named 'unselectValue' is available. It can be used to set the value - * that will be returned when no option is selected in multiple mode. When set, a hidden field is - * rendered so that if no option is selected in multiple mode, we can still obtain the posted - * unselect value. If 'unselectValue' is not set or set to NULL, the hidden field will not be rendered. - * @return string the generated drop down list - * @see clientChange - * @see inputField - * @see listData + * @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\util\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 string $selection the selected value + * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. The following attribute + * will be specially handled and not put in the resulting tag: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * @return string the generated drop-down list tag */ - public static function dropDownList($name, $select, $data, $htmlOptions = array()) + public static function dropDownList($name, $items, $selection = null, $attributes = array()) { - $htmlOptions['name'] = $name; - - if (!isset($htmlOptions['id'])) { - $htmlOptions['id'] = static::getIdByName($name); - } elseif ($htmlOptions['id'] === false) { - unset($htmlOptions['id']); - } - - static::clientChange('change', $htmlOptions); - $options = "\n" . static::listOptions($select, $data, $htmlOptions); - $hidden = ''; - - if (isset($htmlOptions['multiple'])) { - if (substr($htmlOptions['name'], -2) !== '[]') { - $htmlOptions['name'] .= '[]'; - } - - if (isset($htmlOptions['unselectValue'])) { - $hiddenOptions = isset($htmlOptions['id']) ? array('id' => static::ID_PREFIX . $htmlOptions['id']) : array('id' => false); - $hidden = static::hiddenField(substr($htmlOptions['name'], 0, -2), $htmlOptions['unselectValue'], $hiddenOptions); - unset($htmlOptions['unselectValue']); - } - } - // add a hidden field so that if the option is not selected, it still submits a value - return $hidden . static::tag('select', $htmlOptions, $options); + $attributes['name'] = $name; + $options = static::renderOptions($items, $selection, $attributes); + return static::tag('select', "\n" . $options . "\n", $attributes); } /** * Generates a list box. * @param string $name the input name - * @param mixed $select the selected value(s). This can be either a string for single selection or an array for multiple selections. - * @param array $data data for generating the list options (value=>display) - * You may use {@link listData} to generate this data. - * Please refer to {@link listOptions} on how this data is used to generate the list options. - * Note, the values and labels will be automatically HTML-encoded by this method. - * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special - * attributes are also recognized. See {@link clientChange} and {@link tag} for more details. - * In addition, the following options are also supported specifically for list box: - * - * @return string the generated list box - * @see clientChange - * @see inputField - * @see listData - */ - public static function listBox($name, $select, $data, $htmlOptions = array()) - { - if (!isset($htmlOptions['size'])) { - $htmlOptions['size'] = 4; + * @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\util\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 string|array $selection the selected value(s) + * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. The following attribute + * will be specially handled and not put in the resulting tag: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * array( + * 'value1' => array('disabled' => true), + * 'value2' => array('label' => 'value 2'), + * ); + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * @return string the generated list box tag + */ + public static function listBox($name, $items, $selection = null, $attributes = array()) + { + if (!isset($attributes['size'])) { + $attributes['size'] = 4; + } + if (isset($attributes['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + $hidden = static::hiddenInput($name, $attributes['unselect']); + unset($attributes['unselect']); + } else { + $hidden = ''; } - if (isset($htmlOptions['multiple'])) { - if (substr($name, -2) !== '[]') { - $name .= '[]'; - } + if (isset($attributes['multiple']) && $attributes['multiple'] && substr($name, -2) !== '[]') { + $name .= '[]'; } - return static::dropDownList($name, $select, $data, $htmlOptions); + $attributes['name'] = $name; + $options = static::renderOptions($items, $selection, $attributes); + return $hidden . static::tag('select', "\n" . $options . "\n", $attributes); } /** - * Generates a check box list. - * A check box list allows multiple selection, like {@link listBox}. - * As a result, the corresponding POST value is an array. - * @param string $name name of the check box list. You can use this name to retrieve - * the selected value(s) once the form is submitted. - * @param mixed $select selection of the check boxes. This can be either a string - * for single selection or an array for multiple selections. - * @param array $data value-label pairs used to generate the check box list. - * Note, the values will be automatically HTML-encoded, while the labels will not. - * @param array $htmlOptions additional HTML options. The options will be applied to - * each checkbox input. The following special options are recognized: - * - * @return string the generated check box list + * Generates a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * @param string $name the name attribute of each checkbox. + * @param array $items the data item used to generate the checkboxes. + * The array keys are the labels, while the array values are the corresponding checkbox values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param string|array $selection the selected value(s). + * @param callable $formatter a callback that can be used to customize the generation of the HTML code + * corresponding to a single checkbox. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $value, $checked) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input. + * @return string the generated checkbox list */ - public static function checkBoxList($name, $select, $data, $htmlOptions = array()) + public static function checkboxList($name, $items, $selection = null, $formatter = null) { - $template = isset($htmlOptions['template']) ? $htmlOptions['template'] : '{input} {label}'; - $separator = isset($htmlOptions['separator']) ? $htmlOptions['separator'] : "
\n"; - $container = isset($htmlOptions['container']) ? $htmlOptions['container'] : 'span'; - unset($htmlOptions['template'], $htmlOptions['separator'], $htmlOptions['container']); - if (substr($name, -2) !== '[]') { $name .= '[]'; } - if (isset($htmlOptions['checkAll'])) { - $checkAllLabel = $htmlOptions['checkAll']; - $checkAllLast = isset($htmlOptions['checkAllLast']) && $htmlOptions['checkAllLast']; - } - unset($htmlOptions['checkAll'], $htmlOptions['checkAllLast']); - - $labelOptions = isset($htmlOptions['labelOptions']) ? $htmlOptions['labelOptions'] : array(); - unset($htmlOptions['labelOptions']); - - $items = array(); - $baseID = isset($htmlOptions['baseID']) ? $htmlOptions['baseID'] : static::getIdByName($name); - unset($htmlOptions['baseID']); - $id = 0; - $checkAll = true; - - foreach ($data as $value => $label) { - $checked = !is_array($select) && !strcmp($value, $select) || is_array($select) && in_array($value, $select); - $checkAll = $checkAll && $checked; - $htmlOptions['value'] = $value; - $htmlOptions['id'] = $baseID . '_' . $id++; - $option = static::checkBox($name, $checked, $htmlOptions); - $label = static::label($label, $htmlOptions['id'], $labelOptions); - $items[] = strtr($template, array('{input}' => $option, '{label}' => $label)); - } - - if (isset($checkAllLabel)) { - $htmlOptions['value'] = 1; - $htmlOptions['id'] = $id = $baseID . '_all'; - $option = static::checkBox($id, $checkAll, $htmlOptions); - $label = static::label($checkAllLabel, $id, $labelOptions); - $item = strtr($template, array('{input}' => $option, '{label}' => $label)); - if ($checkAllLast) { - $items[] = $item; + $lines = array(); + $index = 0; + foreach ($items as $value => $label) { + $checked = $selection !== null && + (!is_array($selection) && !strcmp($value, $selection) + || is_array($selection) && in_array($value, $selection)); + if ($formatter !== null) { + $lines[] = call_user_func($formatter, $index, $label, $name, $value, $checked); } else { - array_unshift($items, $item); + $lines[] = static::label(static::checkbox($name, $value, $checked) . ' ' . $label); } - $name = strtr($name, array('[' => '\\[', ']' => '\\]')); - $js = <<getClientScript(); - $cs->registerCoreScript('jquery'); - $cs->registerScript($id, $js); + $index++; } - if (empty($container)) { - return implode($separator, $items); - } else { - return static::tag($container, array('id' => $baseID), implode($separator, $items)); - } + return implode("\n", $lines); } /** - * Generates a radio button list. - * A radio button list is like a {@link checkBoxList check box list}, except that - * it only allows single selection. - * @param string $name name of the radio button list. You can use this name to retrieve - * the selected value(s) once the form is submitted. - * @param string $select selection of the radio buttons. - * @param array $data value-label pairs used to generate the radio button list. - * Note, the values will be automatically HTML-encoded, while the labels will not. - * @param array $htmlOptions additional HTML options. The options will be applied to - * each radio button input. The following special options are recognized: - * - * @return string the generated radio button list - */ - public static function radioButtonList($name, $select, $data, $htmlOptions = array()) - { - $template = isset($htmlOptions['template']) ? $htmlOptions['template'] : '{input} {label}'; - $separator = isset($htmlOptions['separator']) ? $htmlOptions['separator'] : "
\n"; - $container = isset($htmlOptions['container']) ? $htmlOptions['container'] : 'span'; - unset($htmlOptions['template'], $htmlOptions['separator'], $htmlOptions['container']); - - $labelOptions = isset($htmlOptions['labelOptions']) ? $htmlOptions['labelOptions'] : array(); - unset($htmlOptions['labelOptions']); - - $items = array(); - $baseID = isset($htmlOptions['baseID']) ? $htmlOptions['baseID'] : static::getIdByName($name); - unset($htmlOptions['baseID']); - $id = 0; - foreach ($data as $value => $label) { - $checked = !strcmp($value, $select); - $htmlOptions['value'] = $value; - $htmlOptions['id'] = $baseID . '_' . $id++; - $option = static::radioButton($name, $checked, $htmlOptions); - $label = static::label($label, $htmlOptions['id'], $labelOptions); - $items[] = strtr($template, array('{input}' => $option, '{label}' => $label)); - } - if (empty($container)) { - return implode($separator, $items); - } else { - return static::tag($container, array('id' => $baseID), implode($separator, $items)); - } - } - - /** - * Normalizes the input parameter to be a valid URL. - * - * If the input parameter is an empty string, the currently requested URL will be returned. - * - * If the input parameter is a non-empty string, it is treated as a valid URL and will - * be returned without any change. + * Generates a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * @param string $name the name attribute of each radio button. + * @param array $items the data item used to generate the radio buttons. + * The array keys are the labels, while the array values are the corresponding radio button values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param string|array $selection the selected value(s). + * @param callable $formatter a callback that can be used to customize the generation of the HTML code + * corresponding to a single radio button. The signature of this callback must be: * - * If the input parameter is an array, it is treated as a controller route and a list of - * GET parameters, and the {@link CController::createUrl} method will be invoked to - * create a URL. In this case, the first array element refers to the controller route, - * and the rest key-value pairs refer to the additional GET parameters for the URL. - * For example, array('post/list', 'page'=>3) may be used to generate the URL - * /index.php?r=post/list&page=3. + * ~~~ + * function ($index, $label, $name, $value, $checked) + * ~~~ * - * @param mixed $url the parameter to be used to generate a valid URL - * @return string the normalized URL + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input. + * @return string the generated radio button list */ - public static function normalizeUrl($url) + public static function radioList($name, $items, $selection = null, $formatter = null) { - if (is_array($url)) { - if (isset($url[0])) { - if (($c = Yii::app()->getController()) !== null) { - $url = $c->createUrl($url[0], array_splice($url, 1)); - } else { - $url = Yii::app()->createUrl($url[0], array_splice($url, 1)); - } + $lines = array(); + $index = 0; + foreach ($items as $value => $label) { + $checked = $selection !== null && + (!is_array($selection) && !strcmp($value, $selection) + || is_array($selection) && in_array($value, $selection)); + if ($formatter !== null) { + $lines[] = call_user_func($formatter, $index, $label, $name, $value, $checked); } else { - $url = ''; + $lines[] = static::label(static::radio($name, $value, $checked) . ' ' . $label); } + $index++; } - return $url === '' ? Yii::app()->getRequest()->getUrl() : $url; - } - /** - * Generates an input HTML tag. - * This method generates an input HTML tag based on the given input name and value. - * @param string $type the input type (e.g. 'text', 'radio') - * @param string $name the input name - * @param string $value the input value - * @param array $htmlOptions additional HTML attributes for the HTML tag (see {@link tag}). - * @return string the generated input tag - */ - protected static function inputField($type, $name, $value, $htmlOptions) - { - $htmlOptions['type'] = $type; - $htmlOptions['value'] = $value; - $htmlOptions['name'] = $name; - if (!isset($htmlOptions['id'])) { - $htmlOptions['id'] = static::getIdByName($name); - } elseif ($htmlOptions['id'] === false) { - unset($htmlOptions['id']); - } - return static::tag('input', $htmlOptions); + return implode("\n", $lines); } /** - * Generates the list options. - * @param mixed $selection the selected value(s). This can be either a string for single selection or an array for multiple selections. - * @param array $listData the option data (see {@link listData}) - * @param array $htmlOptions additional HTML attributes. The following two special attributes are recognized: - * + * Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]]. + * @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\util\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 string|array $selection the selected value(s). This can be either a string for single selection + * or an array for multiple selections. + * @param array $attributes the attributes parameter that is passed to the [[dropDownList()]] or [[listBox()]] call. + * This method will take out these elements, if any: "prompt", "options" and "groups". See more details + * in [[dropDownList()]] for the explanation of these elements. + * * @return string the generated list options */ - public static function listOptions($selection, $listData, &$htmlOptions) + public static function renderOptions($items, $selection = null, &$attributes = array()) { - $raw = isset($htmlOptions['encode']) && !$htmlOptions['encode']; - $content = ''; - if (isset($htmlOptions['prompt'])) { - $content .= '\n"; - unset($htmlOptions['prompt']); - } - if (isset($htmlOptions['empty'])) { - if (!is_array($htmlOptions['empty'])) { - $htmlOptions['empty'] = array('' => $htmlOptions['empty']); - } - foreach ($htmlOptions['empty'] as $value => $label) { - $content .= '\n"; - } - unset($htmlOptions['empty']); + $lines = array(); + if (isset($attributes['prompt'])) { + $prompt = strtr(static::encode($attributes['prompt']), ' ', ' '); + $lines[] = static::tag('option', $prompt, array('value' => '')); } - if (isset($htmlOptions['options'])) { - $options = $htmlOptions['options']; - unset($htmlOptions['options']); - } else { - $options = array(); - } + $options = isset($attributes['options']) ? $attributes['options'] : array(); + $groups = isset($attributes['groups']) ? $attributes['groups'] : array(); + unset($attributes['prompt'], $attributes['options'], $attributes['groups']); - $key = isset($htmlOptions['key']) ? $htmlOptions['key'] : 'primaryKey'; - if (is_array($selection)) { - foreach ($selection as $i => $item) { - if (is_object($item)) { - $selection[$i] = $item->$key; - } - } - } elseif (is_object($selection)) { - $selection = $selection->$key; - } - - foreach ($listData as $key => $value) { + foreach ($items as $key => $value) { if (is_array($value)) { - $content .= '\n"; - $dummy = array('options' => $options); - if (isset($htmlOptions['encode'])) { - $dummy['encode'] = $htmlOptions['encode']; - } - $content .= static::listOptions($selection, $value, $dummy); - $content .= '' . "\n"; + $groupAttrs = isset($groups[$key]) ? $groups[$key] : array(); + $groupAttrs['label'] = $key; + $attrs = array('options' => $options, 'groups' => $groups); + $content = static::renderOptions($selection, $value, $attrs); + $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs); } else { - $attributes = array('value' => (string)$key, 'encode' => !$raw); - if (!is_array($selection) && !strcmp($key, $selection) || is_array($selection) && in_array($key, $selection)) { - $attributes['selected'] = 'selected'; - } - if (isset($options[$key])) { - $attributes = array_merge($attributes, $options[$key]); - } - $content .= static::tag('option', $attributes, $raw ? (string)$value : static::encode((string)$value)) . "\n"; + $attrs = isset($options[$key]) ? $options[$key] : array(); + $attrs['value'] = $key; + $attrs['selected'] = $selection !== null && + (!is_array($selection) && !strcmp($key, $selection) + || is_array($selection) && in_array($key, $selection)); + $lines[] = static::tag('option', strtr(static::encode($value), ' ', ' '), $attrs); } } - unset($htmlOptions['key']); - - return $content; + return implode("\n", $lines); } /** * Renders the HTML tag attributes. - * Since version 1.1.5, attributes whose value is null will not be rendered. - * Special attributes, such as 'checked', 'disabled', 'readonly', will be rendered - * properly based on their corresponding boolean value. - * @param array $attributes attributes to be rendered - * @return string the rendering result + * Boolean attributes such as s 'checked', 'disabled', 'readonly', will be handled specially + * according to [[booleanAttributes]] and [[showBooleanAttributeValues]]. + * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the rendering result. + * @return string the rendering result. If the attributes are not empty, they will be rendered + * into a string with a leading white space (such that it can be directly appended to the tag name + * in a tag. If there is no attribute, an empty string will be returned. */ public static function renderAttributes($attributes) { - static $specialAttributes = array( - 'async' => 1, - 'autofocus' => 1, - 'autoplay' => 1, - 'checked' => 1, - 'controls' => 1, - 'declare' => 1, - 'default' => 1, - 'defer' => 1, - 'disabled' => 1, - 'formnovalidate' => 1, - 'hidden' => 1, - 'ismap' => 1, - 'loop' => 1, - 'multiple' => 1, - 'muted' => 1, - 'nohref' => 1, - 'noresize' => 1, - 'novalidate' => 1, - 'open' => 1, - 'readonly' => 1, - 'required' => 1, - 'reversed' => 1, - 'scoped' => 1, - 'seamless' => 1, - 'selected' => 1, - 'typemustmatch' => 1, - ); - - if ($attributes === array()) { - return ''; + if (count($attributes) > 1) { + $sorted = array(); + foreach (static::$attributeOrder as $name) { + if (isset($attributes[$name])) { + $sorted[$name] = $attributes[$name]; + } + } + $attributes = array_merge($sorted, $attributes); } $html = ''; - if (isset($attributes['encode'])) { - $raw = !$attributes['encode']; - unset($attributes['encode']); - } else { - $raw = false; - } - foreach ($attributes as $name => $value) { - if (isset($specialAttributes[$name])) { - if ($value) { - $html .= ' ' . $name; - if (static::$renderSpecialAttributesValue) { - $html .= '="' . $name . '"'; - } + if (isset(static::$booleanAttributes[strtolower($name)])) { + if ($value || strcasecmp($name, $value) === 0) { + $html .= static::$showBooleanAttributeValues ? " $name=\"$name\"" : " $name"; } } elseif ($value !== null) { - $html .= ' ' . $name . '="' . ($raw ? $value : static::encode($value)) . '"'; + $html .= " $name=\"" . static::encode($value) . '"'; } } - return $html; } + + /** + * Normalizes the input parameter to be a valid URL. + * + * If the input parameter + * + * - is an empty string: the currently requested URL will be returned; + * - is a non-empty string: it will be processed by [[Yii::getAlias()]] which, if the string is an alias, + * will be resolved into a URL; + * - is an array: the first array element is considered a route, while the rest of the name-value + * pairs are considered as the parameters to be used for URL creation using [[\yii\base\Application::createUrl()]]. + * Here are some examples: `array('post/index', 'page' => 2)`, `array('index')`. + * + * @param array|string $url the parameter to be used to generate a valid URL + * @return string the normalized URL + * @throws InvalidParamException if the parameter is invalid. + */ + public static function url($url) + { + if (is_array($url)) { + if (isset($url[0])) { + return Yii::$app->createUrl($url[0], array_splice($url, 1)); + } else { + throw new InvalidParamException('The array specifying a URL must contain at least one element.'); + } + } elseif ($url === '') { + return Yii::$app->getRequest()->getUrl(); + } else { + return Yii::getAlias($url); + } + } } diff --git a/framework/web/Application.php b/framework/web/Application.php index d2a758a..0b9ce06 100644 --- a/framework/web/Application.php +++ b/framework/web/Application.php @@ -6,6 +6,7 @@ */ namespace yii\web; +use yii\base\InvalidParamException; /** * Application is the base class for all application classes. @@ -62,11 +63,48 @@ class Application extends \yii\base\Application } /** - * @return UrlManager + * Creates a URL using the given route and parameters. + * + * This method first normalizes the given route by converting a relative route into an absolute one. + * A relative route is a route without slash. If the route is an empty string, it stands for + * the route of the currently active [[controller]]. If the route is not empty, it stands for + * an action ID of the [[controller]]. + * + * After normalizing the route, this method calls [[\yii\web\UrlManager::createUrl()]] + * to create a relative URL. + * + * @param string $route the route. This can be either an absolute or a relative route. + * @param array $params the parameters (name-value pairs) to be included in the generated URL + * @return string the created URL + * @throws InvalidParamException if a relative route is given and there is no active controller. + * @see createAbsoluteUrl */ - public function getUrlManager() + public function createUrl($route, $params = array()) { - return $this->getComponent('urlManager'); + if (strpos($route, '/') === false) { + // a relative route + if ($this->controller !== null) { + $route = $route === '' ? $this->controller->route : $this->controller->uniqueId . '/' . $route; + } else { + throw new InvalidParamException('No active controller exists for resolving a relative route.'); + } + } + return $this->getUrlManager()->createUrl($route, $params); + } + + /** + * Creates an absolute URL using the given route and parameters. + * This method first calls [[createUrl()]] to create a relative URL. + * It then prepends [[\yii\web\UrlManager::hostInfo]] to the URL to form an absolute one. + * @param string $route the route. This can be either an absolute or a relative route. + * See [[createUrl()]] for more details. + * @param array $params the parameters (name-value pairs) + * @return string the created URL + * @see createUrl + */ + public function createAbsoluteUrl($route, $params = array()) + { + return $this->getUrlManager()->getHostInfo() . $this->createUrl($route, $params); } /** @@ -86,9 +124,6 @@ class Application extends \yii\base\Application 'session' => array( 'class' => 'yii\web\Session', ), - 'urlManager' => array( - 'class' => 'yii\web\UrlManager', - ), )); } } diff --git a/framework/web/Controller.php b/framework/web/Controller.php index 2779c35..9420034 100644 --- a/framework/web/Controller.php +++ b/framework/web/Controller.php @@ -16,4 +16,8 @@ namespace yii\web; */ class Controller extends \yii\base\Controller { + public function createUrl($route, $params = array()) + { + + } } \ No newline at end of file diff --git a/framework/web/Request.php b/framework/web/Request.php index 65a7f30..c7899cf 100644 --- a/framework/web/Request.php +++ b/framework/web/Request.php @@ -368,7 +368,7 @@ class Request extends \yii\base\Request */ protected function resolvePathInfo() { - $pathInfo = $this->getRequestUri(); + $pathInfo = $this->getUrl(); if (($pos = strpos($pathInfo, '?')) !== false) { $pathInfo = substr($pathInfo, 0, $pos); @@ -407,42 +407,41 @@ class Request extends \yii\base\Request } /** - * Returns the currently requested URL. - * This is a shortcut to the concatenation of [[hostInfo]] and [[requestUri]]. - * @return string the currently requested URL. + * Returns the currently requested absolute URL. + * This is a shortcut to the concatenation of [[hostInfo]] and [[url]]. + * @return string the currently requested absolute URL. */ - public function getUrl() + public function getAbsoluteUrl() { - return $this->getHostInfo() . $this->getRequestUri(); + return $this->getHostInfo() . $this->getUrl(); } - private $_requestUri; + private $_url; /** - * Returns the portion after [[hostInfo]] for the currently requested URL. - * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any. - * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework. - * @return string the request URI portion for the currently requested URL. - * Note that the URI returned is URL-encoded. - * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration + * Returns the currently requested relative URL. + * This refers to the portion of the URL that is after the [[hostInfo]] part. + * It includes the [[queryString]] part if any. + * @return string the currently requested relative URL. Note that the URI returned is URL-encoded. + * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration */ - public function getRequestUri() + public function getUrl() { - if ($this->_requestUri === null) { - $this->_requestUri = $this->resolveRequestUri(); + if ($this->_url === null) { + $this->_url = $this->resolveRequestUri(); } - return $this->_requestUri; + return $this->_url; } /** - * Sets the currently requested URI. + * Sets the currently requested relative URL. * The URI must refer to the portion that is after [[hostInfo]]. * Note that the URI should be URL-encoded. * @param string $value the request URI to be set */ - public function setRequestUri($value) + public function setUrl($value) { - $this->_requestUri = $value; + $this->_url = $value; } /** diff --git a/todo.md b/todo.md index c80da5a..03b9f70 100644 --- a/todo.md +++ b/todo.md @@ -18,6 +18,7 @@ * backend-specific unit tests * dependency unit tests - validators + * Refactor validators to add validateValue() for every validator, if possible. Check if value is an array. * FileValidator: depends on CUploadedFile * CaptchaValidator: depends on CaptchaAction * DateValidator: should we use CDateTimeParser, or simply use strtotime()?