diff --git a/framework/util/Html.php b/framework/util/Html.php index 774a733..8c29047 100644 --- a/framework/util/Html.php +++ b/framework/util/Html.php @@ -217,7 +217,7 @@ class Html if (!isset($attributes['type'])) { $attributes['type'] = 'text/css'; } - return static::tag('style', "\n/**/\n", $attributes); + return static::tag('style', "/**/", $attributes); } /** @@ -233,7 +233,7 @@ class Html if (!isset($attributes['type'])) { $attributes['type'] = 'text/javascript'; } - return static::tag('script', "\n/**/\n", $attributes); + return static::tag('script', "/**/", $attributes); } /** @@ -278,23 +278,25 @@ class Html */ public static function beginForm($action = '', $method = 'post', $attributes = array()) { - $attributes['action'] = $url = static::url($action); - $attributes['method'] = $method; - - $form = static::beginTag('form', $attributes); + $action = static::url($action); // 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::hiddenInput(urldecode(substr($pair, 0, $pos)), urldecode(substr($pair, $pos + 1))); + if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) { + foreach (explode('&', substr($action, $pos + 1)) as $pair) { + if (($pos1 = strpos($pair, '=')) !== false) { + $hiddens[] = static::hiddenInput(urldecode(substr($pair, 0, $pos1)), urldecode(substr($pair, $pos1 + 1))); } else { $hiddens[] = static::hiddenInput(urldecode($pair), ''); } } + $action = substr($action, 0, $pos); } + + $attributes['action'] = $action; + $attributes['method'] = $method; + $form = static::beginTag('form', $attributes); if ($hiddens !== array()) { $form .= "\n" . implode("\n", $hiddens); } @@ -696,17 +698,20 @@ class Html if (!isset($attributes['size'])) { $attributes['size'] = 4; } + if (isset($attributes['multiple']) && $attributes['multiple'] && substr($name, -2) !== '[]') { + $name .= '[]'; + } + $attributes['name'] = $name; if (isset($attributes['unselect'])) { // add a hidden field so that if the list box has no option being selected, it still submits a value + if (substr($name, -2) === '[]') { + $name = substr($name, 0, -2); + } $hidden = static::hiddenInput($name, $attributes['unselect']); unset($attributes['unselect']); } else { $hidden = ''; } - if (isset($attributes['multiple']) && $attributes['multiple'] && substr($name, -2) !== '[]') { - $name .= '[]'; - } - $attributes['name'] = $name; $options = static::renderOptions($items, $selection, $attributes); return $hidden . static::tag('select', "\n" . $options . "\n", $attributes); } @@ -720,8 +725,13 @@ class Html * 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: + * @param array $options options (name => config) for the checkbox list. The following options are supported: + * + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: * * ~~~ * function ($index, $label, $name, $value, $checked) @@ -732,12 +742,13 @@ class Html * value and the checked status of the checkbox input. * @return string the generated checkbox list */ - public static function checkboxList($name, $items, $selection = null, $formatter = null) + public static function checkboxList($name, $items, $selection = null, $options = array()) { if (substr($name, -2) !== '[]') { $name .= '[]'; } + $formatter = isset($options['item']) ? $options['item'] : null; $lines = array(); $index = 0; foreach ($items as $value => $label) { @@ -752,7 +763,16 @@ class Html $index++; } - return implode("\n", $lines); + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + $name2 = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name; + $hidden = static::hiddenInput($name2, $options['unselect']); + } else { + $hidden = ''; + } + $separator = isset($options['separator']) ? $options['separator'] : "\n"; + + return $hidden . implode($separator, $lines); } /** @@ -763,8 +783,13 @@ class Html * 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: + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: * * ~~~ * function ($index, $label, $name, $value, $checked) @@ -775,8 +800,9 @@ class Html * value and the checked status of the radio button input. * @return string the generated radio button list */ - public static function radioList($name, $items, $selection = null, $formatter = null) + public static function radioList($name, $items, $selection = null, $options = array()) { + $formatter = isset($options['item']) ? $options['item'] : null; $lines = array(); $index = 0; foreach ($items as $value => $label) { @@ -791,7 +817,15 @@ class Html $index++; } - return implode("\n", $lines); + $separator = isset($options['separator']) ? $options['separator'] : "\n"; + if (isset($options['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, $options['unselect']); + } else { + $hidden = ''; + } + + return $hidden . implode($separator, $lines); } /** diff --git a/framework/web/Application.php b/framework/web/Application.php index 0b9ce06..61e84a3 100644 --- a/framework/web/Application.php +++ b/framework/web/Application.php @@ -66,9 +66,9 @@ class Application extends \yii\base\Application * 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]]. + * A relative route is a route without a leading slash. It is considered to be relative to the currently + * requested route. If the route is an empty string, it stands for the route of the currently active + * [[controller]]. Otherwise, the [[Controller::uniqueId]] will be prepended to the route. * * After normalizing the route, this method calls [[\yii\web\UrlManager::createUrl()]] * to create a relative URL. @@ -81,12 +81,12 @@ class Application extends \yii\base\Application */ public function createUrl($route, $params = array()) { - if (strpos($route, '/') === false) { + if (strncmp($route, '/', 1) !== 0) { // 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.'); + throw new InvalidParamException('Relative route cannot be handled because there is no active controller.'); } } return $this->getUrlManager()->createUrl($route, $params); diff --git a/tests/unit/framework/util/ArrayHelperTest.php b/tests/unit/framework/util/ArrayHelperTest.php index a713381..a36ce68 100644 --- a/tests/unit/framework/util/ArrayHelperTest.php +++ b/tests/unit/framework/util/ArrayHelperTest.php @@ -1,6 +1,6 @@ array( + 'request' => array( + 'class' => 'yii\web\Request', + 'url' => '/test', + ), + ), + )); + } + + public function tearDown() + { + Yii::$app = null; + } + + public function testEncode() + { + $this->assertEquals("a<>&"'", Html::encode("a<>&\"'")); + } + + public function testDecode() + { + $this->assertEquals("a<>&\"'", Html::decode("a<>&"'")); + } + + public function testTag() + { + $this->assertEquals('
', Html::tag('br')); + $this->assertEquals('', Html::tag('span')); + $this->assertEquals('
content
', Html::tag('div', 'content')); + $this->assertEquals('', Html::tag('input', '', array('type' => 'text', 'name' => 'test', 'value' => '<>'))); + + Html::$closeVoidElements = false; + + $this->assertEquals('
', Html::tag('br')); + $this->assertEquals('', Html::tag('span')); + $this->assertEquals('
content
', Html::tag('div', 'content')); + $this->assertEquals('', Html::tag('input', '', array('type' => 'text', 'name' => 'test', 'value' => '<>'))); + + Html::$closeVoidElements = true; + } + + public function testBeginTag() + { + $this->assertEquals('
', Html::beginTag('br')); + $this->assertEquals('', Html::beginTag('span', array('id' => 'test', 'class' => 'title'))); + } + + public function testEndTag() + { + $this->assertEquals('
', Html::endTag('br')); + $this->assertEquals('
', Html::endTag('span')); + } + + public function testCdata() + { + $data = 'test<>'; + $this->assertEquals('', Html::cdata($data)); + } + + public function testStyle() + { + $content = 'a <>'; + $this->assertEquals("", Html::style($content)); + $this->assertEquals("", Html::style($content, array('type' => 'text/less'))); + } + + public function testScript() + { + $content = 'a <>'; + $this->assertEquals("", Html::script($content)); + $this->assertEquals("", Html::script($content, array('type' => 'text/js'))); + } + + public function testCssFile() + { + $this->assertEquals('', Html::cssFile('http://example.com')); + $this->assertEquals('', Html::cssFile('')); + } + + public function testJsFile() + { + $this->assertEquals('', Html::jsFile('http://example.com')); + $this->assertEquals('', Html::jsFile('')); + } + + public function testBeginForm() + { + $this->assertEquals('
', Html::beginForm()); + $this->assertEquals('', Html::beginForm('/example', 'get')); + $hiddens = array( + '', + '', + ); + $this->assertEquals('' . "\n" . implode("\n", $hiddens), Html::beginForm('/example?id=1&title=%3C', 'get')); + } + + public function testEndForm() + { + $this->assertEquals('
', Html::endForm()); + } + + public function testA() + { + $this->assertEquals('something<>', Html::a('something<>')); + $this->assertEquals('something', Html::a('something', '/example')); + $this->assertEquals('something', Html::a('something', '')); + } + + public function testMailto() + { + $this->assertEquals('test<>', Html::mailto('test<>')); + $this->assertEquals('test<>', Html::mailto('test<>', 'test>')); + } + + public function testImg() + { + $this->assertEquals('', Html::img('/example')); + $this->assertEquals('', Html::img('')); + $this->assertEquals('something', Html::img('/example', array('alt' => 'something', 'width' => 10))); + } + + public function testLabel() + { + $this->assertEquals('', Html::label('something<>')); + $this->assertEquals('', Html::label('something<>', 'a')); + $this->assertEquals('', Html::label('something<>', 'a', array('class' => 'test'))); + } + + public function testButton() + { + $this->assertEquals('', Html::button()); + $this->assertEquals('', Html::button('test', 'value', 'content<>')); + $this->assertEquals('', Html::button('test', 'value', 'content<>', array('type' => 'submit', 'class' => "t"))); + } + + public function testSubmitButton() + { + $this->assertEquals('', Html::submitButton()); + $this->assertEquals('', Html::submitButton('test', 'value', 'content<>', array('class' => 't'))); + } + + public function testResetButton() + { + $this->assertEquals('', Html::resetButton()); + $this->assertEquals('', Html::resetButton('test', 'value', 'content<>', array('class' => 't'))); + } + + public function testInput() + { + $this->assertEquals('', Html::input('text')); + $this->assertEquals('', Html::input('text', 'test', 'value', array('class' => 't'))); + } + + public function testButtonInput() + { + } + + public function testSubmitInput() + { + } + + public function testResetInput() + { + } + + public function testTextInput() + { + } + + public function testHiddenInput() + { + } + + public function testPasswordInput() + { + } + + public function testFileInput() + { + } + + public function testTextarea() + { + } + + public function testRadio() + { + } + + public function testCheckbox() + { + } + + public function testDropDownList() + { + } + + public function testListBox() + { + } + + public function testCheckboxList() + { + } + + public function testRadioList() + { + } + + public function testRenderOptions() + { + } + + public function testRenderAttributes() + { + } + + public function testUrl() + { + } +} diff --git a/tests/unit/runtime/.gitignore b/tests/unit/runtime/.gitignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/tests/unit/runtime/.gitignore @@ -0,0 +1 @@ +*