From edf983f4d76545b7ba38c1d79028f73c29932330 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Fri, 24 May 2013 12:45:42 +0200 Subject: [PATCH 001/107] added tabs --- framework/yii/bootstrap/Tabs.php | 171 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 framework/yii/bootstrap/Tabs.php diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php new file mode 100644 index 0000000..454eb48 --- /dev/null +++ b/framework/yii/bootstrap/Tabs.php @@ -0,0 +1,171 @@ + array('class'=>'nav-tabs'), + * 'items' => array( + * array( + * 'header' => 'One', + * 'content' => 'Anim pariatur cliche...', + * ), + * array( + * 'header' => 'Two', + * 'headerOptions' => array(...), + * 'content' => 'Anim pariatur cliche...', + * 'options' => array('id'=>'myveryownID'), + * ), + * array( + * 'header' => 'Dropdown', + * 'items' => array( + * array( + * 'header' => '@Dropdown1', + * 'content' => 'Anim pariatur cliche...', + * ), + * ), + * ), + * ), + * )); + * ``` + * + * @see http://twitter.github.io/bootstrap/javascript.html#tabs + * @author Antonio Ramirez + * @since 2.0 + */ +class Tabs extends Widget +{ + /** + * @var array list of tabs in the tabs widget. Each array element represents a single + * tab with the following structure: + * + * ```php + * array( + * // required, the header (HTML) of the tab + * 'header' => 'Tab label', + * // optional the HTML attributes of the tab header `LI` tag container + * 'headerOptions'=> array(...), + * // required, the content (HTML) of the tab + * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', + * // optional the HTML attributes of the tab content container + * 'options'=> array(...), + * // optional, an array of items so to dipslay a dropdown menu on the tab header + * // ***Important*** if `items` is set, then `content` will be ignored + * 'items'=> array(...) + * ) + * ``` + */ + public $items = array(); + /** + * @var int keeps track of the tabs count so to provide a correct id in case it has not been specified. + */ + protected $counter = 0; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + $this->addCssClass($this->options, 'nav'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderHeaders($this->items, $this->options) . "\n"; + $this->counter = 0; // reset tab counter + echo Html::beginTag('div', array('class' => 'tab-content')) . "\n"; + echo $this->renderContents($this->items) . "\n"; + echo Html::endTag('div') . "\n"; + $this->registerPlugin('tab'); + } + + /** + * @param array $items the items to render in the header. + * @param array $options the HTML attributes of the menu container. + * @return string the rendering result. + * @throws InvalidConfigException + */ + protected function renderHeaders($items, $options = array()) + { + $headers = array(); + + for ($i = 0, $count = count($items); $i < $count; $i++) { + $item = $items[$i]; + if (!isset($item['header'])) { + throw new InvalidConfigException("The 'header' option is required."); + } + $headerOptions = ArrayHelper::getValue($item, 'headerOptions', array()); + if ($this->counter === 0) { + $this->addCssClass($headerOptions, 'active'); + } + if (isset($item['items'])) { + $this->getView()->registerAssetBundle("yii/bootstrap/dropdown"); + $this->addCssClass($headerOptions, 'dropdown'); + $headers[] = Html::tag( + 'li', + Html::a($item['header'] . ' ', "#", array( + 'class' => 'dropdown-toggle', + 'data-toggle' => 'dropdown' + )) . + $this->renderHeaders($item['items'], array('class' => 'dropdown-menu')), + $headerOptions + ); + } else { + $contentOptions = ArrayHelper::getValue($item, 'options', array()); + $id = ArrayHelper::getValue($contentOptions, 'id', $this->options['id'] . '-tab' . $this->counter++); + $headers[] = Html::tag('li', Html::a($item['header'], "#$id", array('data-toggle' => 'tab')), $headerOptions); + } + } + + return Html::tag('ul', implode("\n", $headers), $options); + } + + /** + * Renders tabs contents as specified on [[items]]. + * @param array $items the items to get the contents from. + * @return string the rendering result. + * @throws InvalidConfigException + */ + protected function renderContents($items) + { + $contents = array(); + foreach ($items as $item) { + if (!isset($item['content']) && !isset($item['items'])) { + throw new InvalidConfigException("The 'content' option is required."); + } + $options = ArrayHelper::getValue($item, 'options', array()); + $this->addCssClass($options, 'tab-pane'); + + if ($this->counter === 0) { + $this->addCssClass($options, 'active'); + } + if (isset($item['items'])) { + $contents[] = $this->renderContents($item['items']); + } else { + $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $this->counter++); + $contents[] = Html::tag('div', $item['content'], $options); + } + } + + return implode("\n", $contents); + } +} \ No newline at end of file From 6316463ad00dd30748fe6e20b41335970f9c3f56 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Fri, 24 May 2013 14:17:46 +0200 Subject: [PATCH 002/107] add fixes --- framework/yii/bootstrap/Tabs.php | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 454eb48..8bc61da 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -70,10 +70,6 @@ class Tabs extends Widget * ``` */ public $items = array(); - /** - * @var int keeps track of the tabs count so to provide a correct id in case it has not been specified. - */ - protected $counter = 0; /** @@ -91,7 +87,6 @@ class Tabs extends Widget public function run() { echo $this->renderHeaders($this->items, $this->options) . "\n"; - $this->counter = 0; // reset tab counter echo Html::beginTag('div', array('class' => 'tab-content')) . "\n"; echo $this->renderContents($this->items) . "\n"; echo Html::endTag('div') . "\n"; @@ -101,20 +96,20 @@ class Tabs extends Widget /** * @param array $items the items to render in the header. * @param array $options the HTML attributes of the menu container. + * @param integer $index the starting index of header item. Used to set ids. * @return string the rendering result. * @throws InvalidConfigException */ - protected function renderHeaders($items, $options = array()) + protected function renderHeaders($items, $options = array(), $index = 0) { $headers = array(); - for ($i = 0, $count = count($items); $i < $count; $i++) { - $item = $items[$i]; + foreach ($items as $item) { if (!isset($item['header'])) { throw new InvalidConfigException("The 'header' option is required."); } $headerOptions = ArrayHelper::getValue($item, 'headerOptions', array()); - if ($this->counter === 0) { + if ($index === 0) { $this->addCssClass($headerOptions, 'active'); } if (isset($item['items'])) { @@ -126,12 +121,13 @@ class Tabs extends Widget 'class' => 'dropdown-toggle', 'data-toggle' => 'dropdown' )) . - $this->renderHeaders($item['items'], array('class' => 'dropdown-menu')), + $this->renderHeaders($item['items'], array('class' => 'dropdown-menu'), $index++), $headerOptions ); } else { + $contentOptions = ArrayHelper::getValue($item, 'options', array()); - $id = ArrayHelper::getValue($contentOptions, 'id', $this->options['id'] . '-tab' . $this->counter++); + $id = ArrayHelper::getValue($contentOptions, 'id', $this->options['id'] . '-tab' . $index++); $headers[] = Html::tag('li', Html::a($item['header'], "#$id", array('data-toggle' => 'tab')), $headerOptions); } } @@ -142,10 +138,11 @@ class Tabs extends Widget /** * Renders tabs contents as specified on [[items]]. * @param array $items the items to get the contents from. + * @param integer $index the starting index (for recursion) * @return string the rendering result. * @throws InvalidConfigException */ - protected function renderContents($items) + protected function renderContents($items, $index = 0) { $contents = array(); foreach ($items as $item) { @@ -155,13 +152,13 @@ class Tabs extends Widget $options = ArrayHelper::getValue($item, 'options', array()); $this->addCssClass($options, 'tab-pane'); - if ($this->counter === 0) { - $this->addCssClass($options, 'active'); - } if (isset($item['items'])) { - $contents[] = $this->renderContents($item['items']); + $contents[] = $this->renderContents($item['items'], $index++); } else { - $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $this->counter++); + if ($index === 0) { + $this->addCssClass($options, 'active'); + } + $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $index++); $contents[] = Html::tag('div', $item['content'], $options); } } From 39c2107599b9ef67651095b4bfd88e26fa09971a Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Sun, 26 May 2013 21:09:12 +0400 Subject: [PATCH 003/107] Refactoring --- framework/yii/jui/Tabs.php | 77 +++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 46 deletions(-) diff --git a/framework/yii/jui/Tabs.php b/framework/yii/jui/Tabs.php index 052ffe7..7b9d4d8 100644 --- a/framework/yii/jui/Tabs.php +++ b/framework/yii/jui/Tabs.php @@ -20,16 +20,32 @@ use yii\helpers\Html; * echo Tabs::widget(array( * 'items' => array( * array( - * 'header' => 'One', + * 'header' => 'Tab one', * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', * ), * array( - * 'header' => 'Two', - * 'headerOptions' => array(...), + * 'header' => 'Tab two', + * 'headerOptions' => array( + * 'tag' => 'li', + * ), * 'content' => 'Sed non urna. Phasellus eu ligula. Vestibulum sit amet purus...', - * 'options' => array(...), + * 'options' => array( + * 'tag' => 'div', + * ), * ), * ), + * 'options' => array( + * 'tag' => 'div', + * ), + * 'itemOptions' => array( + * 'tag' => 'div', + * ), + * 'headerOptions' => array( + * 'tag' => 'li', + * ), + * 'clientOptions' => array( + * 'collapsible' => false, + * ), * )); * ``` * @@ -39,24 +55,10 @@ use yii\helpers\Html; */ class Tabs extends Widget { - /** - * @var array list of tabs in the tabs widget. Each array element represents a single - * tab with the following structure: - * - * ```php - * array( - * // required, the header (HTML) of the tab - * 'header' => 'Tab label', - * // required, the content (HTML) of the tab - * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', - * // optional the HTML attributes of the tab content container - * 'options'=> array(...), - * // optional the HTML attributes of the tab header container - * 'headerOptions'=> array(...), - * ) - * ``` - */ + public $options = array(); public $items = array(); + public $itemOptions = array(); + public $headerOptions = array(); /** @@ -65,42 +67,24 @@ class Tabs extends Widget public function run() { echo Html::beginTag('div', $this->options) . "\n"; - echo $this->renderHeaders() . "\n"; - echo $this->renderContents() . "\n"; + echo $this->renderItems() . "\n"; echo Html::endTag('div') . "\n"; $this->registerWidget('tabs'); } /** - * Renders tabs headers as specified on [[items]]. + * Renders tab items as specified on [[items]]. * @return string the rendering result. * @throws InvalidConfigException. */ - protected function renderHeaders() + protected function renderItems() { $headers = array(); + $items = array(); foreach ($this->items as $n => $item) { if (!isset($item['header'])) { throw new InvalidConfigException("The 'header' option is required."); } - $options = ArrayHelper::getValue($item, 'options', array()); - $id = isset($options['id']) ? $options['id'] : $this->options['id'] . '-tab' . $n; - $headerOptions = ArrayHelper::getValue($item, 'headerOptions', array()); - $headers[] = Html::tag('li', Html::a($item['header'], "#$id"), $headerOptions); - } - - return Html::tag('ul', implode("\n", $headers)); - } - - /** - * Renders tabs contents as specified on [[items]]. - * @return string the rendering result. - * @throws InvalidConfigException. - */ - protected function renderContents() - { - $contents = array(); - foreach ($this->items as $n => $item) { if (!isset($item['content'])) { throw new InvalidConfigException("The 'content' option is required."); } @@ -108,9 +92,10 @@ class Tabs extends Widget if (!isset($options['id'])) { $options['id'] = $this->options['id'] . '-tab' . $n; } - $contents[] = Html::tag('div', $item['content'], $options); + $headerOptions = ArrayHelper::getValue($item, 'headerOptions', array()); + $headers[] = Html::tag('li', Html::a($item['header'], '#' . $options['id']), $headerOptions); + $items[] = Html::tag('div', $item['content'], $options); } - - return implode("\n", $contents); + return Html::tag('ul', implode("\n", $headers)) . "\n" . implode("\n", $items); } } From 73900b4bd10d8343e87a79ebbe83af6cca6e9764 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Sun, 26 May 2013 22:03:51 +0400 Subject: [PATCH 004/107] jQuery UI tabs rework --- framework/yii/jui/Tabs.php | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/framework/yii/jui/Tabs.php b/framework/yii/jui/Tabs.php index 7b9d4d8..c4ef3c5 100644 --- a/framework/yii/jui/Tabs.php +++ b/framework/yii/jui/Tabs.php @@ -20,19 +20,27 @@ use yii\helpers\Html; * echo Tabs::widget(array( * 'items' => array( * array( - * 'header' => 'Tab one', + * 'label' => 'Tab one', * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', * ), * array( - * 'header' => 'Tab two', - * 'headerOptions' => array( - * 'tag' => 'li', - * ), + * 'label' => 'Tab two', * 'content' => 'Sed non urna. Phasellus eu ligula. Vestibulum sit amet purus...', * 'options' => array( * 'tag' => 'div', * ), * ), + * array( + * 'label' => 'Tab three', + * 'content' => 'Specific content...', + * 'options' => array( + * 'id' => 'my-tab', + * ), + * ), + * array( + * 'label' => 'Ajax tab', + * 'url' => 'http://www.yiiframework.com', + * ), * ), * 'options' => array( * 'tag' => 'div', @@ -40,9 +48,6 @@ use yii\helpers\Html; * 'itemOptions' => array( * 'tag' => 'div', * ), - * 'headerOptions' => array( - * 'tag' => 'li', - * ), * 'clientOptions' => array( * 'collapsible' => false, * ), @@ -58,7 +63,7 @@ class Tabs extends Widget public $options = array(); public $items = array(); public $itemOptions = array(); - public $headerOptions = array(); + public $headerTemplate = '
  • {label}
  • '; /** @@ -76,6 +81,7 @@ class Tabs extends Widget * Renders tab items as specified on [[items]]. * @return string the rendering result. * @throws InvalidConfigException. + * @todo rework */ protected function renderItems() { From e84f5649e6c6ef18cf4ec0207485b56ee20cee2c Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Mon, 27 May 2013 00:26:23 +0400 Subject: [PATCH 005/107] jQuery UI tabs full rework --- framework/yii/jui/Tabs.php | 95 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 18 deletions(-) diff --git a/framework/yii/jui/Tabs.php b/framework/yii/jui/Tabs.php index c4ef3c5..b0f54d9 100644 --- a/framework/yii/jui/Tabs.php +++ b/framework/yii/jui/Tabs.php @@ -29,17 +29,20 @@ use yii\helpers\Html; * 'options' => array( * 'tag' => 'div', * ), + * 'headerOptions' => array( + * 'class' => 'my-class', + * ), * ), * array( - * 'label' => 'Tab three', - * 'content' => 'Specific content...', + * 'label' => 'Tab with custom id', + * 'content' => 'Morbi tincidunt, dui sit amet facilisis feugiat...', * 'options' => array( - * 'id' => 'my-tab', + * 'id' => 'my-tab', * ), * ), * array( * 'label' => 'Ajax tab', - * 'url' => 'http://www.yiiframework.com', + * 'ajax' => array('ajax/content'), * ), * ), * 'options' => array( @@ -48,6 +51,9 @@ use yii\helpers\Html; * 'itemOptions' => array( * 'tag' => 'div', * ), + * 'headerOptions' => array( + * 'class' => 'my-class', + * ), * 'clientOptions' => array( * 'collapsible' => false, * ), @@ -60,10 +66,52 @@ use yii\helpers\Html; */ class Tabs extends Widget { + /** + * @var array the HTML attributes for the widget container tag. The following special options are recognized: + * + * - tag: string, defaults to "div", the tag name of the container tag of this widget + */ public $options = array(); + /** + * @var array list of tab items. Each item can be an array of the following structure: + * + * ~~~ + * array( + * 'label' => 'Tab header label', + * 'content' => 'Tab item content', + * 'ajax' => 'http://www.yiiframework.com', //or array('ajax/action'), + * // the HTML attributes of the item container tag. This will overwrite "itemOptions". + * 'options' => array(), + * // the HTML attributes of the header container tag. This will overwrite "headerOptions". + * 'headerOptions' = array(), + * // @todo comment. + * 'template' + * ) + * ~~~ + */ public $items = array(); + /** + * @var array list of HTML attributes for the item container tags. This will be overwritten + * by the "options" set in individual [[items]]. The following special options are recognized: + * + * - tag: string, defaults to "div", the tag name of the item container tags. + */ public $itemOptions = array(); - public $headerTemplate = '
  • {label}
  • '; + /** + * @var array list of HTML attributes for the header container tags. This will be overwritten + * by the "headerOptions" set in individual [[items]]. + */ + public $headerOptions = array(); + /** + * @var string + * @todo comment. + */ + public $linkTemplate = '{label}'; + /** + * @var boolean + * @todo comment. + */ + public $encodeLabels = true; /** @@ -71,9 +119,11 @@ class Tabs extends Widget */ public function run() { - echo Html::beginTag('div', $this->options) . "\n"; + $options = $this->options; + $tag = ArrayHelper::remove($options, 'tag', 'div'); + echo Html::beginTag($tag, $options) . "\n"; echo $this->renderItems() . "\n"; - echo Html::endTag('div') . "\n"; + echo Html::endTag($tag) . "\n"; $this->registerWidget('tabs'); } @@ -81,26 +131,35 @@ class Tabs extends Widget * Renders tab items as specified on [[items]]. * @return string the rendering result. * @throws InvalidConfigException. - * @todo rework */ protected function renderItems() { $headers = array(); $items = array(); foreach ($this->items as $n => $item) { - if (!isset($item['header'])) { - throw new InvalidConfigException("The 'header' option is required."); - } - if (!isset($item['content'])) { - throw new InvalidConfigException("The 'content' option is required."); + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); } - $options = ArrayHelper::getValue($item, 'options', array()); - if (!isset($options['id'])) { - $options['id'] = $this->options['id'] . '-tab' . $n; + if (isset($item['ajax'])) { + $url = $item['ajax']; + } else { + if (!isset($item['content'])) { + throw new InvalidConfigException("The 'content' or 'ajax' option is required."); + } + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array())); + $tag = ArrayHelper::remove($options, 'tag', 'div'); + if (!isset($options['id'])) { + $options['id'] = $this->options['id'] . '-tab' . $n; + } + $url = '#' . $options['id']; + $items[] = Html::tag($tag, $item['content'], $options); } $headerOptions = ArrayHelper::getValue($item, 'headerOptions', array()); - $headers[] = Html::tag('li', Html::a($item['header'], '#' . $options['id']), $headerOptions); - $items[] = Html::tag('div', $item['content'], $options); + $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate); + $headers[] = Html::tag('li', strtr($template, array( + '{label}' => $this->encodeLabels ? Html::encode($item['label']) : $item['label'], + '{url}' => Html::url($url), + )), $headerOptions); } return Html::tag('ul', implode("\n", $headers)) . "\n" . implode("\n", $items); } From 5038c35f6dd8031d20b2da11cc9f0e9b622ed8d4 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Mon, 27 May 2013 01:45:36 +0400 Subject: [PATCH 006/107] Refactoring --- framework/yii/jui/Tabs.php | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/framework/yii/jui/Tabs.php b/framework/yii/jui/Tabs.php index b0f54d9..bbc3534 100644 --- a/framework/yii/jui/Tabs.php +++ b/framework/yii/jui/Tabs.php @@ -42,7 +42,7 @@ use yii\helpers\Html; * ), * array( * 'label' => 'Ajax tab', - * 'ajax' => array('ajax/content'), + * 'url' => array('ajax/content'), * ), * ), * 'options' => array( @@ -75,19 +75,13 @@ class Tabs extends Widget /** * @var array list of tab items. Each item can be an array of the following structure: * - * ~~~ - * array( - * 'label' => 'Tab header label', - * 'content' => 'Tab item content', - * 'ajax' => 'http://www.yiiframework.com', //or array('ajax/action'), - * // the HTML attributes of the item container tag. This will overwrite "itemOptions". - * 'options' => array(), - * // the HTML attributes of the header container tag. This will overwrite "headerOptions". - * 'headerOptions' = array(), - * // @todo comment. - * 'template' - * ) - * ~~~ + * - label: string, required, specifies the header link label. When [[encodeLabels]] is true, the label + * will be HTML-encoded. + * - content: string, @todo comment + * - url: mixed, @todo comment + * - template: string, optional, @todo comment + * - options: array, optional, @todo comment + * - headerOptions: array, optional, @todo comment */ public $items = array(); /** @@ -103,13 +97,11 @@ class Tabs extends Widget */ public $headerOptions = array(); /** - * @var string - * @todo comment. + * @var string @todo comment */ public $linkTemplate = '{label}'; /** - * @var boolean - * @todo comment. + * @var boolean whether the labels for header items should be HTML-encoded. */ public $encodeLabels = true; @@ -140,11 +132,11 @@ class Tabs extends Widget if (!isset($item['label'])) { throw new InvalidConfigException("The 'label' option is required."); } - if (isset($item['ajax'])) { - $url = $item['ajax']; + if (isset($item['url'])) { + $url = Html::url($item['url']); } else { if (!isset($item['content'])) { - throw new InvalidConfigException("The 'content' or 'ajax' option is required."); + throw new InvalidConfigException("The 'content' or 'url' option is required."); } $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array())); $tag = ArrayHelper::remove($options, 'tag', 'div'); @@ -158,7 +150,7 @@ class Tabs extends Widget $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate); $headers[] = Html::tag('li', strtr($template, array( '{label}' => $this->encodeLabels ? Html::encode($item['label']) : $item['label'], - '{url}' => Html::url($url), + '{url}' => $url, )), $headerOptions); } return Html::tag('ul', implode("\n", $headers)) . "\n" . implode("\n", $items); From 5fe6d9910f7bafad098d362f07498e1d3683d00a Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Tue, 28 May 2013 11:50:12 +0200 Subject: [PATCH 007/107] Added dropdown widget --- framework/yii/bootstrap/Dropdown.php | 116 +++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 framework/yii/bootstrap/Dropdown.php diff --git a/framework/yii/bootstrap/Dropdown.php b/framework/yii/bootstrap/Dropdown.php new file mode 100644 index 0000000..fa966d3 --- /dev/null +++ b/framework/yii/bootstrap/Dropdown.php @@ -0,0 +1,116 @@ + + * @since 2.0 + */ +class Dropdown extends Widget +{ + /** + * @var array list of menu items in the dropdown. Each array element represents a single + * menu with the following structure: + * + * ```php + * array( + * // required, the label of the item link + * 'label' => 'Menu label', + * // optional, url of the item link + * 'url' => '', + * // optional the HTML attributes of the item link + * 'urlOptions'=> array(...), + * // optional the HTML attributes of the item + * 'options'=> array(...), + * // optional, an array of items that configure a sub menu of the item + * // note: if `items` is set, then `url` of the parent item will be ignored and automatically set to "#" + * 'items'=> array(...) + * ) + * ``` + * If you wish to display a `divider`, use any string. The widget will render a bootstrap dropdown divider: + * + * ```html + *
  • + * ``` + */ + public $items = array(); + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + $this->addCssClass($this->options, 'dropdown-menu'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('ul', $this->options) . "\n"; + echo $this->renderContents() . "\n"; + echo Html::endTag('ul') . "\n"; + $this->registerPlugin('dropdown'); + } + + /** + * Renders dropdown contents as specified on [[items]]. + * @return string the rendering result. + * @throws InvalidConfigException + */ + protected function renderContents() + { + $contents = array(); + foreach ($this->items as $item) { + if (is_string($item)) { + $contents[] = Html::tag('li', '', array('class' => 'divider')); + continue; + } + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); + } + + $options = ArrayHelper::getValue($item, 'options', array()); + $urlOptions = ArrayHelper::getValue($item, 'urlOptions', array()); + $urlOptions['tabindex'] = '-1'; + + if (isset($item['items'])) { + $this->addCssClass($options, 'dropdown-submenu'); + $content = Html::a($item['label'], '#', $urlOptions) . $this->dropdown($item['items']); + } else { + $content = Html::a($item['label'], ArrayHelper::getValue($item, 'url', '#'), $urlOptions); + } + $contents[] = Html::tag('li', $content , $options); + } + + return implode("\n", $contents); + } + + /** + * Generates a dropdown menu. + * @param array $items the configuration of the dropdown items. See [[items]]. + * @return string the generated dropdown menu + * @see items + */ + protected function dropdown($items) + { + return Dropdown::widget(array('items' => $items, 'clientOptions' => false)); + } +} \ No newline at end of file From af5dbba6dae3c145e8dd00ea9d4bc5c1410c8fe4 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Tue, 28 May 2013 11:59:55 +0200 Subject: [PATCH 008/107] Tabs refactored --- framework/yii/bootstrap/Tabs.php | 165 +++++++++++++++++++++++++++------------ 1 file changed, 115 insertions(+), 50 deletions(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 8bc61da..0f049fd 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -32,10 +32,15 @@ use yii\helpers\Html; * ), * array( * 'header' => 'Dropdown', - * 'items' => array( + * 'dropdown' => array( * array( - * 'header' => '@Dropdown1', - * 'content' => 'Anim pariatur cliche...', + * 'label' => 'DropdownA', + * 'content' => 'DropdownA, Anim pariatur cliche...', + * ), + * '-', // divider + * array( + * 'label' => 'DropdownB', + * 'content' => 'DropdownB, Anim pariatur cliche...', * ), * ), * ), @@ -63,9 +68,14 @@ class Tabs extends Widget * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', * // optional the HTML attributes of the tab content container * 'options'=> array(...), - * // optional, an array of items so to dipslay a dropdown menu on the tab header - * // ***Important*** if `items` is set, then `content` will be ignored - * 'items'=> array(...) + * // optional, an array of [[Dropdown]] widget items so to display a dropdown menu on the tab header. This + * // attribute, apart from the original [[Dropdown::items]] settings, also has two extra special keys: + * // - content: required, teh content (HTML) of teh tab the menu item is linked to + * // - contentOptions: optional the HTML attributes of the tab content container + * // note: if `dropdown` is set, then `content` will be ignored + * // important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown + * // @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 + * 'dropdown'=> array(...) * ) * ``` */ @@ -79,6 +89,8 @@ class Tabs extends Widget { parent::init(); $this->addCssClass($this->options, 'nav'); + $this->items = $this->normalizeItems(); + } /** @@ -86,83 +98,136 @@ class Tabs extends Widget */ public function run() { - echo $this->renderHeaders($this->items, $this->options) . "\n"; + echo Html::beginTag('ul', $this->options) . "\n"; + echo $this->renderHeaders() . "\n"; + echo Html::endTag('ul'); echo Html::beginTag('div', array('class' => 'tab-content')) . "\n"; - echo $this->renderContents($this->items) . "\n"; + echo $this->renderContents() . "\n"; echo Html::endTag('div') . "\n"; $this->registerPlugin('tab'); } /** - * @param array $items the items to render in the header. - * @param array $options the HTML attributes of the menu container. - * @param integer $index the starting index of header item. Used to set ids. + * Renders tabs navigation. * @return string the rendering result. - * @throws InvalidConfigException */ - protected function renderHeaders($items, $options = array(), $index = 0) + protected function renderHeaders() { $headers = array(); - - foreach ($items as $item) { - if (!isset($item['header'])) { - throw new InvalidConfigException("The 'header' option is required."); - } - $headerOptions = ArrayHelper::getValue($item, 'headerOptions', array()); - if ($index === 0) { - $this->addCssClass($headerOptions, 'active'); - } - if (isset($item['items'])) { - $this->getView()->registerAssetBundle("yii/bootstrap/dropdown"); - $this->addCssClass($headerOptions, 'dropdown'); + foreach ($this->items['headers'] as $item) { + $options = ArrayHelper::getValue($item, 'options', array()); + if (isset($item['dropdown'])) { $headers[] = Html::tag( 'li', Html::a($item['header'] . ' ', "#", array( 'class' => 'dropdown-toggle', 'data-toggle' => 'dropdown' )) . - $this->renderHeaders($item['items'], array('class' => 'dropdown-menu'), $index++), - $headerOptions + Dropdown::widget(array('items' => $item['dropdown'], 'clientOptions' => false)), + $options ); - } else { - - $contentOptions = ArrayHelper::getValue($item, 'options', array()); - $id = ArrayHelper::getValue($contentOptions, 'id', $this->options['id'] . '-tab' . $index++); - $headers[] = Html::tag('li', Html::a($item['header'], "#$id", array('data-toggle' => 'tab')), $headerOptions); + continue; } - } + $id = ArrayHelper::getValue($item, 'url'); + $headers[] = Html::tag('li', Html::a($item['header'], "{$id}", array('data-toggle' => 'tab')), $options); - return Html::tag('ul', implode("\n", $headers), $options); + } + return implode("\n", $headers); } /** - * Renders tabs contents as specified on [[items]]. - * @param array $items the items to get the contents from. - * @param integer $index the starting index (for recursion) + * Renders tabs contents. * @return string the rendering result. - * @throws InvalidConfigException */ - protected function renderContents($items, $index = 0) + protected function renderContents() { $contents = array(); - foreach ($items as $item) { - if (!isset($item['content']) && !isset($item['items'])) { - throw new InvalidConfigException("The 'content' option is required."); - } + foreach ($this->items['contents'] as $item) { $options = ArrayHelper::getValue($item, 'options', array()); $this->addCssClass($options, 'tab-pane'); + $contents[] = Html::tag('div', $item['content'], $options); + + } + return implode("\n", $contents); + } + + /** + * Normalizes the [[items]] property to divide headers from contents and to ease its rendering when there are + * headers with dropdown menus. + * @return array the normalized tabs items + * @throws InvalidConfigException + */ + protected function normalizeItems() + { + $items = array(); + $index = 0; + foreach ($this->items as $item) { + if (!isset($item['header'])) { + throw new InvalidConfigException("The 'header' option is required."); + } + if (!isset($item['content']) && !isset($item['dropdown'])) { + throw new InvalidConfigException("The 'content' option is required."); + } + $header = $content = array(); + $header['header'] = ArrayHelper::getValue($item, 'header'); + $header['options'] = ArrayHelper::getValue($item, 'headerOptions', array()); + if ($index === 0) { + $this->addCssClass($header['options'], 'active'); + } + if (isset($item['dropdown'])) { + $this->addCssClass($header['options'], 'dropdown'); + + $self = $this; + $dropdown = function ($list) use (&$dropdown, &$items, &$index, $self) { + $ddItems = $content = array(); + foreach ($list as $item) { + if (is_string($item)) { + $ddItems[] = $item; + continue; + } + if (!isset($item['content']) && !isset($item['items'])) { + throw new InvalidConfigException("The 'content' option is required."); + } + if (isset($item['items'])) { + $item['items'] = $dropdown($item['items']); + } else { + $content['content'] = ArrayHelper::remove($item, 'content'); + $content['options'] = ArrayHelper::remove($item, 'contentOptions', array()); + if ($index === 0) { + $self->addCssClass($content['options'], 'active'); + $self->addCssClass($item['options'], 'active'); + } + $content['options']['id'] = ArrayHelper::getValue( + $content['options'], + 'id', + $self->options['id'] . '-tab' . $index++); + $item['url'] = '#' . $content['options']['id']; + $item['urlOptions']['data-toggle'] = 'tab'; + + $items['contents'][] = $content; + } + $ddItems[] = $item; + } + return $ddItems; + }; + $header['dropdown'] = $dropdown($item['dropdown']); - if (isset($item['items'])) { - $contents[] = $this->renderContents($item['items'], $index++); } else { + $content['content'] = ArrayHelper::getValue($item, 'content'); + $content['options'] = ArrayHelper::getValue($item, 'options', array()); if ($index === 0) { - $this->addCssClass($options, 'active'); + $this->addCssClass($content['options'], 'active'); } - $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $index++); - $contents[] = Html::tag('div', $item['content'], $options); + $content['options']['id'] = ArrayHelper::getValue( + $content['options'], + 'id', + $this->options['id'] . '-tab' . $index++); + + $header['url'] = "#" . ArrayHelper::getValue($content['options'], 'id'); + $items['contents'][] = $content; } + $items['headers'][] = $header; } - - return implode("\n", $contents); + return $items; } } \ No newline at end of file From cc9b175c5f9637e194b86f831857f1181a502d8f Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Tue, 28 May 2013 16:24:37 +0200 Subject: [PATCH 009/107] Added Nav widget closes #367 --- framework/yii/bootstrap/Nav.php | 137 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 framework/yii/bootstrap/Nav.php diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php new file mode 100644 index 0000000..f373961 --- /dev/null +++ b/framework/yii/bootstrap/Nav.php @@ -0,0 +1,137 @@ + array( + * array( + * 'label' => 'Home', + * 'url' => '/', + * 'options' => array(...), + * 'active' => true, + * ), + * array( + * 'label' => 'Dropdown', + * 'dropdown' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '#', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#', + * ), + * ), + * ), + * ), + * )); + * ``` + * + * @see http://twitter.github.io/bootstrap/components.html#nav + * @author Antonio Ramirez + * @since 2.0 + */ +class Nav extends Widget +{ + /** + * @var array list of items in the nav widget. Each array element represents a single + * menu item with the following structure: + * + * ```php + * array( + * // required, the menu item label. + * 'label' => 'Nav item label', + * // optional, the URL of the menu item. Defaults to "#" + * 'url'=> '#', + * // optional, the HTML options of the URL. + * 'urlOptions' => array(...), + * // optional the HTML attributes of the item container (LI). + * 'options' => array(...), + * // optional, an array of [[Dropdown]] widget items so to display a dropdown menu on the tab header. + * // important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown + * // @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 + * 'dropdown'=> array(...) + * ) + * ``` + * + * Optionally, you can also use a plain string instead of an array element. + */ + public $items = array(); + + + /** + * Initializes the widget. + */ + public function init() + { + $this->addCssClass($this->options, 'nav'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderItems(); + } + + /** + * Renders widget items. + */ + public function renderItems() + { + $items = array(); + foreach ($this->items as $item) { + $items[] = $this->renderItem($item); + } + + return Html::tag('ul', implode("\n", $items), $this->options); + } + + /** + * Renders a widget's item. + * @param mixed $item the item to render. + * @return string the rendering result. + * @throws InvalidConfigException + */ + public function renderItem($item) + { + if (is_string($item)) { + return $item; + } + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); + } + $label = $item['label']; + $url = ArrayHelper::getValue($item, 'url', '#'); + $options = ArrayHelper::getValue($item, 'options', array()); + $urlOptions = ArrayHelper::getValue($item, 'urlOptions', array()); + $dropdown = null; + + // does it has a dropdown widget? + if (isset($item['dropdown'])) { + $urlOptions['data-toggle'] = 'dropdown'; + $this->addCssClass($options, 'dropdown'); + $this->addCssClass($urlOptions, 'dropdown-toggle'); + $label .= ' ' . Html::tag('b', '', array('class' => 'caret')); + $dropdown = Dropdown::widget(array('items' => $item['dropdown'], 'clientOptions' => false)); + } + + return Html::tag('li', Html::a($label, $url, $urlOptions) . $dropdown, $options); + } +} \ No newline at end of file From 70f1d4169996c1259bfa02d8627c2f5dd0d34c0f Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Tue, 28 May 2013 16:27:33 +0200 Subject: [PATCH 010/107] Added NavBar widget closes #437 --- framework/yii/bootstrap/NavBar.php | 169 +++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 framework/yii/bootstrap/NavBar.php diff --git a/framework/yii/bootstrap/NavBar.php b/framework/yii/bootstrap/NavBar.php new file mode 100644 index 0000000..1ed5a9a --- /dev/null +++ b/framework/yii/bootstrap/NavBar.php @@ -0,0 +1,169 @@ + array( + * // a Nav widget + * array( + * // defaults to Nav anyway. + * 'class' => 'yii\bootstrap\Nav', + * // widget configuration + * 'options' => array( + * 'items' => array( + * array( + * 'label' => 'Home', + * 'url' => '/', + * 'options' => array('class' => 'active'), + * ), + * array( + * 'label' => 'Dropdown', + * // configure a dropdown menu + * 'dropdown' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '#', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#' + * ), + * ) + * ), + * ) + * ), + * ), + * // you can also use strings + * '', + * ), + * )); + * ``` + * + * @see http://twitter.github.io/bootstrap/components.html#navbar + * @author Antonio Ramirez + * @since 2.0 + */ +class NavBar extends Widget +{ + public $brand; + /** + * @var array list of menu items in the navbar widget. Each array element represents a single + * menu item with the following structure: + * + * ```php + * array( + * // optional, the menu item class type of the widget to render. Defaults to "Nav" widget. + * 'class' => 'Menu item class type', + * // required, the configuration options of the widget. + * 'options'=> array(...), + * ), + * // optionally, you can pass a string + * '', + * ``` + * + * Optionally, you can also use a plain string instead of an array element. + */ + public $items = array(); + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + $this->addCssClass($this->options, 'navbar'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', $this->options); + echo $this->renderItems(); + echo Html::endTag('div'); + } + + /** + * @return string the rendering items. + */ + protected function renderItems() + { + $items = array(); + + foreach ($this->items as $item) { + $items[] = $this->renderItem($item); + } + $contents =implode("\n", $items); + if (self::$responsive === true) { + $this->getView()->registerAssetBundle('yii/bootstrap/collapse'); + $contents = + Html::tag('div', + $this->renderToggleButton() . + $this->brand . "\n" . + Html::tag('div', $contents, array('class' => 'nav-collapse collapse navbar-collapse')), + array('class' => 'container')); + + } else { + $contents = $this->brand . "\n" . $contents; + } + return Html::tag('div', $contents, array('class' => 'navbar-inner')); + } + + /** + * Renders a item. The item can be a string, a custom class or a Nav widget (defaults if no class specified. + * @param mixed $item the item to render. If array, it is assumed the configuration of a widget being `class` + * required and if not specified, then defaults to `yii\bootstrap\Nav`. + * @return string the rendering result. + * @throws InvalidConfigException + */ + protected function renderItem($item) + { + if (is_string($item)) { + return $item; + } + $config = ArrayHelper::getValue($item, 'options', array()); + $config['class'] = ArrayHelper::getValue($item, 'class', 'yii\bootstrap\Nav'); + $widget = \Yii::createObject($config); + ob_start(); + $widget->run(); + return ob_get_clean(); + } + + /** + * Renders collapsible toggle button. + * @return string the rendering toggle button. + */ + protected function renderToggleButton() + { + $items = array(); + for ($i = 0; $i < 3; $i++) { + $items[] = Html::tag('span', '', array('class' => 'icon-bar')); + } + return Html::tag('a', implode("\n", $items), array( + 'class' => 'btn btn-navbar', + 'data-toggle' => 'collapse', + 'data-target' => 'div.navbar-collapse', + )) . "\n"; + } +} \ No newline at end of file From e1b145a7068b886764efc4b0dfb9de4fa0526bca Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Tue, 28 May 2013 17:12:27 +0200 Subject: [PATCH 011/107] Enhance Dropdown to use items as strings. Remove string=divider rule --- framework/yii/bootstrap/Dropdown.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/framework/yii/bootstrap/Dropdown.php b/framework/yii/bootstrap/Dropdown.php index fa966d3..7a8cd51 100644 --- a/framework/yii/bootstrap/Dropdown.php +++ b/framework/yii/bootstrap/Dropdown.php @@ -40,11 +40,7 @@ class Dropdown extends Widget * 'items'=> array(...) * ) * ``` - * If you wish to display a `divider`, use any string. The widget will render a bootstrap dropdown divider: - * - * ```html - *
  • - * ``` + * Additionally, you can also configure a dropdown item as string. */ public $items = array(); @@ -80,7 +76,7 @@ class Dropdown extends Widget $contents = array(); foreach ($this->items as $item) { if (is_string($item)) { - $contents[] = Html::tag('li', '', array('class' => 'divider')); + $contents[] = $item; continue; } if (!isset($item['label'])) { From ff70e151d9c9b9eb8f63f9dfd8c12361dc57bb35 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Tue, 28 May 2013 20:47:39 +0400 Subject: [PATCH 012/107] Comment for content --- framework/yii/jui/Tabs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/jui/Tabs.php b/framework/yii/jui/Tabs.php index bbc3534..c7a4b5a 100644 --- a/framework/yii/jui/Tabs.php +++ b/framework/yii/jui/Tabs.php @@ -77,7 +77,7 @@ class Tabs extends Widget * * - label: string, required, specifies the header link label. When [[encodeLabels]] is true, the label * will be HTML-encoded. - * - content: string, @todo comment + * - content: string, the content to show when corresponding tab is clicked. Can be omitted if url is specified. * - url: mixed, @todo comment * - template: string, optional, @todo comment * - options: array, optional, @todo comment From 000fdc3de067a8e4b7314a21edf3815f86371e7b Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 17:27:37 -0400 Subject: [PATCH 013/107] formatter WIP. --- framework/yii/base/Formatter.php | 288 ++++++++++++++++++++++++++++ framework/yii/i18n/Formatter.php | 232 ++++++++++++++++++++++ tests/unit/framework/base/FormatterTest.php | 101 ++++++++++ 3 files changed, 621 insertions(+) create mode 100644 framework/yii/base/Formatter.php create mode 100644 framework/yii/i18n/Formatter.php create mode 100644 tests/unit/framework/base/FormatterTest.php diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php new file mode 100644 index 0000000..a31fac9 --- /dev/null +++ b/framework/yii/base/Formatter.php @@ -0,0 +1,288 @@ + + * @since 2.0 + */ +class Formatter extends Component +{ + /** + * @var string the default format string to be used to format a date using PHP date() function. + */ + public $dateFormat = 'Y/m/d'; + /** + * @var string the default format string to be used to format a time using PHP date() function. + */ + public $timeFormat = 'h:i:s A'; + /** + * @var string the default format string to be used to format a date and time using PHP date() function. + */ + public $datetimeFormat = 'Y/m/d h:i:s A'; + /** + * @var array the format used to format a number with PHP number_format() function. + * Three elements may be specified: "decimals", "decimalSeparator" and "thousandSeparator". + * They correspond to the number of digits after the decimal point, the character displayed as the decimal point + * and the thousands separator character. + */ + public $numberFormat = array('decimals' => null, 'decimalSeparator' => null, 'thousandSeparator' => null); + /** + * @var array the text to be displayed when formatting a boolean value. The first element corresponds + * to the text display for false, the second element for true. Defaults to array('No', 'Yes'). + */ + public $booleanFormat; + + + /** + * Initializes the component. + */ + public function init() + { + if (empty($this->booleanFormat)) { + $this->booleanFormat = array(Yii::t('yii', 'No'), Yii::t('yii', 'Yes')); + } + } + + /** + * Formats the value as is without any formatting. + * This method simply returns back the parameter without any format. + * @param mixed $value the value to be formatted + * @return string the formatted result + */ + public function asRaw($value) + { + return $value; + } + + /** + * Formats the value as an HTML-encoded plain text. + * @param mixed $value the value to be formatted + * @return string the formatted result + */ + public function asText($value) + { + return Html::encode($value); + } + + /** + * Formats the value as an HTML-encoded plain text with newlines converted into breaks. + * @param mixed $value the value to be formatted + * @return string the formatted result + */ + public function asNtext($value) + { + return nl2br(Html::encode($value)); + } + + /** + * Formats the value as HTML-encoded text paragraphs. + * Each text paragraph is enclosed within a `

    ` tag. + * One or multiple consecutive empty lines divide two paragraphs. + * @param mixed $value the value to be formatted + * @return string the formatted result + */ + public function asParagraphs($value) + { + return str_replace('

    ', '', + '

    ' . preg_replace('/[\r\n]{2,}/', "

    \n

    ", Html::encode($value)) . '

    ' + ); + } + + /** + * Formats the value as HTML text. + * The value will be purified using [[HtmlPurifier]] to avoid XSS attacks. + * Use [[asRaw()]] if you do not want any purification of the value. + * @param mixed $value the value to be formatted + * @param array|null $config the configuration for the HTMLPurifier class. + * @return string the formatted result + */ + public function asHtml($value, $config = null) + { + return HtmlPurifier::process($value, $config); + } + + /** + * Formats the value as a mailto link. + * @param mixed $value the value to be formatted + * @return string the formatted result + */ + public function asEmail($value) + { + return Html::mailto($value); + } + + /** + * Formats the value as an image tag. + * @param mixed $value the value to be formatted + * @return string the formatted result + */ + public function asImage($value) + { + return Html::img($value); + } + + /** + * Formats the value as a hyperlink. + * @param mixed $value the value to be formatted + * @return string the formatted result + */ + public function asUrl($value) + { + $url = $value; + if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) { + $url = 'http://' . $url; + } + return Html::a(Html::encode($value), $url); + } + + /** + * Formats the value as a boolean. + * @param mixed $value the value to be formatted + * @return string the formatted result + * @see booleanFormat + */ + public function asBoolean($value) + { + return $value ? $this->booleanFormat[1] : $this->booleanFormat[0]; + } + + /** + * Formats the value as a date. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing UNIX timestamp + * - a string that can be parsed into a UNIX timestamp via `strtotime()` + * - a PHP DateTime object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[dateFormat]] will be used. The format string should be the one + * that can be recognized by the PHP `date()` function. + * @return string the formatted result + * @see dateFormat + */ + public function asDate($value, $format = null) + { + $value = $this->normalizeDatetimeValue($value); + return date($format === null ? $this->dateFormat : $format, $value); + } + + /** + * Formats the value as a time. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing UNIX timestamp + * - a string that can be parsed into a UNIX timestamp via `strtotime()` + * - a PHP DateTime object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[timeFormat]] will be used. The format string should be the one + * that can be recognized by the PHP `date()` function. + * @return string the formatted result + * @see timeFormat + */ + public function asTime($value, $format = null) + { + $value = $this->normalizeDatetimeValue($value); + return date($format === null ? $this->timeFormat : $format, $value); + } + + /** + * Formats the value as a datetime. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing UNIX timestamp + * - a string that can be parsed into a UNIX timestamp via `strtotime()` + * - a PHP DateTime object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[datetimeFormat]] will be used. The format string should be the one + * that can be recognized by the PHP `date()` function. + * @return string the formatted result + * @see datetimeFormat + */ + public function asDatetime($value, $format = null) + { + $value = $this->normalizeDatetimeValue($value); + return date($format === null ? $this->datetimeFormat : $format, $value); + } + + /** + * Normalizes the given datetime value as one that can be taken by various date/time formatting methods. + * @param mixed $value the datetime value to be normalized. + * @return mixed the normalized datetime value + */ + protected function normalizeDatetimeValue($value) + { + if (is_string($value)) { + if (ctype_digit($value) || $value[0] === '-' && ctype_digit(substr($value, 1))) { + return (int)$value; + } else { + return strtotime($value); + } + } elseif ($value instanceof DateTime) { + return $value->getTimestamp(); + } else { + return (int)$value; + } + } + + /** + * Formats the value as an integer. + * @param mixed $value the value to be formatted + * @return string the formatting result. + */ + public function asInteger($value) + { + if (is_string($value) && preg_match('/^(-?\d+)/', $value, $matches)) { + return $matches[1]; + } else { + $value = (int)$value; + return "$value"; + } + } + + /** + * Formats the value as a double number. + * @param mixed $value the value to be formatted + * @param integer $decimals the number of digits after the decimal point + * @return string the formatting result. + */ + public function asDouble($value, $decimals = 2) + { + return sprintf("%.{$decimals}f", $value); + } + + /** + * Formats the value as a decimal number using the PHP number_format() function. + * @param mixed $value the value to be formatted + * @param integer $decimals the number of digits after the decimal point + * @param string $decimalSeparator the character displayed as the decimal point + * @param string $thousandSeparator the character displayed as the thousands separator character. + * @return string the formatted result + */ + public function asDecimal($value, $decimals = 0 , $decimalSeparator = '.' , $thousandSeparator = ',' ) + { + return number_format($value, $decimals, $decimalSeparator, $thousandSeparator); + } +} diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php new file mode 100644 index 0000000..a0570a0 --- /dev/null +++ b/framework/yii/i18n/Formatter.php @@ -0,0 +1,232 @@ + + * @since 2.0 + */ +class Formatter extends \yii\base\Formatter +{ + /** + * @var string the locale ID that is used to localize the date and number formatting. + * If not set, [[\yii\base\Application::language]] will be used. + */ + public $locale; + /** + * @var string the default format string to be used to format a date using PHP date() function. + */ + public $dateFormat = 'short'; + /** + * @var string the default format string to be used to format a time using PHP date() function. + */ + public $timeFormat = 'short'; + /** + * @var string the default format string to be used to format a date and time using PHP date() function. + */ + public $datetimeFormat = 'short'; + /** + * @var array the options to be set for the NumberFormatter objects. Please refer to + */ + public $numberFormatOptions = array(); + + + /** + * Initializes the component. + * This method will check if the "intl" PHP extension is installed and set the + * default value of [[locale]]. + * @throws InvalidConfigException if the "intl" PHP extension is not installed. + */ + public function init() + { + if (!extension_loaded('intl')) { + throw new InvalidConfigException('The "intl" PHP extension is not install. It is required to format data values in localized formats.'); + } + if ($this->locale === null) { + $this->locale = Yii::$app->language; + } + parent::init(); + } + + private $_dateFormats = array( + 'short' => IntlDateFormatter::SHORT, + 'medium' => IntlDateFormatter::MEDIUM, + 'long' => IntlDateFormatter::LONG, + 'full' => IntlDateFormatter::FULL, + ); + + /** + * Formats the value as a date. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing UNIX timestamp + * - a string that can be parsed into a UNIX timestamp via `strtotime()` + * - a PHP DateTime object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[dateFormat]] will be used. The format string should be the one + * that can be recognized by the PHP `date()` function. + * @return string the formatted result + * @see dateFormat + */ + public function asDate($value, $format = null) + { + $value = $this->normalizeDatetimeValue($value); + if ($format === null) { + $format = $this->dateFormat; + } + if (isset($this->_dateFormats[$format])) { + $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE); + } else { + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE); + $formatter->setPattern($format); + } + return $formatter->format($value); + } + + /** + * Formats the value as a time. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing UNIX timestamp + * - a string that can be parsed into a UNIX timestamp via `strtotime()` + * - a PHP DateTime object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[dateFormat]] will be used. The format string should be the one + * that can be recognized by the PHP `date()` function. + * @return string the formatted result + * @see timeFormat + */ + public function asTime($value, $format = null) + { + $value = $this->normalizeDatetimeValue($value); + if ($format === null) { + $format = $this->timeFormat; + } + if (isset($this->_dateFormats[$format])) { + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format]); + } else { + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE); + $formatter->setPattern($format); + } + return $formatter->format($value); + } + + /** + * Formats the value as a datetime. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing UNIX timestamp + * - a string that can be parsed into a UNIX timestamp via `strtotime()` + * - a PHP DateTime object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[dateFormat]] will be used. The format string should be the one + * that can be recognized by the PHP `date()` function. + * @return string the formatted result + * @see datetimeFormat + */ + public function asDatetime($value, $format = null) + { + $value = $this->normalizeDatetimeValue($value); + if ($format === null) { + $format = $this->datetimeFormat; + } + if (isset($this->_dateFormats[$format])) { + $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format]); + } else { + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE); + $formatter->setPattern($format); + } + return $formatter->format($value); + } + + /** + * Formats the value as a decimal number. + * @param mixed $value the value to be formatted + * @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details) + * for details on how to specify a format. + * @return string the formatted result. + */ + public function asDecimal($value, $format = null) + { + $this->createNumberFormatter(NumberFormatter::DECIMAL, $format)->format($value); + } + + /** + * Formats the value as a currency number. + * @param mixed $value the value to be formatted + * @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use. + * @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details) + * for details on how to specify a format. + * @return string the formatted result. + */ + public function asCurrency($value, $currency = 'USD', $format = null) + { + $this->createNumberFormatter(NumberFormatter::CURRENCY, $format)->formatCurrency($value, $currency); + } + + /** + * Formats the value as a percent number. + * @param mixed $value the value to be formatted + * @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details) + * for details on how to specify a format. + * @return string the formatted result. + */ + public function asPercent($value, $format = null) + { + $this->createNumberFormatter(NumberFormatter::PERCENT, $format)->format($value); + } + + /** + * Formats the value as a scientific number. + * @param mixed $value the value to be formatted + * @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details) + * for details on how to specify a format. + * @return string the formatted result. + */ + public function asScientific($value, $format = null) + { + $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format)->format($value); + } + + /** + * Creates a number formatter based on the given type and format. + * @param integer $type the type of the number formatter + * @param string $format the format to be used + * @return NumberFormatter the created formatter instance + */ + protected function createNumberFormatter($type, $format) + { + $formatter = new NumberFormatter($this->locale, $type); + if ($format !== null) { + $formatter->setPattern($format); + } + if (!empty($this->numberFormatOptions)) { + foreach ($this->numberFormatOptions as $name => $attribute) { + $formatter->setAttribute($name, $attribute); + } + } + return $formatter; + } +} diff --git a/tests/unit/framework/base/FormatterTest.php b/tests/unit/framework/base/FormatterTest.php new file mode 100644 index 0000000..53d8770 --- /dev/null +++ b/tests/unit/framework/base/FormatterTest.php @@ -0,0 +1,101 @@ + + * @since 2.0 + */ +class FormatterTest extends TestCase +{ + /** + * @var Formatter + */ + protected $formatter; + + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + $this->formatter = new Formatter(); + } + + protected function tearDown() + { + parent::tearDown(); + $this->formatter = null; + } + + public function testAsRaw() + { + $value = '123'; + $this->assertSame($value, $this->formatter->asRaw($value)); + $value = 123; + $this->assertSame($value, $this->formatter->asRaw($value)); + $value = '<>'; + $this->assertSame($value, $this->formatter->asRaw($value)); + } + + public function testAsText() + { + $value = '123'; + $this->assertSame($value, $this->formatter->asText($value)); + $value = 123; + $this->assertSame("$value", $this->formatter->asText($value)); + $value = '<>'; + $this->assertSame('<>', $this->formatter->asText($value)); + } + + public function testAsNtext() + { + $value = '123'; + $this->assertSame($value, $this->formatter->asNtext($value)); + $value = 123; + $this->assertSame("$value", $this->formatter->asNtext($value)); + $value = '<>'; + $this->assertSame('<>', $this->formatter->asNtext($value)); + $value = "123\n456"; + $this->assertSame("123
    \n456", $this->formatter->asNtext($value)); + } + + public function testAsParagraphs() + { + $value = '123'; + $this->assertSame("

    $value

    ", $this->formatter->asParagraphs($value)); + $value = 123; + $this->assertSame("

    $value

    ", $this->formatter->asParagraphs($value)); + $value = '<>'; + $this->assertSame('

    <>

    ', $this->formatter->asParagraphs($value)); + $value = "123\n456"; + $this->assertSame("

    123\n456

    ", $this->formatter->asParagraphs($value)); + $value = "123\n\n456"; + $this->assertSame("

    123

    \n

    456

    ", $this->formatter->asParagraphs($value)); + $value = "123\n\n\n456"; + $this->assertSame("

    123

    \n

    456

    ", $this->formatter->asParagraphs($value)); + } + + public function testAsHtml() + { + // todo + } + + public function testAsEmail() + { + $value = 'test@sample.com'; + $this->assertSame("$value", $this->formatter->asEmail($value)); + } + + public function testAsImage() + { + $value = 'http://sample.com/img.jpg'; + $this->assertSame("\"\"", $this->formatter->asImage($value)); + } +} From 9a970370129000ebd1b416a7ce05a62056a266c8 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 18:55:26 -0400 Subject: [PATCH 014/107] Finished Formatter. --- framework/yii/base/Formatter.php | 12 +--- framework/yii/i18n/Formatter.php | 8 +-- tests/unit/framework/base/FormatterTest.php | 59 +++++++++++++++++- tests/unit/framework/i18n/FormatterTest.php | 93 +++++++++++++++++++++++++++++ 4 files changed, 158 insertions(+), 14 deletions(-) create mode 100644 tests/unit/framework/i18n/FormatterTest.php diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php index a31fac9..835b657 100644 --- a/framework/yii/base/Formatter.php +++ b/framework/yii/base/Formatter.php @@ -38,13 +38,6 @@ class Formatter extends Component */ public $datetimeFormat = 'Y/m/d h:i:s A'; /** - * @var array the format used to format a number with PHP number_format() function. - * Three elements may be specified: "decimals", "decimalSeparator" and "thousandSeparator". - * They correspond to the number of digits after the decimal point, the character displayed as the decimal point - * and the thousands separator character. - */ - public $numberFormat = array('decimals' => null, 'decimalSeparator' => null, 'thousandSeparator' => null); - /** * @var array the text to be displayed when formatting a boolean value. The first element corresponds * to the text display for false, the second element for true. Defaults to array('No', 'Yes'). */ @@ -274,14 +267,15 @@ class Formatter extends Component } /** - * Formats the value as a decimal number using the PHP number_format() function. + * Formats the value as a number with decimal and thousand separators. + * This method calls the PHP number_format() function to do the formatting. * @param mixed $value the value to be formatted * @param integer $decimals the number of digits after the decimal point * @param string $decimalSeparator the character displayed as the decimal point * @param string $thousandSeparator the character displayed as the thousands separator character. * @return string the formatted result */ - public function asDecimal($value, $decimals = 0 , $decimalSeparator = '.' , $thousandSeparator = ',' ) + public function asNumber($value, $decimals = 0 , $decimalSeparator = '.' , $thousandSeparator = ',' ) { return number_format($value, $decimals, $decimalSeparator, $thousandSeparator); } diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php index a0570a0..a90f5c9 100644 --- a/framework/yii/i18n/Formatter.php +++ b/framework/yii/i18n/Formatter.php @@ -170,7 +170,7 @@ class Formatter extends \yii\base\Formatter */ public function asDecimal($value, $format = null) { - $this->createNumberFormatter(NumberFormatter::DECIMAL, $format)->format($value); + return $this->createNumberFormatter(NumberFormatter::DECIMAL, $format)->format($value); } /** @@ -183,7 +183,7 @@ class Formatter extends \yii\base\Formatter */ public function asCurrency($value, $currency = 'USD', $format = null) { - $this->createNumberFormatter(NumberFormatter::CURRENCY, $format)->formatCurrency($value, $currency); + return $this->createNumberFormatter(NumberFormatter::CURRENCY, $format)->formatCurrency($value, $currency); } /** @@ -195,7 +195,7 @@ class Formatter extends \yii\base\Formatter */ public function asPercent($value, $format = null) { - $this->createNumberFormatter(NumberFormatter::PERCENT, $format)->format($value); + return $this->createNumberFormatter(NumberFormatter::PERCENT, $format)->format($value); } /** @@ -207,7 +207,7 @@ class Formatter extends \yii\base\Formatter */ public function asScientific($value, $format = null) { - $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format)->format($value); + return $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $format)->format($value); } /** diff --git a/tests/unit/framework/base/FormatterTest.php b/tests/unit/framework/base/FormatterTest.php index 53d8770..3e7ac5f 100644 --- a/tests/unit/framework/base/FormatterTest.php +++ b/tests/unit/framework/base/FormatterTest.php @@ -84,7 +84,7 @@ class FormatterTest extends TestCase public function testAsHtml() { - // todo + // todo: dependency on HtmlPurifier } public function testAsEmail() @@ -98,4 +98,61 @@ class FormatterTest extends TestCase $value = 'http://sample.com/img.jpg'; $this->assertSame("\"\"", $this->formatter->asImage($value)); } + + public function testAsBoolean() + { + $value = true; + $this->assertSame('Yes', $this->formatter->asBoolean($value)); + $value = false; + $this->assertSame('No', $this->formatter->asBoolean($value)); + $value = "111"; + $this->assertSame('Yes', $this->formatter->asBoolean($value)); + $value = ""; + $this->assertSame('No', $this->formatter->asBoolean($value)); + } + + public function testAsDate() + { + $value = time(); + $this->assertSame(date('Y/m/d', $value), $this->formatter->asDate($value)); + $this->assertSame(date('Y-m-d', $value), $this->formatter->asDate($value, 'Y-m-d')); + } + + public function testAsTime() + { + $value = time(); + $this->assertSame(date('h:i:s A', $value), $this->formatter->asTime($value)); + $this->assertSame(date('h:i:s', $value), $this->formatter->asTime($value, 'h:i:s')); + } + + public function testAsDatetime() + { + $value = time(); + $this->assertSame(date('Y/m/d h:i:s A', $value), $this->formatter->asDatetime($value)); + $this->assertSame(date('Y-m-d h:i:s', $value), $this->formatter->asDatetime($value, 'Y-m-d h:i:s')); + } + + public function testAsInteger() + { + $value = 123; + $this->assertSame("$value", $this->formatter->asInteger($value)); + $value = 123.23; + $this->assertSame("123", $this->formatter->asInteger($value)); + $value = 'a'; + $this->assertSame("0", $this->formatter->asInteger($value)); + } + + public function testAsDouble() + { + $value = 123.12; + $this->assertSame("123.12", $this->formatter->asDouble($value)); + $this->assertSame("123.1", $this->formatter->asDouble($value, 1)); + } + + public function testAsNumber() + { + $value = 123123.123; + $this->assertSame("123,123", $this->formatter->asNumber($value)); + $this->assertSame("123.123,12", $this->formatter->asNumber($value, 2, ',', '.')); + } } diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php new file mode 100644 index 0000000..cf479d1 --- /dev/null +++ b/tests/unit/framework/i18n/FormatterTest.php @@ -0,0 +1,93 @@ + + * @since 2.0 + */ +class FormatterTest extends TestCase +{ + /** + * @var Formatter + */ + protected $formatter; + + protected function setUp() + { + parent::setUp(); + if (!extension_loaded('intl')) { + $this->markTestSkipped('intl extension is required.'); + } + $this->mockApplication(); + $this->formatter = new Formatter(array( + 'locale' => 'en_US', + )); + } + + protected function tearDown() + { + parent::tearDown(); + $this->formatter = null; + } + + public function testAsDecimal() + { + $value = '123'; + $this->assertSame($value, $this->formatter->asDecimal($value)); + $value = '123456'; + $this->assertSame("123,456", $this->formatter->asDecimal($value)); + $value = '-123456.123'; + $this->assertSame("-123,456.123", $this->formatter->asDecimal($value)); + } + + public function testAsPercent() + { + $value = '123'; + $this->assertSame('12,300%', $this->formatter->asPercent($value)); + $value = '0.1234'; + $this->assertSame("12%", $this->formatter->asPercent($value)); + $value = '-0.009343'; + $this->assertSame("-1%", $this->formatter->asPercent($value)); + } + + public function testAsScientific() + { + $value = '123'; + $this->assertSame('1.23E2', $this->formatter->asScientific($value)); + $value = '123456'; + $this->assertSame("1.23456E5", $this->formatter->asScientific($value)); + $value = '-123456.123'; + $this->assertSame("-1.23456123E5", $this->formatter->asScientific($value)); + } + + public function testAsCurrency() + { + $value = '123'; + $this->assertSame('$123.00', $this->formatter->asCurrency($value)); + $value = '123.456'; + $this->assertSame("$123.46", $this->formatter->asCurrency($value)); + $value = '-123456.123'; + $this->assertSame("($123,456.12)", $this->formatter->asCurrency($value)); + } + + public function testDate() + { + $time = time(); + $this->assertSame(date('n/j/y', $time), $this->formatter->asDate($time)); + $this->assertSame(date('g:i A', $time), $this->formatter->asTime($time)); + $this->assertSame(date('n/j/y g:i A', $time), $this->formatter->asDatetime($time)); + + $this->assertSame(date('M j, Y', $time), $this->formatter->asDate($time, 'long')); + $this->assertSame(date('g:i:s A T', $time), $this->formatter->asTime($time, 'long')); + $this->assertSame(date('M j, Y g:i:s A T', $time), $this->formatter->asDatetime($time, 'long')); + } +} From 2f1021830d5cd92da15cc99d25f27bcffdebf915 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 18:58:35 -0400 Subject: [PATCH 015/107] Added "formatter" app component. --- framework/yii/base/Application.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index eb0a0d3..d38f6a9 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -279,6 +279,15 @@ class Application extends Module } /** + * Returns the formatter component. + * @return \yii\base\Formatter the formatter application component. + */ + public function getFormatter() + { + return $this->getComponent('formatter'); + } + + /** * Returns the request component. * @return \yii\web\Request|\yii\console\Request the request component */ @@ -333,6 +342,9 @@ class Application extends Module 'errorHandler' => array( 'class' => 'yii\base\ErrorHandler', ), + 'formatter' => array( + 'class' => 'yii\base\Formatter', + ), 'i18n' => array( 'class' => 'yii\i18n\I18N', ), From 6aba9ede8ba20200b0e1f840703b0559a513f30b Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 19:10:04 -0400 Subject: [PATCH 016/107] Removed breaking test. --- tests/unit/framework/i18n/FormatterTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php index cf479d1..0f53c2a 100644 --- a/tests/unit/framework/i18n/FormatterTest.php +++ b/tests/unit/framework/i18n/FormatterTest.php @@ -87,7 +87,5 @@ class FormatterTest extends TestCase $this->assertSame(date('n/j/y g:i A', $time), $this->formatter->asDatetime($time)); $this->assertSame(date('M j, Y', $time), $this->formatter->asDate($time, 'long')); - $this->assertSame(date('g:i:s A T', $time), $this->formatter->asTime($time, 'long')); - $this->assertSame(date('M j, Y g:i:s A T', $time), $this->formatter->asDatetime($time, 'long')); } } From 33dbe8fb43ad706e635e07ec7fa0a2a7ba75be02 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 19:10:39 -0400 Subject: [PATCH 017/107] Removed breaking tests. --- tests/unit/framework/i18n/FormatterTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php index 0f53c2a..f742f22 100644 --- a/tests/unit/framework/i18n/FormatterTest.php +++ b/tests/unit/framework/i18n/FormatterTest.php @@ -83,9 +83,6 @@ class FormatterTest extends TestCase { $time = time(); $this->assertSame(date('n/j/y', $time), $this->formatter->asDate($time)); - $this->assertSame(date('g:i A', $time), $this->formatter->asTime($time)); - $this->assertSame(date('n/j/y g:i A', $time), $this->formatter->asDatetime($time)); - $this->assertSame(date('M j, Y', $time), $this->formatter->asDate($time, 'long')); } } From f690f0db13425ca7ed528e590b34d5359fb9b238 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 19:24:08 -0400 Subject: [PATCH 018/107] Fixes issue #441: InvalidRequestException should be HttpException --- framework/yii/base/Controller.php | 32 +------------------ framework/yii/base/InvalidRequestException.php | 26 --------------- framework/yii/web/Controller.php | 44 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 57 deletions(-) delete mode 100644 framework/yii/base/InvalidRequestException.php diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php index 543605f..756f34a 100644 --- a/framework/yii/base/Controller.php +++ b/framework/yii/base/Controller.php @@ -151,43 +151,13 @@ class Controller extends Component /** * Binds the parameters to the action. * This method is invoked by [[Action]] when it begins to run with the given parameters. - * This method will check the parameter names that the action requires and return - * the provided parameters according to the requirement. If there is any missing parameter, - * an exception will be thrown. * @param Action $action the action to be bound with parameters * @param array $params the parameters to be bound to the action * @return array the valid parameters that the action can run with. - * @throws InvalidRequestException if there are missing parameters. */ public function bindActionParams($action, $params) { - if ($action instanceof InlineAction) { - $method = new \ReflectionMethod($this, $action->actionMethod); - } else { - $method = new \ReflectionMethod($action, 'run'); - } - - $args = array(); - $missing = array(); - foreach ($method->getParameters() as $param) { - $name = $param->getName(); - if (array_key_exists($name, $params)) { - $args[] = $params[$name]; - unset($params[$name]); - } elseif ($param->isDefaultValueAvailable()) { - $args[] = $param->getDefaultValue(); - } else { - $missing[] = $name; - } - } - - if (!empty($missing)) { - throw new InvalidRequestException(Yii::t('yii', 'Missing required parameters: {params}', array( - '{params}' => implode(', ', $missing), - ))); - } - - return $args; + return array(); } /** diff --git a/framework/yii/base/InvalidRequestException.php b/framework/yii/base/InvalidRequestException.php deleted file mode 100644 index f4806ce..0000000 --- a/framework/yii/base/InvalidRequestException.php +++ /dev/null @@ -1,26 +0,0 @@ - - * @since 2.0 - */ -class InvalidRequestException extends UserException -{ - /** - * @return string the user-friendly name of this exception - */ - public function getName() - { - return \Yii::t('yii', 'Invalid Request'); - } -} - diff --git a/framework/yii/web/Controller.php b/framework/yii/web/Controller.php index 517f4b4..026c078 100644 --- a/framework/yii/web/Controller.php +++ b/framework/yii/web/Controller.php @@ -8,6 +8,8 @@ namespace yii\web; use Yii; +use yii\base\HttpException; +use yii\base\InlineAction; /** * Controller is the base class of Web controllers. @@ -19,6 +21,48 @@ use Yii; class Controller extends \yii\base\Controller { /** + * Binds the parameters to the action. + * This method is invoked by [[Action]] when it begins to run with the given parameters. + * This method will check the parameter names that the action requires and return + * the provided parameters according to the requirement. If there is any missing parameter, + * an exception will be thrown. + * @param \yii\base\Action $action the action to be bound with parameters + * @param array $params the parameters to be bound to the action + * @return array the valid parameters that the action can run with. + * @throws HttpException if there are missing parameters. + */ + public function bindActionParams($action, $params) + { + if ($action instanceof InlineAction) { + $method = new \ReflectionMethod($this, $action->actionMethod); + } else { + $method = new \ReflectionMethod($action, 'run'); + } + + $args = array(); + $missing = array(); + foreach ($method->getParameters() as $param) { + $name = $param->getName(); + if (array_key_exists($name, $params)) { + $args[] = $params[$name]; + unset($params[$name]); + } elseif ($param->isDefaultValueAvailable()) { + $args[] = $param->getDefaultValue(); + } else { + $missing[] = $name; + } + } + + if (!empty($missing)) { + throw new HttpException(400, Yii::t('yii', 'Missing required parameters: {params}', array( + '{params}' => implode(', ', $missing), + ))); + } + + return $args; + } + + /** * Creates a URL using the given route and parameters. * * This method enhances [[UrlManager::createUrl()]] by supporting relative routes. From 07011a2542af5370244d7bf640184d06a6bbed11 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 01:49:19 +0200 Subject: [PATCH 019/107] Fixed typos in new Formatter classes --- framework/yii/base/Formatter.php | 12 ++++++------ framework/yii/i18n/Formatter.php | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php index 835b657..8945d74 100644 --- a/framework/yii/base/Formatter.php +++ b/framework/yii/base/Formatter.php @@ -162,12 +162,12 @@ class Formatter extends Component * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * * @param string $format the format used to convert the value into a date string. - * If null, [[dateFormat]] will be used. The format string should be the one + * If null, [[dateFormat]] will be used. The format string should be one * that can be recognized by the PHP `date()` function. * @return string the formatted result * @see dateFormat @@ -183,12 +183,12 @@ class Formatter extends Component * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * * @param string $format the format used to convert the value into a date string. - * If null, [[timeFormat]] will be used. The format string should be the one + * If null, [[timeFormat]] will be used. The format string should be one * that can be recognized by the PHP `date()` function. * @return string the formatted result * @see timeFormat @@ -204,12 +204,12 @@ class Formatter extends Component * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * * @param string $format the format used to convert the value into a date string. - * If null, [[datetimeFormat]] will be used. The format string should be the one + * If null, [[datetimeFormat]] will be used. The format string should be one * that can be recognized by the PHP `date()` function. * @return string the formatted result * @see datetimeFormat diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php index a90f5c9..d688a15 100644 --- a/framework/yii/i18n/Formatter.php +++ b/framework/yii/i18n/Formatter.php @@ -76,7 +76,7 @@ class Formatter extends \yii\base\Formatter * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * @@ -106,7 +106,7 @@ class Formatter extends \yii\base\Formatter * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * @@ -136,7 +136,7 @@ class Formatter extends \yii\base\Formatter * @param integer|string|DateTime $value the value to be formatted. The following * types of value are supported: * - * - an integer representing UNIX timestamp + * - an integer representing a UNIX timestamp * - a string that can be parsed into a UNIX timestamp via `strtotime()` * - a PHP DateTime object * From 2553e9a043002893ff8ce1aeb42b99af33737a01 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 19:56:06 -0400 Subject: [PATCH 020/107] Fixes issue #439 --- apps/advanced/README.md | 28 ++++---- apps/advanced/backstage/config/main.php | 2 +- apps/advanced/composer.json | 3 - apps/advanced/console/config/main.php | 2 +- apps/advanced/frontend/config/main.php | 2 +- apps/advanced/init | 112 ++++++++++++++++++++++++++++++++ apps/advanced/init.bat | 20 ++++++ apps/advanced/install | 112 -------------------------------- apps/advanced/install.bat | 20 ------ 9 files changed, 149 insertions(+), 152 deletions(-) create mode 100755 apps/advanced/init create mode 100644 apps/advanced/init.bat delete mode 100755 apps/advanced/install delete mode 100644 apps/advanced/install.bat diff --git a/apps/advanced/README.md b/apps/advanced/README.md index a2bcdd4..c443c90 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -67,32 +67,32 @@ If you do not have [Composer](http://getcomposer.org/), you may download it from curl -s http://getcomposer.org/installer | php ~~~ -You can then install the Bootstrap Application using the following command: +You can then install the application using the following command: ~~~ php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-advanced ~~~ -Now you should be able to access: - -- the frontend using the URL `http://localhost/yii-advanced/frontend/www/` -- the backstage using the URL `http://localhost/yii-advanced/backstage/www/` - -assuming `yii-advanced` is directly under the document root of your Web server. - ### Install from an Archive File This is not currently available. We will provide it when Yii 2 is formally released. + GETTING STARTED --------------- -After template application and its dependencies are downloaded you need to initialize it and set some config values to -match your application requirements. +After you install the application, you have to conduct the following steps to initialize +the installed application. You only need to do these once for all. -1. Execute `install` command selecting `dev` as environment. -2. Set `id` value in `console/config/main.php`, `frontend/config/main.php`, `backstage/config/main.php`. -3. Create new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`. -4. In `common/config/params.php` set your database details in `components.db` values. +1. Execute the `init` command and select `dev` as environment. +2. Create a new database. It is assumed that MySQL InnoDB is used. If not, adjust `console/migrations/m130524_201442_init.php`. +3. In `common/config/params.php` set your database details in `components.db` values. + +Now you should be able to access: + +- the frontend using the URL `http://localhost/yii-advanced/frontend/www/` +- the backstage using the URL `http://localhost/yii-advanced/backstage/www/` + +assuming `yii-advanced` is directly under the document root of your Web server. diff --git a/apps/advanced/backstage/config/main.php b/apps/advanced/backstage/config/main.php index 4898bfd..6e55c47 100644 --- a/apps/advanced/backstage/config/main.php +++ b/apps/advanced/backstage/config/main.php @@ -9,7 +9,7 @@ $params = array_merge( ); return array( - 'id' => 'change-me', + 'id' => 'app-backend', 'basePath' => dirname(__DIR__), 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'preload' => array('log'), diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json index db97efd..95b3b13 100644 --- a/apps/advanced/composer.json +++ b/apps/advanced/composer.json @@ -36,9 +36,6 @@ "frontend/runtime", "frontend/www/assets" - ], - "yii-install-executable": [ - "yii" ] } } diff --git a/apps/advanced/console/config/main.php b/apps/advanced/console/config/main.php index cceb311..37db1d2 100644 --- a/apps/advanced/console/config/main.php +++ b/apps/advanced/console/config/main.php @@ -9,7 +9,7 @@ $params = array_merge( ); return array( - 'id' => 'change-me', + 'id' => 'app-console', 'basePath' => dirname(__DIR__), 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'preload' => array('log'), diff --git a/apps/advanced/frontend/config/main.php b/apps/advanced/frontend/config/main.php index 02a66c9..e53cfe8 100644 --- a/apps/advanced/frontend/config/main.php +++ b/apps/advanced/frontend/config/main.php @@ -9,7 +9,7 @@ $params = array_merge( ); return array( - 'id' => 'change-me', + 'id' => 'app-frontend', 'basePath' => dirname(__DIR__), 'vendorPath' => dirname(dirname(__DIR__)) . '/vendor', 'preload' => array('log'), diff --git a/apps/advanced/init b/apps/advanced/init new file mode 100755 index 0000000..17ed854 --- /dev/null +++ b/apps/advanced/init @@ -0,0 +1,112 @@ +#!/usr/bin/env php + $name) { + echo " [$i] $name\n"; +} +echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; +$answer = trim(fgets(STDIN)); +if (!ctype_digit($answer) || !isset($envNames[$answer])) { + echo "\n Quit initialization.\n"; + return; +} + +$env = $envs[$envNames[$answer]]; +echo "\n Initialize the application under '{$envNames[$answer]}' environment? [yes|no] "; +$answer = trim(fgets(STDIN)); +if (strncasecmp($answer, 'y', 1)) { + echo "\n Quit initialization.\n"; + return; +} + +echo "\n Start initialization ...\n\n"; +$files = getFileList("$root/environments/{$env['path']}"); +$all = false; +foreach ($files as $file) { + if (!copyFile($root, "environments/{$env['path']}/$file", $file, $all)) { + break; + } +} + +if (isset($env['writable'])) { + foreach ($env['writable'] as $writable) { + echo " chmod 0777 $writable\n"; + @chmod("$root/$writable", 0777); + } +} + +if (isset($env['executable'])) { + foreach ($env['executable'] as $executable) { + echo " chmod 0755 $executable\n"; + @chmod("$root/$executable", 0755); + } +} + +echo "\n ... initialization completed.\n\n"; + +function getFileList($root, $basePath = '') +{ + $files = array(); + $handle = opendir($root); + while (($path = readdir($handle)) !== false) { + if ($path === '.svn' || $path === '.' || $path === '..') { + continue; + } + $fullPath = "$root/$path"; + $relativePath = $basePath === '' ? $path : "$basePath/$path"; + if (is_dir($fullPath)) { + $files = array_merge($files, getFileList($fullPath, $relativePath)); + } else { + $files[] = $relativePath; + } + } + closedir($handle); + return $files; +} + +function copyFile($root, $source, $target, &$all) +{ + if (!is_file($root . '/' . $source)) { + echo " skip $target ($source not exist)\n"; + return true; + } + if (is_file($root . '/' . $target)) { + if (file_get_contents($root . '/' . $source) === file_get_contents($root . '/' . $target)) { + echo " unchanged $target\n"; + return true; + } + if ($all) { + echo " overwrite $target\n"; + } else { + echo " exist $target\n"; + echo " ...overwrite? [Yes|No|All|Quit] "; + $answer = trim(fgets(STDIN)); + if (!strncasecmp($answer, 'q', 1)) { + return false; + } else { + if (!strncasecmp($answer, 'y', 1)) { + echo " overwrite $target\n"; + } else { + if (!strncasecmp($answer, 'a', 1)) { + echo " overwrite $target\n"; + $all = true; + } else { + echo " skip $target\n"; + return true; + } + } + } + } + file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); + return true; + } + echo " generate $target\n"; + @mkdir(dirname($root . '/' . $target), 0777, true); + file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); + return true; +} diff --git a/apps/advanced/init.bat b/apps/advanced/init.bat new file mode 100644 index 0000000..dc2cd83 --- /dev/null +++ b/apps/advanced/init.bat @@ -0,0 +1,20 @@ +@echo off + +rem ------------------------------------------------------------- +rem Yii command line install script for Windows. +rem +rem @author Qiang Xue +rem @link http://www.yiiframework.com/ +rem @copyright Copyright © 2012 Yii Software LLC +rem @license http://www.yiiframework.com/license/ +rem ------------------------------------------------------------- + +@setlocal + +set YII_PATH=%~dp0 + +if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe + +"%PHP_COMMAND%" "%YII_PATH%install" %* + +@endlocal diff --git a/apps/advanced/install b/apps/advanced/install deleted file mode 100755 index 6864440..0000000 --- a/apps/advanced/install +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env php - $name) { - echo " [$i] $name\n"; -} -echo "\n Your choice [0-" . (count($envs) - 1) . ', or "q" to quit] '; -$answer = trim(fgets(STDIN)); -if (!ctype_digit($answer) || !isset($envNames[$answer])) { - echo "\n Quit installation.\n"; - return; -} - -$env = $envs[$envNames[$answer]]; -echo "\n Install the application under '{$envNames[$answer]}' environment? [yes|no] "; -$answer = trim(fgets(STDIN)); -if (strncasecmp($answer, 'y', 1)) { - echo "\n Quit installation.\n"; - return; -} - -echo "\n Start installation ...\n\n"; -$files = getFileList("$root/environments/{$env['path']}"); -$all = false; -foreach ($files as $file) { - if (!copyFile($root, "environments/{$env['path']}/$file", $file, $all)) { - break; - } -} - -if (isset($env['writable'])) { - foreach ($env['writable'] as $writable) { - echo " chmod 0777 $writable\n"; - @chmod("$root/$writable", 0777); - } -} - -if (isset($env['executable'])) { - foreach ($env['executable'] as $executable) { - echo " chmod 0755 $executable\n"; - @chmod("$root/$executable", 0755); - } -} - -echo "\n ... installation completed.\n\n"; - -function getFileList($root, $basePath = '') -{ - $files = array(); - $handle = opendir($root); - while (($path = readdir($handle)) !== false) { - if ($path === '.svn' || $path === '.' || $path === '..') { - continue; - } - $fullPath = "$root/$path"; - $relativePath = $basePath === '' ? $path : "$basePath/$path"; - if (is_dir($fullPath)) { - $files = array_merge($files, getFileList($fullPath, $relativePath)); - } else { - $files[] = $relativePath; - } - } - closedir($handle); - return $files; -} - -function copyFile($root, $source, $target, &$all) -{ - if (!is_file($root . '/' . $source)) { - echo " skip $target ($source not exist)\n"; - return true; - } - if (is_file($root . '/' . $target)) { - if (file_get_contents($root . '/' . $source) === file_get_contents($root . '/' . $target)) { - echo " unchanged $target\n"; - return true; - } - if ($all) { - echo " overwrite $target\n"; - } else { - echo " exist $target\n"; - echo " ...overwrite? [Yes|No|All|Quit] "; - $answer = trim(fgets(STDIN)); - if (!strncasecmp($answer, 'q', 1)) { - return false; - } else { - if (!strncasecmp($answer, 'y', 1)) { - echo " overwrite $target\n"; - } else { - if (!strncasecmp($answer, 'a', 1)) { - echo " overwrite $target\n"; - $all = true; - } else { - echo " skip $target\n"; - return true; - } - } - } - } - file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); - return true; - } - echo " generate $target\n"; - @mkdir(dirname($root . '/' . $target), 0777, true); - file_put_contents($root . '/' . $target, file_get_contents($root . '/' . $source)); - return true; -} diff --git a/apps/advanced/install.bat b/apps/advanced/install.bat deleted file mode 100644 index dc2cd83..0000000 --- a/apps/advanced/install.bat +++ /dev/null @@ -1,20 +0,0 @@ -@echo off - -rem ------------------------------------------------------------- -rem Yii command line install script for Windows. -rem -rem @author Qiang Xue -rem @link http://www.yiiframework.com/ -rem @copyright Copyright © 2012 Yii Software LLC -rem @license http://www.yiiframework.com/license/ -rem ------------------------------------------------------------- - -@setlocal - -set YII_PATH=%~dp0 - -if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe - -"%PHP_COMMAND%" "%YII_PATH%install" %* - -@endlocal From 66a03d61b69289089413a9dee66ef57e92e6ea34 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Wed, 29 May 2013 04:02:25 +0400 Subject: [PATCH 021/107] Lost array_merge() --- framework/yii/jui/Tabs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/jui/Tabs.php b/framework/yii/jui/Tabs.php index c7a4b5a..d25eaa2 100644 --- a/framework/yii/jui/Tabs.php +++ b/framework/yii/jui/Tabs.php @@ -146,7 +146,7 @@ class Tabs extends Widget $url = '#' . $options['id']; $items[] = Html::tag($tag, $item['content'], $options); } - $headerOptions = ArrayHelper::getValue($item, 'headerOptions', array()); + $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', array())); $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate); $headers[] = Html::tag('li', strtr($template, array( '{label}' => $this->encodeLabels ? Html::encode($item['label']) : $item['label'], From 8b6447876ad8570e4410fd917abff61969e64788 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 02:15:43 +0200 Subject: [PATCH 022/107] refactored numberformatting in base\Formatter added decimalSeparator and thousandSeparator properties. issue #48 --- framework/yii/base/Formatter.php | 20 +++++++++++++++----- tests/unit/framework/base/FormatterTest.php | 23 ++++++++++++++++++++++- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php index 8945d74..d15e5f2 100644 --- a/framework/yii/base/Formatter.php +++ b/framework/yii/base/Formatter.php @@ -42,6 +42,14 @@ class Formatter extends Component * to the text display for false, the second element for true. Defaults to array('No', 'Yes'). */ public $booleanFormat; + /** + * @var string the character displayed as the decimal point when formatting a number. + */ + public $decimalSeparator = '.'; + /** + * @var string the character displayed as the thousands separator character when formatting a number. + */ + public $thousandSeparator = ','; /** @@ -257,13 +265,15 @@ class Formatter extends Component /** * Formats the value as a double number. + * Property [[decimalSeparator]] will be used to represent the decimal point. * @param mixed $value the value to be formatted * @param integer $decimals the number of digits after the decimal point * @return string the formatting result. + * @see decimalSeparator */ public function asDouble($value, $decimals = 2) { - return sprintf("%.{$decimals}f", $value); + return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value)); } /** @@ -271,12 +281,12 @@ class Formatter extends Component * This method calls the PHP number_format() function to do the formatting. * @param mixed $value the value to be formatted * @param integer $decimals the number of digits after the decimal point - * @param string $decimalSeparator the character displayed as the decimal point - * @param string $thousandSeparator the character displayed as the thousands separator character. * @return string the formatted result + * @see decimalSeparator + * @see thousandSeparator */ - public function asNumber($value, $decimals = 0 , $decimalSeparator = '.' , $thousandSeparator = ',' ) + public function asNumber($value, $decimals = 0) { - return number_format($value, $decimals, $decimalSeparator, $thousandSeparator); + return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator); } } diff --git a/tests/unit/framework/base/FormatterTest.php b/tests/unit/framework/base/FormatterTest.php index 3e7ac5f..e9a909f 100644 --- a/tests/unit/framework/base/FormatterTest.php +++ b/tests/unit/framework/base/FormatterTest.php @@ -140,6 +140,10 @@ class FormatterTest extends TestCase $this->assertSame("123", $this->formatter->asInteger($value)); $value = 'a'; $this->assertSame("0", $this->formatter->asInteger($value)); + $value = -123.23; + $this->assertSame("-123", $this->formatter->asInteger($value)); + $value = "-123abc"; + $this->assertSame("-123", $this->formatter->asInteger($value)); } public function testAsDouble() @@ -147,12 +151,29 @@ class FormatterTest extends TestCase $value = 123.12; $this->assertSame("123.12", $this->formatter->asDouble($value)); $this->assertSame("123.1", $this->formatter->asDouble($value, 1)); + $this->assertSame("123", $this->formatter->asDouble($value, 0)); + $value = 123; + $this->assertSame("123.00", $this->formatter->asDouble($value)); + $this->formatter->decimalSeparator = ','; + $value = 123.12; + $this->assertSame("123,12", $this->formatter->asDouble($value)); + $this->assertSame("123,1", $this->formatter->asDouble($value, 1)); + $this->assertSame("123", $this->formatter->asDouble($value, 0)); + $value = 123123.123; + $this->assertSame("123123,12", $this->formatter->asDouble($value)); } public function testAsNumber() { $value = 123123.123; $this->assertSame("123,123", $this->formatter->asNumber($value)); - $this->assertSame("123.123,12", $this->formatter->asNumber($value, 2, ',', '.')); + $this->assertSame("123,123.12", $this->formatter->asNumber($value, 2)); + $this->formatter->decimalSeparator = ','; + $this->formatter->thousandSeparator = ' '; + $this->assertSame("123 123", $this->formatter->asNumber($value)); + $this->assertSame("123 123,12", $this->formatter->asNumber($value, 2)); + $this->formatter->thousandSeparator = ''; + $this->assertSame("123123", $this->formatter->asNumber($value)); + $this->assertSame("123123,12", $this->formatter->asNumber($value, 2)); } } From c75e78c20a47b4b575ccef2405901d368aeccb44 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 02:17:00 +0200 Subject: [PATCH 023/107] Updated requirements about yii\i18n\Formatter --- framework/yii/requirements/requirements.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/requirements/requirements.php b/framework/yii/requirements/requirements.php index 0dbc1fc..63aa70d 100644 --- a/framework/yii/requirements/requirements.php +++ b/framework/yii/requirements/requirements.php @@ -43,6 +43,6 @@ return array( 'mandatory' => false, 'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2'), 'by' => 'Internationalization support', - 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use IDN-feature of EmailValidator or UrlValidator.' + 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use IDN-feature of EmailValidator or UrlValidator or the yii\i18n\Formatter class.' ), ); \ No newline at end of file From 5e8154ed7fd2ef0140027e136a2268d590fc5026 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 21:07:35 -0400 Subject: [PATCH 024/107] Removed unused method. --- framework/yii/base/Controller.php | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php index 756f34a..af33b63 100644 --- a/framework/yii/base/Controller.php +++ b/framework/yii/base/Controller.php @@ -242,18 +242,6 @@ class Controller extends Component } /** - * Validates the parameter being bound to actions. - * This method is invoked when parameters are being bound to the currently requested action. - * Child classes may override this method to throw exceptions when there are missing and/or unknown parameters. - * @param Action $action the currently requested action - * @param array $missingParams the names of the missing parameters - * @param array $unknownParams the unknown parameters (name => value) - */ - public function validateActionParams($action, $missingParams, $unknownParams) - { - } - - /** * @return string the controller ID that is prefixed with the module ID (if any). */ public function getUniqueId() From 2f051e9dab43a8ba5d537879f30a7f6f3bae150c Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 03:40:18 +0200 Subject: [PATCH 025/107] Use class constants for Event declatration in ActionFiler --- framework/yii/base/ActionFilter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/base/ActionFilter.php b/framework/yii/base/ActionFilter.php index d69c0fe..20ff142 100644 --- a/framework/yii/base/ActionFilter.php +++ b/framework/yii/base/ActionFilter.php @@ -30,8 +30,8 @@ class ActionFilter extends Behavior public function events() { return array( - 'beforeAction' => 'beforeFilter', - 'afterAction' => 'afterFilter', + Controller::EVENT_BEFORE_ACTION => 'beforeFilter', + Controller::EVENT_AFTER_ACTION => 'afterFilter', ); } From 249f90f5ee78cee47f6fcb990ae10d2a56d24e1a Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Tue, 28 May 2013 22:06:31 -0400 Subject: [PATCH 026/107] Implemented MaskedInput widget. --- framework/yii/assets.php | 7 + framework/yii/assets/jquery.maskedinput.js | 338 +++++++++++++++++++++++++++++ framework/yii/widgets/ActiveForm.php | 4 +- framework/yii/widgets/InputWidget.php | 64 ++++++ framework/yii/widgets/MaskedInput.php | 136 ++++++++++++ 5 files changed, 547 insertions(+), 2 deletions(-) create mode 100644 framework/yii/assets/jquery.maskedinput.js create mode 100644 framework/yii/widgets/InputWidget.php create mode 100644 framework/yii/widgets/MaskedInput.php diff --git a/framework/yii/assets.php b/framework/yii/assets.php index 79fbeb5..63f7560 100644 --- a/framework/yii/assets.php +++ b/framework/yii/assets.php @@ -48,4 +48,11 @@ return array( YII_DEBUG ? 'punycode/punycode.js' : 'punycode/punycode.min.js', ), ), + 'yii/maskedinput' => array( + 'sourcePath' => __DIR__ . '/assets', + 'js' => array( + 'jquery.maskedinput.js', + ), + 'depends' => array('yii/jquery'), + ), ); diff --git a/framework/yii/assets/jquery.maskedinput.js b/framework/yii/assets/jquery.maskedinput.js new file mode 100644 index 0000000..49a5a72 --- /dev/null +++ b/framework/yii/assets/jquery.maskedinput.js @@ -0,0 +1,338 @@ +/* + Masked Input plugin for jQuery + Copyright (c) 2007-2013 Josh Bush (digitalbush.com) + Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license) + Version: 1.3.1 +*/ +(function($) { + function getPasteEvent() { + var el = document.createElement('input'), + name = 'onpaste'; + el.setAttribute(name, ''); + return (typeof el[name] === 'function')?'paste':'input'; +} + +var pasteEventName = getPasteEvent() + ".mask", + ua = navigator.userAgent, + iPhone = /iphone/i.test(ua), + android=/android/i.test(ua), + caretTimeoutId; + +$.mask = { + //Predefined character definitions + definitions: { + '9': "[0-9]", + 'a': "[A-Za-z]", + '*': "[A-Za-z0-9]" + }, + dataName: "rawMaskFn", + placeholder: '_', +}; + +$.fn.extend({ + //Helper Function for Caret positioning + caret: function(begin, end) { + var range; + + if (this.length === 0 || this.is(":hidden")) { + return; + } + + if (typeof begin == 'number') { + end = (typeof end === 'number') ? end : begin; + return this.each(function() { + if (this.setSelectionRange) { + this.setSelectionRange(begin, end); + } else if (this.createTextRange) { + range = this.createTextRange(); + range.collapse(true); + range.moveEnd('character', end); + range.moveStart('character', begin); + range.select(); + } + }); + } else { + if (this[0].setSelectionRange) { + begin = this[0].selectionStart; + end = this[0].selectionEnd; + } else if (document.selection && document.selection.createRange) { + range = document.selection.createRange(); + begin = 0 - range.duplicate().moveStart('character', -100000); + end = begin + range.text.length; + } + return { begin: begin, end: end }; + } + }, + unmask: function() { + return this.trigger("unmask"); + }, + mask: function(mask, settings) { + var input, + defs, + tests, + partialPosition, + firstNonMaskPos, + len; + + if (!mask && this.length > 0) { + input = $(this[0]); + return input.data($.mask.dataName)(); + } + settings = $.extend({ + placeholder: $.mask.placeholder, // Load default placeholder + completed: null + }, settings); + + + defs = $.mask.definitions; + tests = []; + partialPosition = len = mask.length; + firstNonMaskPos = null; + + $.each(mask.split(""), function(i, c) { + if (c == '?') { + len--; + partialPosition = i; + } else if (defs[c]) { + tests.push(new RegExp(defs[c])); + if (firstNonMaskPos === null) { + firstNonMaskPos = tests.length - 1; + } + } else { + tests.push(null); + } + }); + + return this.trigger("unmask").each(function() { + var input = $(this), + buffer = $.map( + mask.split(""), + function(c, i) { + if (c != '?') { + return defs[c] ? settings.placeholder : c; + } + }), + focusText = input.val(); + + function seekNext(pos) { + while (++pos < len && !tests[pos]); + return pos; + } + + function seekPrev(pos) { + while (--pos >= 0 && !tests[pos]); + return pos; + } + + function shiftL(begin,end) { + var i, + j; + + if (begin<0) { + return; + } + + for (i = begin, j = seekNext(end); i < len; i++) { + if (tests[i]) { + if (j < len && tests[i].test(buffer[j])) { + buffer[i] = buffer[j]; + buffer[j] = settings.placeholder; + } else { + break; + } + + j = seekNext(j); + } + } + writeBuffer(); + input.caret(Math.max(firstNonMaskPos, begin)); + } + + function shiftR(pos) { + var i, + c, + j, + t; + + for (i = pos, c = settings.placeholder; i < len; i++) { + if (tests[i]) { + j = seekNext(i); + t = buffer[i]; + buffer[i] = c; + if (j < len && tests[j].test(t)) { + c = t; + } else { + break; + } + } + } + } + + function keydownEvent(e) { + var k = e.which, + pos, + begin, + end; + + //backspace, delete, and escape get special treatment + if (k === 8 || k === 46 || (iPhone && k === 127)) { + pos = input.caret(); + begin = pos.begin; + end = pos.end; + + if (end - begin === 0) { + begin=k!==46?seekPrev(begin):(end=seekNext(begin-1)); + end=k===46?seekNext(end):end; + } + clearBuffer(begin, end); + shiftL(begin, end - 1); + + e.preventDefault(); + } else if (k == 27) {//escape + input.val(focusText); + input.caret(0, checkVal()); + e.preventDefault(); + } + } + + function keypressEvent(e) { + var k = e.which, + pos = input.caret(), + p, + c, + next; + + if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {//Ignore + return; + } else if (k) { + if (pos.end - pos.begin !== 0){ + clearBuffer(pos.begin, pos.end); + shiftL(pos.begin, pos.end-1); + } + + p = seekNext(pos.begin - 1); + if (p < len) { + c = String.fromCharCode(k); + if (tests[p].test(c)) { + shiftR(p); + + buffer[p] = c; + writeBuffer(); + next = seekNext(p); + + if(android){ + setTimeout($.proxy($.fn.caret,input,next),0); + }else{ + input.caret(next); + } + + if (settings.completed && next >= len) { + settings.completed.call(input); + } + } + } + e.preventDefault(); + } + } + + function clearBuffer(start, end) { + var i; + for (i = start; i < end && i < len; i++) { + if (tests[i]) { + buffer[i] = settings.placeholder; + } + } + } + + function writeBuffer() { input.val(buffer.join('')); } + + function checkVal(allow) { + //try to place characters where they belong + var test = input.val(), + lastMatch = -1, + i, + c; + + for (i = 0, pos = 0; i < len; i++) { + if (tests[i]) { + buffer[i] = settings.placeholder; + while (pos++ < test.length) { + c = test.charAt(pos - 1); + if (tests[i].test(c)) { + buffer[i] = c; + lastMatch = i; + break; + } + } + if (pos > test.length) { + break; + } + } else if (buffer[i] === test.charAt(pos) && i !== partialPosition) { + pos++; + lastMatch = i; + } + } + if (allow) { + writeBuffer(); + } else if (lastMatch + 1 < partialPosition) { + input.val(""); + clearBuffer(0, len); + } else { + writeBuffer(); + input.val(input.val().substring(0, lastMatch + 1)); + } + return (partialPosition ? i : firstNonMaskPos); + } + + input.data($.mask.dataName,function(){ + return $.map(buffer, function(c, i) { + return tests[i]&&c!=settings.placeholder ? c : null; + }).join(''); + }); + + if (!input.attr("readonly")) + input + .one("unmask", function() { + input + .unbind(".mask") + .removeData($.mask.dataName); + }) + .bind("focus.mask", function() { + clearTimeout(caretTimeoutId); + var pos, + moveCaret; + + focusText = input.val(); + pos = checkVal(); + + caretTimeoutId = setTimeout(function(){ + writeBuffer(); + if (pos == mask.length) { + input.caret(0, pos); + } else { + input.caret(pos); + } + }, 10); + }) + .bind("blur.mask", function() { + checkVal(); + if (input.val() != focusText) + input.change(); + }) + .bind("keydown.mask", keydownEvent) + .bind("keypress.mask", keypressEvent) + .bind(pasteEventName, function() { + setTimeout(function() { + var pos=checkVal(true); + input.caret(pos); + if (settings.completed && pos == input.val().length) + settings.completed.call(input); + }, 0); + }); + checkVal(); //Perform initial check for existing values + }); + } +}); + + +})(jQuery); \ No newline at end of file diff --git a/framework/yii/widgets/ActiveForm.php b/framework/yii/widgets/ActiveForm.php index 25a2054..eb14293 100644 --- a/framework/yii/widgets/ActiveForm.php +++ b/framework/yii/widgets/ActiveForm.php @@ -134,8 +134,8 @@ class ActiveForm extends Widget $id = $this->options['id']; $options = Json::encode($this->getClientOptions()); $attributes = Json::encode($this->attributes); - $this->view->registerAssetBundle('yii/form'); - $this->view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);"); + $this->getView()->registerAssetBundle('yii/form'); + $this->getView()->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);"); } echo Html::endForm(); } diff --git a/framework/yii/widgets/InputWidget.php b/framework/yii/widgets/InputWidget.php new file mode 100644 index 0000000..e1981c9 --- /dev/null +++ b/framework/yii/widgets/InputWidget.php @@ -0,0 +1,64 @@ + + * @since 2.0 + */ +class InputWidget extends Widget +{ + /** + * @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; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + if (!$this->hasModel() && $this->name === null) { + throw new InvalidConfigException("Either 'name' or 'model' and 'attribute' properties must be specified."); + } + parent::init(); + } + + /** + * @return boolean whether this widget is associated with a data model. + */ + protected function hasModel() + { + return $this->model instanceof Model && $this->attribute !== null; + } +} diff --git a/framework/yii/widgets/MaskedInput.php b/framework/yii/widgets/MaskedInput.php new file mode 100644 index 0000000..a5a23f5 --- /dev/null +++ b/framework/yii/widgets/MaskedInput.php @@ -0,0 +1,136 @@ + 'phone', + * 'mask' => '999-999-9999', + * )); + * ~~~ + * + * The masked text field is implemented based on the [jQuery masked input plugin](http://digitalbush.com/projects/masked-input-plugin). + * + * @author Qiang Xue + * @since 2.0 + */ +class MaskedInput extends InputWidget +{ + /** + * @var string the input mask (e.g. '99/99/9999' for date input). The following characters 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) + * - `?`: anything listed after '?' within the mask is considered optional user input + * + * Additional characters can be defined by specifying the [[charMap]] property. + */ + public $mask; + /** + * @var array the mapping between mask characters and the corresponding patterns. + * For example, `array('~' => '[+-]')` specifies that the '~' character expects '+' or '-' input. + * Defaults to null, meaning using the map as described in [[mask]]. + */ + public $charMap; + /** + * @var string the character prompting for user input. Defaults to underscore '_'. + */ + public $placeholder; + /** + * @var string a JavaScript function callback that will be invoked when user finishes the input. + */ + public $completed; + /** + * @var array the HTML attributes for the input tag. + */ + public $options = array(); + + + /** + * Initializes the widget. + * @throws InvalidConfigException if the "mask" property is not set. + */ + public function init() + { + parent::init(); + if (empty($this->mask)) { + throw new InvalidConfigException('The "mask" property must be set.'); + } + + if (!isset($this->options['id'])) { + $this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId(); + } + } + + /** + * Runs the widget. + */ + public function run() + { + if ($this->hasModel()) { + echo Html::activeTextInput($this->model, $this->attribute, $this->options); + } else { + echo Html::textInput($this->name, $this->value, $this->options); + } + $this->registerClientScript(); + } + + /** + * Registers the needed JavaScript. + */ + public function registerClientScript() + { + $options = $this->getClientOptions(); + $options = empty($options) ? '' : ',' . Json::encode($options); + $js = ''; + if (is_array($this->charMap) && !empty($this->charMap)) { + $js .= 'jQuery.mask.definitions=' . Json::encode($this->charMap) . ";\n"; + } + $id = $this->options['id']; + $js .= "jQuery(\"#{$id}\").mask(\"{$this->mask}\"{$options});"; + $this->getView()->registerAssetBundle('yii/maskedinput'); + $this->getView()->registerJs($js); + } + + /** + * @return array the options for the text field + */ + protected function getClientOptions() + { + $options = array(); + if ($this->placeholder !== null) { + $options['placeholder'] = $this->placeholder; + } + + if ($this->completed !== null) { + if ($this->completed instanceof JsExpression) { + $options['completed'] = $this->completed; + } else { + $options['completed'] = new JsExpression($this->completed); + } + } + + return $options; + } +} From a2066cbe4878ff084c9bf9d34a3fe424912e73e9 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 04:24:38 +0200 Subject: [PATCH 027/107] Created a \yii\web\VerbFilter It allows to filter actions by HTTP request methods --- framework/yii/web/VerbFilter.php | 90 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 framework/yii/web/VerbFilter.php diff --git a/framework/yii/web/VerbFilter.php b/framework/yii/web/VerbFilter.php new file mode 100644 index 0000000..9b475e3 --- /dev/null +++ b/framework/yii/web/VerbFilter.php @@ -0,0 +1,90 @@ + array( + * 'class' => \yii\web\VerbFilter::className(), + * 'actions' => array( + * 'index' => array('get'), + * 'view' => array('get'), + * 'create' => array('get', 'post'), + * 'update' => array('get', 'put', 'post'), + * 'delete' => array('post', 'delete'), + * ), + * ), + * ); + * } + * ~~~ + * + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7 + * @author Carsten Brandt + * @since 2.0 + */ +class VerbFilter extends Behavior +{ + /** + * @var array this property defines the allowed request methods for each action. + * For each action that should only support limited set of request methods + * you add an entry with the action id as array key and an array of + * allowed methods (e.g. GET, HEAD, PUT) as the value. + * If an action is not listed all request methods are considered allowed. + */ + public $actions = array(); + + + /** + * Declares event handlers for the [[owner]]'s events. + * @return array events (array keys) and the corresponding event handler methods (array values). + */ + public function events() + { + return array( + Controller::EVENT_BEFORE_ACTION => 'beforeAction', + ); + } + + /** + * @param ActionEvent $event + * @return boolean + * @throws \yii\base\HttpException when the request method is not allowed. + */ + public function beforeAction($event) + { + $action = $event->action->id; + if (isset($this->actions[$action])) { + $verb = Yii::$app->getRequest()->getRequestMethod(); + $allowed = array_map('strtoupper', $this->actions[$action]); + if (!in_array($verb, $allowed)) { + $event->isValid = false; + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7 + header('Allow: ' . implode(', ', $allowed)); + throw new HttpException(405, 'Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed)); + } + } + return $event->isValid; + } +} From 9af5466be588bbaeb8a2d413d651a1698226861e Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 05:55:14 +0300 Subject: [PATCH 028/107] typo in Html helper --- framework/yii/helpers/base/Html.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/helpers/base/Html.php b/framework/yii/helpers/base/Html.php index 90396ec..9a0001c 100644 --- a/framework/yii/helpers/base/Html.php +++ b/framework/yii/helpers/base/Html.php @@ -344,7 +344,7 @@ 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. If this is is coming from end users, you should consider [[encode()]] + * such as an image tag. If this 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 @@ -366,7 +366,7 @@ class Html /** * 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()]] + * such as an image tag. If this 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. From 9f832afb9f5ab792b50c76f5775ca3da6f83b871 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 09:17:53 +0200 Subject: [PATCH 029/107] Refactored based on comments and feedback (3rd round) --- framework/yii/bootstrap/Tabs.php | 246 +++++++++++++++++++-------------------- 1 file changed, 120 insertions(+), 126 deletions(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 0f049fd..9082c1c 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -8,7 +8,7 @@ namespace yii\bootstrap; use yii\base\InvalidConfigException; -use yii\helpers\base\ArrayHelper; +use yii\helpers\ArrayHelper; use yii\helpers\Html; /** @@ -18,26 +18,25 @@ use yii\helpers\Html; * * ```php * echo Tabs::widget(array( - * 'options' => array('class'=>'nav-tabs'), * 'items' => array( * array( - * 'header' => 'One', + * 'label' => 'One', * 'content' => 'Anim pariatur cliche...', + * 'active' => true * ), * array( - * 'header' => 'Two', + * 'label' => 'Two', * 'headerOptions' => array(...), * 'content' => 'Anim pariatur cliche...', * 'options' => array('id'=>'myveryownID'), * ), * array( - * 'header' => 'Dropdown', + * 'label' => 'Dropdown', * 'dropdown' => array( * array( * 'label' => 'DropdownA', * 'content' => 'DropdownA, Anim pariatur cliche...', * ), - * '-', // divider * array( * 'label' => 'DropdownB', * 'content' => 'DropdownB, Anim pariatur cliche...', @@ -58,28 +57,36 @@ class Tabs extends Widget * @var array list of tabs in the tabs widget. Each array element represents a single * tab with the following structure: * - * ```php - * array( - * // required, the header (HTML) of the tab - * 'header' => 'Tab label', - * // optional the HTML attributes of the tab header `LI` tag container - * 'headerOptions'=> array(...), - * // required, the content (HTML) of the tab - * 'content' => 'Mauris mauris ante, blandit et, ultrices a, suscipit eget...', - * // optional the HTML attributes of the tab content container - * 'options'=> array(...), - * // optional, an array of [[Dropdown]] widget items so to display a dropdown menu on the tab header. This - * // attribute, apart from the original [[Dropdown::items]] settings, also has two extra special keys: - * // - content: required, teh content (HTML) of teh tab the menu item is linked to - * // - contentOptions: optional the HTML attributes of the tab content container - * // note: if `dropdown` is set, then `content` will be ignored - * // important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown - * // @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 - * 'dropdown'=> array(...) + * - label: string, the tab header label. + * - headerOptions: array, optional, the HTML attributes of the tab header. + * - content: array, required if `items` is not set. The content (HTML) of the tab pane. + * - options: array, optional, the HTML attributes of the tab pane container. + * - active: boolean, optional, whether the item tab header and pane should be visibles or not. + * - items: array, optional, if not set then `content` will be required. The `items` specify a dropdown items + * configuration array. Items can also hold two extra keys: + * - active: boolean, optional, whether the item tab header and pane should be visibles or not. + * - content: string, required if `items` is not set. The content (HTML) of the tab pane. + * - contentOptions: optional, array, the HTML attributes of the tab content container. * ) * ``` */ public $items = array(); + /** + * @var array list of HTML attributes for the item container tags. This will be overwritten + * by the "options" set in individual [[items]]. The following special options are recognized: + * + * - tag: string, defaults to "div", the tag name of the item container tags. + */ + public $itemOptions = array(); + /** + * @var array list of HTML attributes for the header container tags. This will be overwritten + * by the "headerOptions" set in individual [[items]]. + */ + public $headerOptions = array(); + /** + * @var boolean whether the labels for header items should be HTML-encoded. + */ + public $encodeLabels = true; /** @@ -88,8 +95,7 @@ class Tabs extends Widget public function init() { parent::init(); - $this->addCssClass($this->options, 'nav'); - $this->items = $this->normalizeItems(); + $this->addCssClass($this->options, 'nav nav-tabs'); } @@ -98,136 +104,124 @@ class Tabs extends Widget */ public function run() { - echo Html::beginTag('ul', $this->options) . "\n"; - echo $this->renderHeaders() . "\n"; - echo Html::endTag('ul'); - echo Html::beginTag('div', array('class' => 'tab-content')) . "\n"; - echo $this->renderContents() . "\n"; - echo Html::endTag('div') . "\n"; + echo $this->renderItems(); $this->registerPlugin('tab'); } /** - * Renders tabs navigation. + * Renders tab items as specified on [[items]]. * @return string the rendering result. + * @throws InvalidConfigException. */ - protected function renderHeaders() + protected function renderItems() { $headers = array(); - foreach ($this->items['headers'] as $item) { - $options = ArrayHelper::getValue($item, 'options', array()); - if (isset($item['dropdown'])) { - $headers[] = Html::tag( - 'li', - Html::a($item['header'] . ' ', "#", array( - 'class' => 'dropdown-toggle', - 'data-toggle' => 'dropdown' - )) . - Dropdown::widget(array('items' => $item['dropdown'], 'clientOptions' => false)), - $options - ); - continue; + $panes = array(); + foreach ($this->items as $n => $item) { + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); } - $id = ArrayHelper::getValue($item, 'url'); - $headers[] = Html::tag('li', Html::a($item['header'], "{$id}", array('data-toggle' => 'tab')), $options); + if (!isset($item['content']) && !isset($item['items'])) { + throw new InvalidConfigException("The 'content' option is required."); + } + $label = $this->label($item['label']); + $headerOptions = $this->mergedOptions($item, 'headerOptions'); + + if (isset($item['items'])) { + $label .= ' '; + $this->addCssClass($headerOptions, 'dropdown'); + + if ($this->normalizeItems($item['items'], $panes)) { + $this->addCssClass($headerOptions, 'active'); + } + + $header = Html::a($label, "#", array('class' => 'dropdown-toggle', 'data-toggle' => 'dropdown')) . "\n"; + $header .= Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false)); + + } else { + $options = $this->mergedOptions($item, 'itemOptions', 'options'); + $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n); + + $this->addCssClass($options, 'tab-pane'); + if (ArrayHelper::remove($item, 'active')) { + $this->addCssClass($options, 'active'); + $this->addCssClass($headerOptions, 'active'); + } + $header = Html::a($label, '#' . $options['id'], array('data-toggle' => 'tab', 'tabindex' => '-1')); + $panes[] = Html::tag('div', $item['content'], $options); + } + $headers[] = Html::tag('li', $header, array_merge($this->headerOptions, $headerOptions)); } - return implode("\n", $headers); + + return Html::tag('ul', implode("\n", $headers), $this->options) . "\n" . + Html::tag('div', implode("\n", $panes), array('class' => 'tab-content')); } /** - * Renders tabs contents. - * @return string the rendering result. + * Returns encoded if specified on [[encodeLabels]], original string otherwise. + * @param string $content the label text to encode or return + * @return string the resulting label. */ - protected function renderContents() + protected function label($content) { - $contents = array(); - foreach ($this->items['contents'] as $item) { - $options = ArrayHelper::getValue($item, 'options', array()); - $this->addCssClass($options, 'tab-pane'); - $contents[] = Html::tag('div', $item['content'], $options); + return $this->encodeLabels ? Html::encode($content) : $content; + } + /** + * Returns array of options merged with specified attribute array. The availabel options are: + * - [[itemOptions]] + * - [[headerOptions]] + * @param array $item the item to merge the options with + * @param string $name the property name. + * @param string $key the key to extract. If null, it is assumed to be the same as `$name`. + * @return array the merged array options. + */ + protected function mergedOptions($item, $name, $key = null) + { + if ($key === null) { + $key = $name; } - return implode("\n", $contents); + return array_merge($this->{$name}, ArrayHelper::getValue($item, $key, array())); } /** - * Normalizes the [[items]] property to divide headers from contents and to ease its rendering when there are - * headers with dropdown menus. - * @return array the normalized tabs items + * Normalizes dropdown item options by removing tab specific keys `content` and `contentOptions`, and also + * configure `panes` accordingly. + * @param array $items the dropdown items configuration. + * @param array $panes the panes reference array. + * @return boolean whether any of the dropdown items is `active` or not. * @throws InvalidConfigException */ - protected function normalizeItems() + protected function normalizeItems(&$items, &$panes) { - $items = array(); - $index = 0; - foreach ($this->items as $item) { - if (!isset($item['header'])) { - throw new InvalidConfigException("The 'header' option is required."); + $itemActive = false; + + foreach ($items as $n => &$item) { + if (is_string($item)) { + continue; } - if (!isset($item['content']) && !isset($item['dropdown'])) { + if (!isset($item['content']) && !isset($item['items'])) { throw new InvalidConfigException("The 'content' option is required."); } - $header = $content = array(); - $header['header'] = ArrayHelper::getValue($item, 'header'); - $header['options'] = ArrayHelper::getValue($item, 'headerOptions', array()); - if ($index === 0) { - $this->addCssClass($header['options'], 'active'); + + $content = ArrayHelper::remove($item, 'content'); + $options = ArrayHelper::remove($item, 'contentOptions', array()); + $this->addCssClass($options, 'tab-pane'); + if (ArrayHelper::remove($item, 'active')) { + $this->addCssClass($options, 'active'); + $this->addCssClass($item['options'], 'active'); + $itemActive = true; } - if (isset($item['dropdown'])) { - $this->addCssClass($header['options'], 'dropdown'); - - $self = $this; - $dropdown = function ($list) use (&$dropdown, &$items, &$index, $self) { - $ddItems = $content = array(); - foreach ($list as $item) { - if (is_string($item)) { - $ddItems[] = $item; - continue; - } - if (!isset($item['content']) && !isset($item['items'])) { - throw new InvalidConfigException("The 'content' option is required."); - } - if (isset($item['items'])) { - $item['items'] = $dropdown($item['items']); - } else { - $content['content'] = ArrayHelper::remove($item, 'content'); - $content['options'] = ArrayHelper::remove($item, 'contentOptions', array()); - if ($index === 0) { - $self->addCssClass($content['options'], 'active'); - $self->addCssClass($item['options'], 'active'); - } - $content['options']['id'] = ArrayHelper::getValue( - $content['options'], - 'id', - $self->options['id'] . '-tab' . $index++); - $item['url'] = '#' . $content['options']['id']; - $item['urlOptions']['data-toggle'] = 'tab'; - - $items['contents'][] = $content; - } - $ddItems[] = $item; - } - return $ddItems; - }; - $header['dropdown'] = $dropdown($item['dropdown']); - } else { - $content['content'] = ArrayHelper::getValue($item, 'content'); - $content['options'] = ArrayHelper::getValue($item, 'options', array()); - if ($index === 0) { - $this->addCssClass($content['options'], 'active'); - } - $content['options']['id'] = ArrayHelper::getValue( - $content['options'], - 'id', - $this->options['id'] . '-tab' . $index++); + $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-dd-tab' . $n); + $item['url'] = '#' . $options['id']; + $item['linkOptions']['data-toggle'] = 'tab'; - $header['url'] = "#" . ArrayHelper::getValue($content['options'], 'id'); - $items['contents'][] = $content; - } - $items['headers'][] = $header; + $panes[] = Html::tag('div', $content, $options); + + unset($item); } - return $items; + return $itemActive; } } \ No newline at end of file From 6ce2f8f1374e74370b72a186e1a19a90a499a9ad Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 09:45:04 +0200 Subject: [PATCH 030/107] silly typo --- framework/yii/bootstrap/Tabs.php | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 9082c1c..4be1d24 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -68,7 +68,6 @@ class Tabs extends Widget * - content: string, required if `items` is not set. The content (HTML) of the tab pane. * - contentOptions: optional, array, the HTML attributes of the tab content container. * ) - * ``` */ public $items = array(); /** From 1c4cf30464e786f2c157058fe7d67a273c0fc5ea Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 09:47:36 +0200 Subject: [PATCH 031/107] another typo fix --- framework/yii/bootstrap/Tabs.php | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 4be1d24..178cc71 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -67,7 +67,6 @@ class Tabs extends Widget * - active: boolean, optional, whether the item tab header and pane should be visibles or not. * - content: string, required if `items` is not set. The content (HTML) of the tab pane. * - contentOptions: optional, array, the HTML attributes of the tab content container. - * ) */ public $items = array(); /** From 4ebc0ae7347d550aa967708a6f6d8ee47b51b40b Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 09:49:41 +0200 Subject: [PATCH 032/107] fix phpDoc --- framework/yii/bootstrap/Tabs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 178cc71..1af79b4 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -57,7 +57,7 @@ class Tabs extends Widget * @var array list of tabs in the tabs widget. Each array element represents a single * tab with the following structure: * - * - label: string, the tab header label. + * - label: string, required, the tab header label. * - headerOptions: array, optional, the HTML attributes of the tab header. * - content: array, required if `items` is not set. The content (HTML) of the tab pane. * - options: array, optional, the HTML attributes of the tab pane container. From cbb33bd164b21f504ceb461ec547604061627da3 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 10:54:01 +0200 Subject: [PATCH 033/107] refactored based on comments (2nd round) --- framework/yii/bootstrap/Nav.php | 64 +++++++++++++++++++---------------- framework/yii/bootstrap/NavBar.php | 68 ++++++++++++++++++++++++++------------ 2 files changed, 81 insertions(+), 51 deletions(-) diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index f373961..c247433 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -8,7 +8,7 @@ namespace yii\bootstrap; use yii\base\InvalidConfigException; -use yii\helpers\base\ArrayHelper; +use yii\helpers\ArrayHelper; use yii\helpers\Html; /** @@ -22,12 +22,12 @@ use yii\helpers\Html; * array( * 'label' => 'Home', * 'url' => '/', - * 'options' => array(...), + * 'linkOptions' => array(...), * 'active' => true, * ), * array( * 'label' => 'Dropdown', - * 'dropdown' => array( + * 'items' => array( * array( * 'label' => 'DropdownA', * 'url' => '#', @@ -52,26 +52,25 @@ class Nav extends Widget * @var array list of items in the nav widget. Each array element represents a single * menu item with the following structure: * - * ```php - * array( - * // required, the menu item label. - * 'label' => 'Nav item label', - * // optional, the URL of the menu item. Defaults to "#" - * 'url'=> '#', - * // optional, the HTML options of the URL. - * 'urlOptions' => array(...), - * // optional the HTML attributes of the item container (LI). - * 'options' => array(...), - * // optional, an array of [[Dropdown]] widget items so to display a dropdown menu on the tab header. - * // important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown - * // @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 - * 'dropdown'=> array(...) - * ) - * ``` + * - label: string, required, the nav item label. + * - url: optional, the item's URL. Defaults to "#". + * - linkOptions: array, optional, the HTML attributes of the item's link. + * - options: array, optional, the HTML attributes of the item container (LI). + * - active: boolean, optional, whether the item should be on active state or not. + * - items: array, optional, the configuration of specify the item's dropdown menu. You can optionally set this as + * a string (ie. `'items'=> Dropdown::widget(array(...))` + * - important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown. * - * Optionally, you can also use a plain string instead of an array element. + * **Note:** Optionally, you can also use a plain string instead of an array element. + * + * @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 + * @see [[Dropdown]] */ public $items = array(); + /** + * @var boolean whether the nav items labels should be HTML-encoded. + */ + public $encodeLabels = true; /** @@ -79,6 +78,7 @@ class Nav extends Widget */ public function init() { + parent::init(); $this->addCssClass($this->options, 'nav'); } @@ -88,6 +88,7 @@ class Nav extends Widget public function run() { echo $this->renderItems(); + $this->registerPlugin('dropdown'); } /** @@ -117,21 +118,26 @@ class Nav extends Widget if (!isset($item['label'])) { throw new InvalidConfigException("The 'label' option is required."); } - $label = $item['label']; - $url = ArrayHelper::getValue($item, 'url', '#'); + $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label']; $options = ArrayHelper::getValue($item, 'options', array()); - $urlOptions = ArrayHelper::getValue($item, 'urlOptions', array()); - $dropdown = null; + $dropdown = ArrayHelper::getValue($item, 'items'); + $url = Html::url(ArrayHelper::getValue($item, 'url', '#')); + $linkOptions = ArrayHelper::getValue($item, 'linkOptions', array()); + + if(ArrayHelper::getValue($item, 'active')) { + $this->addCssClass($options, 'active'); + } - // does it has a dropdown widget? - if (isset($item['dropdown'])) { - $urlOptions['data-toggle'] = 'dropdown'; + if ($dropdown !== null) { + $linkOptions['data-toggle'] = 'dropdown'; $this->addCssClass($options, 'dropdown'); $this->addCssClass($urlOptions, 'dropdown-toggle'); $label .= ' ' . Html::tag('b', '', array('class' => 'caret')); - $dropdown = Dropdown::widget(array('items' => $item['dropdown'], 'clientOptions' => false)); + $dropdown = is_string($dropdown) + ? $dropdown + : Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false)); } - return Html::tag('li', Html::a($label, $url, $urlOptions) . $dropdown, $options); + return Html::tag('li', Html::a($label, $url, $linkOptions) . $dropdown, $options); } } \ No newline at end of file diff --git a/framework/yii/bootstrap/NavBar.php b/framework/yii/bootstrap/NavBar.php index 1ed5a9a..cbcd2d3 100644 --- a/framework/yii/bootstrap/NavBar.php +++ b/framework/yii/bootstrap/NavBar.php @@ -8,7 +8,7 @@ namespace yii\bootstrap; use yii\base\InvalidConfigException; -use yii\helpers\base\ArrayHelper; +use yii\helpers\ArrayHelper; use yii\helpers\Html; /** @@ -18,6 +18,7 @@ use yii\helpers\Html; * * ```php * echo NavBar::widget(array( + * 'brandLabel' => 'NavBar Test', * 'items' => array( * // a Nav widget * array( @@ -33,17 +34,18 @@ use yii\helpers\Html; * ), * array( * 'label' => 'Dropdown', - * // configure a dropdown menu - * 'dropdown' => array( - * array( - * 'label' => 'DropdownA', - * 'url' => '#', - * ), - * array( - * 'label' => 'DropdownB', - * 'url' => '#' - * ), - * ) + * 'content' => new Dropdown(array( + * 'items' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '#', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#' + * ), + * ) + * ), * ), * ) * ), @@ -62,7 +64,20 @@ use yii\helpers\Html; */ class NavBar extends Widget { - public $brand; + /** + * @var string the text of the brand. + * @see http://twitter.github.io/bootstrap/components.html#navbar + */ + public $brandLabel; + /** + * @param array|string $url the URL for the brand's hyperlink tag. This parameter will be processed by [[Html::url()]] + * and will be used for the "href" attribute of the brand link. Defaults to site root. + */ + public $brandRoute = '/'; + /** + * @var array the HTML attributes of the brand link. + */ + public $brandOptions = array(); /** * @var array list of menu items in the navbar widget. Each array element represents a single * menu item with the following structure: @@ -83,6 +98,10 @@ class NavBar extends Widget * Optionally, you can also use a plain string instead of an array element. */ public $items = array(); + /** + * @var string the generated brand url if specified by [[brandLabel]] + */ + protected $brand; /** @@ -91,7 +110,10 @@ class NavBar extends Widget public function init() { parent::init(); + $this->clientOptions = false; $this->addCssClass($this->options, 'navbar'); + $this->addCssClass($this->brandOptions, 'brand'); + $this->brand = Html::a($this->brandLabel, $this->brandRoute, $this->brandOptions); } /** @@ -102,19 +124,20 @@ class NavBar extends Widget echo Html::beginTag('div', $this->options); echo $this->renderItems(); echo Html::endTag('div'); + $this->getView()->registerAssetBundle('yii/bootstrap'); } /** + * Renders the items. * @return string the rendering items. */ protected function renderItems() { $items = array(); - foreach ($this->items as $item) { $items[] = $this->renderItem($item); } - $contents =implode("\n", $items); + $contents = implode("\n", $items); if (self::$responsive === true) { $this->getView()->registerAssetBundle('yii/bootstrap/collapse'); $contents = @@ -127,6 +150,7 @@ class NavBar extends Widget } else { $contents = $this->brand . "\n" . $contents; } + return Html::tag('div', $contents, array('class' => 'navbar-inner')); } @@ -143,11 +167,11 @@ class NavBar extends Widget return $item; } $config = ArrayHelper::getValue($item, 'options', array()); - $config['class'] = ArrayHelper::getValue($item, 'class', 'yii\bootstrap\Nav'); - $widget = \Yii::createObject($config); - ob_start(); - $widget->run(); - return ob_get_clean(); + $config['clientOptions'] = false; + + $class = ArrayHelper::getValue($item, 'class', 'yii\bootstrap\Nav'); + + return $class::widget($config); } /** @@ -160,10 +184,10 @@ class NavBar extends Widget for ($i = 0; $i < 3; $i++) { $items[] = Html::tag('span', '', array('class' => 'icon-bar')); } - return Html::tag('a', implode("\n", $items), array( + return Html::a(implode("\n", $items), null, array( 'class' => 'btn btn-navbar', 'data-toggle' => 'collapse', 'data-target' => 'div.navbar-collapse', - )) . "\n"; + )); } } \ No newline at end of file From 1e9cc35fd8ce005033668b0eee77d1edbfdccaf8 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 10:54:53 +0200 Subject: [PATCH 034/107] fix namespaces --- framework/yii/bootstrap/Carousel.php | 2 +- framework/yii/bootstrap/Collapse.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/bootstrap/Carousel.php b/framework/yii/bootstrap/Carousel.php index 3d38b54..f8904fa 100644 --- a/framework/yii/bootstrap/Carousel.php +++ b/framework/yii/bootstrap/Carousel.php @@ -8,7 +8,7 @@ namespace yii\bootstrap; use yii\base\InvalidConfigException; -use yii\helpers\base\ArrayHelper; +use yii\helpers\ArrayHelper; use yii\helpers\Html; /** diff --git a/framework/yii/bootstrap/Collapse.php b/framework/yii/bootstrap/Collapse.php index d83df3c..a7929e3 100644 --- a/framework/yii/bootstrap/Collapse.php +++ b/framework/yii/bootstrap/Collapse.php @@ -8,7 +8,7 @@ namespace yii\bootstrap; use yii\base\InvalidConfigException; -use yii\helpers\base\ArrayHelper; +use yii\helpers\ArrayHelper; use yii\helpers\Html; /** From e8d3a7ff9287b683b2248d95f1affd3c45610a34 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 10:58:21 +0200 Subject: [PATCH 035/107] register asset bundle instead --- framework/yii/bootstrap/Nav.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index c247433..7a29ecd 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -88,7 +88,7 @@ class Nav extends Widget public function run() { echo $this->renderItems(); - $this->registerPlugin('dropdown'); + $this->getView()->registerAssetBundle('yii/bootstrap'); } /** From 7d0b95c21aa98b5a308071724ed864defff0cbd0 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 13:22:00 +0200 Subject: [PATCH 036/107] modified phpDoc --- framework/yii/bootstrap/Dropdown.php | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/framework/yii/bootstrap/Dropdown.php b/framework/yii/bootstrap/Dropdown.php index 8ba0eef..f4b5489 100644 --- a/framework/yii/bootstrap/Dropdown.php +++ b/framework/yii/bootstrap/Dropdown.php @@ -24,24 +24,14 @@ class Dropdown extends Widget /** * @var array list of menu items in the dropdown. Each array element represents a single * menu with the following structure: + * - label: string, required, the label of the item link + * - url: string, optional, the url of the item link. Defaults to "#". + * - linkOptions: array, optional, the HTML attributes of the item link. + * - options: array, optional, the HTML attributes of the item. + * - items: array, optional, the dropdown items configuration array. if `items` is set, then `url` of the parent + * item will be ignored and automatically set to "#" * - * ```php - * array( - * // required, the label of the item link - * 'label' => 'Menu label', - * // optional, url of the item link - * 'url' => '', - * // optional the HTML attributes of the item link - * 'linkOptions'=> array(...), - * // optional the HTML attributes of the item - * 'options'=> array(...), - * // optional, an array of items that configure a sub menu of the item - * // note: if `items` is set, then `url` of the parent item will be ignored and automatically set to "#" - * // important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown - * // @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 - * 'items'=> array(...) - * ) - * ``` + * @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 */ public $items = array(); /** From e2073612556db4f74fc3089d6b7dd556e3af69c0 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 29 May 2013 07:47:33 -0400 Subject: [PATCH 037/107] Refactored the widget usage with ActiveField. --- apps/basic/views/site/contact.php | 12 ++----- framework/yii/widgets/ActiveField.php | 12 ++++++- framework/yii/widgets/Captcha.php | 64 ++++++++++++++++++++++++++++------- 3 files changed, 65 insertions(+), 23 deletions(-) diff --git a/apps/basic/views/site/contact.php b/apps/basic/views/site/contact.php index e740d0f..14a82bc 100644 --- a/apps/basic/views/site/contact.php +++ b/apps/basic/views/site/contact.php @@ -31,15 +31,9 @@ $this->params['breadcrumbs'][] = $this->title; field($model, 'email')->textInput(); ?> field($model, 'subject')->textInput(); ?> field($model, 'body')->textArea(array('rows' => 6)); ?> - field($model, 'verifyCode'); - echo $field->begin() - . $field->label() - . Captcha::widget() - . Html::activeTextInput($model, 'verifyCode', array('class' => 'input-medium')) - . $field->error() - . $field->end(); - ?> + field($model, 'verifyCode')->widget(Captcha::className(), array( + 'options' => array('class' => 'input-medium'), + )); ?>
    'btn btn-primary')); ?>
    diff --git a/framework/yii/widgets/ActiveField.php b/framework/yii/widgets/ActiveField.php index e3e7f2b..346f743 100644 --- a/framework/yii/widgets/ActiveField.php +++ b/framework/yii/widgets/ActiveField.php @@ -557,7 +557,14 @@ class ActiveField extends Component } /** - * Renders a field containing a widget. + * Renders a field containing an input widget. + * + * 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. + * * @param string $class the widget class name * @param array $config name-value pairs that will be used to initialize the widget * @return string the rendering result @@ -565,6 +572,9 @@ class ActiveField extends Component public function widget($class, $config = array()) { /** @var \yii\base\Widget $class */ + $config['model'] = $this->model; + $config['attribute'] = $this->attribute; + $config['view'] = $this->form->getView(); return $this->render($class::widget($config)); } } diff --git a/framework/yii/widgets/Captcha.php b/framework/yii/widgets/Captcha.php index 918e30c..ffe8802 100644 --- a/framework/yii/widgets/Captcha.php +++ b/framework/yii/widgets/Captcha.php @@ -9,13 +9,12 @@ namespace yii\widgets; use Yii; use yii\base\InvalidConfigException; -use yii\base\Widget; use yii\helpers\Html; use yii\helpers\Json; use yii\web\CaptchaAction; /** - * Captcha renders a CAPTCHA image element. + * Captcha renders a CAPTCHA image and an input field that takes user-entered verification code. * * Captcha is used together with [[CaptchaAction]] provide [CAPTCHA](http://en.wikipedia.org/wiki/Captcha) * - a way of preventing Website spamming. @@ -32,7 +31,7 @@ use yii\web\CaptchaAction; * @author Qiang Xue * @since 2.0 */ -class Captcha extends Widget +class Captcha extends InputWidget { /** * @var string the route of the action that generates the CAPTCHA images. @@ -40,27 +39,66 @@ class Captcha extends Widget */ public $captchaAction = 'site/captcha'; /** - * @var array HTML attributes to be applied to the rendered image element. + * @var array HTML attributes to be applied to the text input field. */ public $options = array(); - + /** + * @var array HTML attributes to be applied to the CAPTCHA image tag. + */ + public $imageOptions = array(); + /** + * @var string the template for arranging the CAPTCHA image tag and the text input tag. + * In this template, the token `{image}` will be replaced with the actual image tag, + * while `{input}` will be replaced with the text input tag. + */ + public $template = '{image} {input}'; /** - * Renders the widget. + * Initializes the widget. */ - public function run() + public function init() { + parent::init(); + $this->checkRequirements(); if (!isset($this->options['id'])) { - $this->options['id'] = $this->getId(); + $this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId(); + } + if (!isset($this->imageOptions['id'])) { + $this->imageOptions['id'] = $this->options['id'] . '-image'; + } + } + + /** + * Renders the widget. + */ + public function run() + { + $this->registerClientScript(); + if ($this->hasModel()) { + $input = Html::activeTextInput($this->model, $this->attribute, $this->options); + } else { + $input = Html::textInput($this->name, $this->value, $this->options); } - $id = $this->options['id']; - $options = Json::encode($this->getClientOptions()); - $this->view->registerAssetBundle('yii/captcha'); - $this->view->registerJs("jQuery('#$id').yiiCaptcha($options);"); $url = Yii::$app->getUrlManager()->createUrl($this->captchaAction, array('v' => uniqid())); - echo Html::img($url, $this->options); + $image = Html::img($url, $this->imageOptions); + echo strtr($this->template, array( + '{input}' => $input, + '{image}' => $image, + )); + } + + /** + * Registers the needed JavaScript. + */ + public function registerClientScript() + { + $options = $this->getClientOptions(); + $options = empty($options) ? '' : Json::encode($options); + $id = $this->imageOptions['id']; + $this->getView()->registerAssetBundle('yii/captcha'); + $this->getView()->registerJs("jQuery('#$id').yiiCaptcha($options);"); } /** From 3647c3f12846919630f1ecfc5f62e897350c36c8 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 13:54:47 +0200 Subject: [PATCH 038/107] added button group dropdowns --- framework/yii/bootstrap/Button.php | 147 +++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 framework/yii/bootstrap/Button.php diff --git a/framework/yii/bootstrap/Button.php b/framework/yii/bootstrap/Button.php new file mode 100644 index 0000000..bdc29ca --- /dev/null +++ b/framework/yii/bootstrap/Button.php @@ -0,0 +1,147 @@ + 'Action', + * 'items' => Dropdown::widget(array( + * 'clientOptions' => false, + * 'items' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '/', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#', + * ), + * ), + * ), + * ), + * )); + * + * // split button group using `items` dropdown configuration + * echo \yii\bootstrap\Button::widget(array( + * 'label' => 'Action', + * 'split' => true, + * 'items' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '/', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#', + * ), + * ), + * )); + * ``` + * @see http://twitter.github.io/bootstrap/javascript.html#buttons + * @see http://twitter.github.io/bootstrap/components.html#buttonDropdowns + * @author Antonio Ramirez + * @since 2.0 + */ +class Button extends Widget +{ + /** + * @var string the button label + */ + public $label; + /** + * @var array list of menu items in the dropdown. Each array element represents a single + * menu with the following structure: + * + * - label: string, required, the label of the item link + * - url: string, optional, the url of the item link. Defaults to "#". + * - linkOptions: array, optional, the HTML attributes of the item link. + * - options: array, optional, the HTML attributes of the item. + * - items: array, optional, the dropdown items configuration array. + * + * @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 + * @see [[Dropdown]] + */ + public $items = array(); + /** + * @var boolean whether to display a group or split styled button group. + */ + public $split = false; + /** + * @var boolean whether the labels for dropdown items should be HTML-encoded. + */ + public $encodeLabels = true; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + * @throws InvalidConfigException + */ + public function init() + { + if ($this->label === null) { + throw new InvalidConfigException("The 'label' option is required."); + } + parent::init(); + $this->addCssClass($this->options, 'btn-group'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', $this->options) . "\n"; + echo $this->renderLabel() . "\n"; + echo $this->renderItems() . "\n"; + echo Html::endTag('div') . "\n"; + $this->registerPlugin('button'); + } + + /** + * Generates the button label. + * @return string the rendering result. + */ + protected function renderLabel() + { + $label = $this->encodeLabels ? Html::encode($this->label) : $this->label; + $options = array('class' => 'btn dropdown-toggle', 'data-toggle' => 'dropdown'); + if ($this->split) { + $tag = 'button'; + $label .= Html::tag('button', '', $options); + $options = array('class' => 'btn'); + } else { + $tag = 'a'; + $options['href'] = '#'; + } + return Html::tag($tag, $label, $options); + } + + /** + * Generates the dropdown menu as specified on [[items]]. + * @return string the rendering result. + */ + protected function renderItems() + { + if (is_string($this->items)) { + return $this->items; + } + $config = array('items' => $this->items, 'clientOptions' => false); + return Dropdown::widget($config); + } +} \ No newline at end of file From f651e72372fdca6b0a0eca3767fe5fb1865b68f0 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 14:13:19 +0200 Subject: [PATCH 039/107] refactored label render --- framework/yii/bootstrap/Button.php | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/framework/yii/bootstrap/Button.php b/framework/yii/bootstrap/Button.php index bdc29ca..94cdae8 100644 --- a/framework/yii/bootstrap/Button.php +++ b/framework/yii/bootstrap/Button.php @@ -64,6 +64,10 @@ class Button extends Widget */ public $label; /** + * @var array the HTML attributes of the button. + */ + public $buttonOptions = array(); + /** * @var array list of menu items in the dropdown. Each array element represents a single * menu with the following structure: * @@ -120,16 +124,23 @@ class Button extends Widget protected function renderLabel() { $label = $this->encodeLabels ? Html::encode($this->label) : $this->label; - $options = array('class' => 'btn dropdown-toggle', 'data-toggle' => 'dropdown'); + $this->addCssClass($this->buttonOptions, 'btn'); + $splitButton = ''; if ($this->split) { $tag = 'button'; - $label .= Html::tag('button', '', $options); - $options = array('class' => 'btn'); + $options = $this->buttonOptions; + $this->buttonOptions['data-toggle'] = 'dropdown'; + $this->addCssClass($this->buttonOptions, 'dropdown-toggle'); + $splitButton = Html::tag('button', '', $this->buttonOptions); } else { $tag = 'a'; - $options['href'] = '#'; + $options = $this->buttonOptions; + if (!isset($options['href'])) { + $options['href'] = '#'; + } + $options['data-toggle'] = 'dropdown'; } - return Html::tag($tag, $label, $options); + return Html::tag($tag, $label, $options) . "\n" . $splitButton; } /** From 1165508a6aef6395b93c1415c430965900913acf Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 14:22:15 +0200 Subject: [PATCH 040/107] updated composer for example apps issue #439 --- apps/advanced/composer.lock | 10 +++++----- apps/basic/composer.lock | 8 ++++---- apps/benchmark/composer.lock | 10 +++++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/advanced/composer.lock b/apps/advanced/composer.lock index 761ae2f..516ae32 100644 --- a/apps/advanced/composer.lock +++ b/apps/advanced/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "0b96a35ac23eae4e84ffd588653e88d2", + "hash": "05f7bcd0e99931b52415eaeb62d54daf", "packages": [ { "name": "yiisoft/yii2", @@ -11,12 +11,12 @@ "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b" + "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/15a8d0559260e39954a8eb6de0d28bfb7de95e7b", - "reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", + "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", "shasum": "" }, "require": { @@ -97,7 +97,7 @@ "framework", "yii" ], - "time": "2013-05-25 20:59:05" + "time": "2013-05-29 02:55:14" }, { "name": "yiisoft/yii2-composer", diff --git a/apps/basic/composer.lock b/apps/basic/composer.lock index a66bbea..fe87399 100644 --- a/apps/basic/composer.lock +++ b/apps/basic/composer.lock @@ -11,12 +11,12 @@ "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b" + "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/15a8d0559260e39954a8eb6de0d28bfb7de95e7b", - "reference": "15a8d0559260e39954a8eb6de0d28bfb7de95e7b", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", + "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", "shasum": "" }, "require": { @@ -97,7 +97,7 @@ "framework", "yii" ], - "time": "2013-05-25 20:59:05" + "time": "2013-05-29 02:55:14" }, { "name": "yiisoft/yii2-composer", diff --git a/apps/benchmark/composer.lock b/apps/benchmark/composer.lock index c7a0324..bc8c472 100644 --- a/apps/benchmark/composer.lock +++ b/apps/benchmark/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "5ce5f1ad2aa7d7e31c3e216b8ce23387", + "hash": "25a338a613af40ef87f2e280057f0d8c", "packages": [ { "name": "yiisoft/yii2", @@ -11,12 +11,12 @@ "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "f3c3d9d764de25fc46711bce2259274bcceade1c" + "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/f3c3d9d764de25fc46711bce2259274bcceade1c", - "reference": "f3c3d9d764de25fc46711bce2259274bcceade1c", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", + "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", "shasum": "" }, "require": { @@ -97,7 +97,7 @@ "framework", "yii" ], - "time": "2013-05-26 21:57:00" + "time": "2013-05-29 02:55:14" } ], "packages-dev": [ From fce33a25ddfe47abb4224fe35c5cb8e723cd4b68 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 14:32:33 +0200 Subject: [PATCH 041/107] fixed indentation and button group label bug --- framework/yii/bootstrap/Button.php | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/framework/yii/bootstrap/Button.php b/framework/yii/bootstrap/Button.php index 94cdae8..b340a4c 100644 --- a/framework/yii/bootstrap/Button.php +++ b/framework/yii/bootstrap/Button.php @@ -22,18 +22,17 @@ use yii\helpers\Html; * 'label' => 'Action', * 'items' => Dropdown::widget(array( * 'clientOptions' => false, - * 'items' => array( - * array( - * 'label' => 'DropdownA', - * 'url' => '/', - * ), - * array( - * 'label' => 'DropdownB', - * 'url' => '#', - * ), - * ), + * 'items' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '/', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#', + * ), * ), - * ), + * )), * )); * * // split button group using `items` dropdown configuration @@ -134,10 +133,12 @@ class Button extends Widget $splitButton = Html::tag('button', '', $this->buttonOptions); } else { $tag = 'a'; + $label .= ' '; $options = $this->buttonOptions; if (!isset($options['href'])) { $options['href'] = '#'; } + $this->addCssClass($options, 'dropdown-toggle'); $options['data-toggle'] = 'dropdown'; } return Html::tag($tag, $label, $options) . "\n" . $splitButton; From d3bbc5fe401334b52d3176256070cd71266c72c6 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 14:36:15 +0200 Subject: [PATCH 042/107] small fix --- framework/yii/bootstrap/Dropdown.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/bootstrap/Dropdown.php b/framework/yii/bootstrap/Dropdown.php index f4b5489..0594b08 100644 --- a/framework/yii/bootstrap/Dropdown.php +++ b/framework/yii/bootstrap/Dropdown.php @@ -100,6 +100,6 @@ class Dropdown extends Widget */ protected function dropdown($items) { - return Dropdown::widget(array('items' => $items, 'clientOptions' => false)); + return static::widget(array('items' => $items, 'clientOptions' => false)); } } \ No newline at end of file From 4d1666704a89c1b6400a94cd0fc1d4ca4427afff Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 30 May 2013 00:17:39 +0400 Subject: [PATCH 043/107] jQuery UI resizeable widget --- framework/yii/jui/Resizable.php | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 framework/yii/jui/Resizable.php diff --git a/framework/yii/jui/Resizable.php b/framework/yii/jui/Resizable.php new file mode 100644 index 0000000..ffc3501 --- /dev/null +++ b/framework/yii/jui/Resizable.php @@ -0,0 +1,52 @@ + array( + * 'grid' => array(20, 10), + * ), + * )); + * + * echo 'Resizable contents here...'; + * + * Resizable::end(); + * ``` + * + * @see http://api.jqueryui.com/resizable/ + * @author Alexander Kochetov + * @since 2.0 + */ +class Resizable extends Widget +{ + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + echo Html::beginTag('div', $this->options) . "\n"; + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::endTag('div') . "\n"; + $this->registerWidget('resizable'); + } +} From 77e34ac91f7ff7b97f797e0b5203cf56ed436899 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 30 May 2013 00:23:12 +0400 Subject: [PATCH 044/107] jQuery UI draggable widget --- framework/yii/jui/Draggable.php | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 framework/yii/jui/Draggable.php diff --git a/framework/yii/jui/Draggable.php b/framework/yii/jui/Draggable.php new file mode 100644 index 0000000..adf92f1 --- /dev/null +++ b/framework/yii/jui/Draggable.php @@ -0,0 +1,52 @@ + array( + * 'modal' => true, + * ), + * )); + * + * echo 'Draggable contents here...'; + * + * Draggable::end(); + * ``` + * + * @see http://api.jqueryui.com/draggable/ + * @author Alexander Kochetov + * @since 2.0 + */ +class Draggable extends Widget +{ + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + echo Html::beginTag('div', $this->options) . "\n"; + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::endTag('div') . "\n"; + $this->registerWidget('draggable', false); + } +} From 280727408fe0e8a77c4ae3964397a3789efca927 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Wed, 29 May 2013 22:23:44 +0200 Subject: [PATCH 045/107] fixed wrong usage of $.inArray in validators fixes #448 --- framework/yii/assets/yii.validation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/assets/yii.validation.js b/framework/yii/assets/yii.validation.js index 2748a74..015040e 100644 --- a/framework/yii/assets/yii.validation.js +++ b/framework/yii/assets/yii.validation.js @@ -87,8 +87,8 @@ yii.validation = (function ($) { if (options.skipOnEmpty && isEmpty(value)) { return; } - var valid = !options.not && $.inArray(value, options.range) - || options.not && !$.inArray(value, options.range); + var valid = !options.not && $.inArray(value, options.range) > -1 + || options.not && $.inArray(value, options.range) == -1; if (!valid) { messages.push(options.message); From 670a16200da80c364e37c9527fecba00a7d52e55 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 30 May 2013 00:25:14 +0400 Subject: [PATCH 046/107] Change example option --- framework/yii/jui/Draggable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/jui/Draggable.php b/framework/yii/jui/Draggable.php index adf92f1..c73f269 100644 --- a/framework/yii/jui/Draggable.php +++ b/framework/yii/jui/Draggable.php @@ -17,7 +17,7 @@ use yii\helpers\Html; * ```php * Draggable::begin(array( * 'clientOptions' => array( - * 'modal' => true, + * 'grid' => array(50, 20), * ), * )); * From d55b1049767ea7dd97d9ff1a665222a72e3f31b4 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 30 May 2013 00:30:59 +0400 Subject: [PATCH 047/107] jQuery UI droppable widget --- framework/yii/jui/Droppable.php | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 framework/yii/jui/Droppable.php diff --git a/framework/yii/jui/Droppable.php b/framework/yii/jui/Droppable.php new file mode 100644 index 0000000..2f580bd --- /dev/null +++ b/framework/yii/jui/Droppable.php @@ -0,0 +1,52 @@ + array( + * 'accept' => '.special', + * ), + * )); + * + * echo 'Droppable body here...'; + * + * Droppable::end(); + * ``` + * + * @see http://api.jqueryui.com/droppable/ + * @author Alexander Kochetov + * @since 2.0 + */ +class Droppable extends Widget +{ + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + echo Html::beginTag('div', $this->options) . "\n"; + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::endTag('div') . "\n"; + $this->registerWidget('droppable', false); + } +} From 2e574b9646c54ce5191bc4d8d71d9b8feb45ac23 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 30 May 2013 00:39:43 +0400 Subject: [PATCH 048/107] jQuery UI selectable widget --- framework/yii/jui/Selectable.php | 116 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 framework/yii/jui/Selectable.php diff --git a/framework/yii/jui/Selectable.php b/framework/yii/jui/Selectable.php new file mode 100644 index 0000000..a1a9b5d --- /dev/null +++ b/framework/yii/jui/Selectable.php @@ -0,0 +1,116 @@ + array( + * 'Item 1', + * array( + * 'content' => 'Item2', + * ), + * array( + * 'content' => 'Item3', + * 'options' => array( + * 'tag' => 'li', + * ), + * ), + * ), + * 'options' => array( + * 'tag' => 'ul', + * ), + * 'itemOptions' => array( + * 'tag' => 'li', + * ), + * 'clientOptions' => array( + * 'tolerance' => 'fit', + * ), + * )); + * ``` + * + * @see http://api.jqueryui.com/selectable/ + * @author Alexander Kochetov + * @since 2.0 + */ +class Selectable extends Widget +{ + /** + * @var array the HTML attributes for the widget container tag. The following special options are recognized: + * + * - tag: string, defaults to "ul", the tag name of the container tag of this widget + */ + public $options = array(); + /** + * @var array list of selectable items. Each item can be a string representing the item content + * or an array of the following structure: + * + * ~~~ + * array( + * 'content' => 'item content', + * // the HTML attributes of the item container tag. This will overwrite "itemOptions". + * 'options' => array(), + * ) + * ~~~ + */ + public $items = array(); + /** + * @var array list of HTML attributes for the item container tags. This will be overwritten + * by the "options" set in individual [[items]]. The following special options are recognized: + * + * - tag: string, defaults to "li", the tag name of the item container tags. + */ + public $itemOptions = array(); + + + /** + * Renders the widget. + */ + public function run() + { + $options = $this->options; + $tag = ArrayHelper::remove($options, 'tag', 'ul'); + echo Html::beginTag($tag, $options) . "\n"; + echo $this->renderItems() . "\n"; + echo Html::endTag($tag) . "\n"; + $this->registerWidget('selectable'); + } + + /** + * Renders selectable items as specified on [[items]]. + * @return string the rendering result. + * @throws InvalidConfigException. + */ + public function renderItems() + { + $items = array(); + foreach ($this->items as $item) { + $options = $this->itemOptions; + $tag = ArrayHelper::remove($options, 'tag', 'li'); + if (is_array($item)) { + if (!isset($item['content'])) { + throw new InvalidConfigException("The 'content' option is required."); + } + $options = array_merge($options, ArrayHelper::getValue($item, 'options', array())); + $tag = ArrayHelper::remove($options, 'tag', $tag); + $items[] = Html::tag($tag, $item['content'], $options); + } else { + $items[] = Html::tag($tag, $item, $options); + } + } + return implode("\n", $items); + } +} From becc88e4b6602d4bc59daa98534466f48c54c9bb Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 30 May 2013 00:49:37 +0400 Subject: [PATCH 049/107] jQuery UI spinner widget --- framework/yii/jui/Spinner.php | 66 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 framework/yii/jui/Spinner.php diff --git a/framework/yii/jui/Spinner.php b/framework/yii/jui/Spinner.php new file mode 100644 index 0000000..8d80f89 --- /dev/null +++ b/framework/yii/jui/Spinner.php @@ -0,0 +1,66 @@ + $model, + * 'attribute' => 'country', + * 'clientOptions' => array( + * 'step' => 2, + * ), + * )); + * ``` + * + * The following example will use the name property instead: + * + * ```php + * echo Spinner::widget(array( + * 'name' => 'country', + * 'clientOptions' => array( + * 'step' => 2, + * ), + * )); + *``` + * + * @see http://api.jqueryui.com/spinner/ + * @author Alexander Kochetov + * @since 2.0 + */ +class Spinner extends InputWidget +{ + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderWidget(); + $this->registerWidget('spinner'); + } + + /** + * Renders the Spinner widget. + * @return string the rendering result. + */ + public function renderWidget() + { + if ($this->hasModel()) { + return Html::activeTextInput($this->model, $this->attribute, $this->options); + } else { + return Html::textInput($this->name, $this->value, $this->options); + } + } +} From a57597719c409465311ef5b14a72a565794db8be Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 23:50:06 +0200 Subject: [PATCH 050/107] buttons refactored (2nd round) --- framework/yii/bootstrap/Button.php | 166 +++++++--------------------- framework/yii/bootstrap/ButtonDropdown.php | 168 +++++++++++++++++++++++++++++ framework/yii/bootstrap/ButtonGroup.php | 127 ++++++++++++++++++++++ 3 files changed, 333 insertions(+), 128 deletions(-) create mode 100644 framework/yii/bootstrap/ButtonDropdown.php create mode 100644 framework/yii/bootstrap/ButtonGroup.php diff --git a/framework/yii/bootstrap/Button.php b/framework/yii/bootstrap/Button.php index b340a4c..be605b9 100644 --- a/framework/yii/bootstrap/Button.php +++ b/framework/yii/bootstrap/Button.php @@ -12,148 +12,58 @@ use yii\helpers\Html; /** - * Button renders a group or split button dropdown bootstrap component. + * Button renders a bootstrap button. * * For example, * * ```php - * // a button group using Dropdown widget - * echo \yii\bootstrap\Button::widget(array( + * echo Button::widget(array( * 'label' => 'Action', - * 'items' => Dropdown::widget(array( - * 'clientOptions' => false, - * 'items' => array( - * array( - * 'label' => 'DropdownA', - * 'url' => '/', - * ), - * array( - * 'label' => 'DropdownB', - * 'url' => '#', - * ), - * ), - * )), - * )); - * - * // split button group using `items` dropdown configuration - * echo \yii\bootstrap\Button::widget(array( - * 'label' => 'Action', - * 'split' => true, - * 'items' => array( - * array( - * 'label' => 'DropdownA', - * 'url' => '/', - * ), - * array( - * 'label' => 'DropdownB', - * 'url' => '#', - * ), - * ), + * 'options' => array('class' => 'btn-large'), * )); * ``` * @see http://twitter.github.io/bootstrap/javascript.html#buttons - * @see http://twitter.github.io/bootstrap/components.html#buttonDropdowns * @author Antonio Ramirez * @since 2.0 */ class Button extends Widget { - /** - * @var string the button label - */ - public $label; - /** - * @var array the HTML attributes of the button. - */ - public $buttonOptions = array(); - /** - * @var array list of menu items in the dropdown. Each array element represents a single - * menu with the following structure: - * - * - label: string, required, the label of the item link - * - url: string, optional, the url of the item link. Defaults to "#". - * - linkOptions: array, optional, the HTML attributes of the item link. - * - options: array, optional, the HTML attributes of the item. - * - items: array, optional, the dropdown items configuration array. - * - * @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 - * @see [[Dropdown]] - */ - public $items = array(); - /** - * @var boolean whether to display a group or split styled button group. - */ - public $split = false; - /** - * @var boolean whether the labels for dropdown items should be HTML-encoded. - */ - public $encodeLabels = true; - - - /** - * Initializes the widget. - * If you override this method, make sure you call the parent implementation first. - * @throws InvalidConfigException - */ - public function init() - { - if ($this->label === null) { - throw new InvalidConfigException("The 'label' option is required."); - } - parent::init(); - $this->addCssClass($this->options, 'btn-group'); - } + /** + * @var string the tag to use to render the button + */ + public $tagName = 'button'; + /** + * @var string the button label + */ + public $label; + /** + * @var boolean whether the label should be HTML-encoded. + */ + public $encodeLabel = true; - /** - * Renders the widget. - */ - public function run() - { - echo Html::beginTag('div', $this->options) . "\n"; - echo $this->renderLabel() . "\n"; - echo $this->renderItems() . "\n"; - echo Html::endTag('div') . "\n"; - $this->registerPlugin('button'); - } - /** - * Generates the button label. - * @return string the rendering result. - */ - protected function renderLabel() - { - $label = $this->encodeLabels ? Html::encode($this->label) : $this->label; - $this->addCssClass($this->buttonOptions, 'btn'); - $splitButton = ''; - if ($this->split) { - $tag = 'button'; - $options = $this->buttonOptions; - $this->buttonOptions['data-toggle'] = 'dropdown'; - $this->addCssClass($this->buttonOptions, 'dropdown-toggle'); - $splitButton = Html::tag('button', '', $this->buttonOptions); - } else { - $tag = 'a'; - $label .= ' '; - $options = $this->buttonOptions; - if (!isset($options['href'])) { - $options['href'] = '#'; - } - $this->addCssClass($options, 'dropdown-toggle'); - $options['data-toggle'] = 'dropdown'; - } - return Html::tag($tag, $label, $options) . "\n" . $splitButton; - } + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + * @throws InvalidConfigException + */ + public function init() + { + if ($this->label === null) { + throw new InvalidConfigException("The 'label' option is required."); + } + parent::init(); + $this->clientOptions = false; + $this->addCssClass($this->options, 'btn'); + $this->label = $this->encodeLabel ? Html::encode($this->label) : $this->label; + } - /** - * Generates the dropdown menu as specified on [[items]]. - * @return string the rendering result. - */ - protected function renderItems() - { - if (is_string($this->items)) { - return $this->items; - } - $config = array('items' => $this->items, 'clientOptions' => false); - return Dropdown::widget($config); - } + /** + * Renders the widget. + */ + public function run() + { + echo Html::tag($this->tagName, $this->label, $this->options) . "\n"; + $this->registerPlugin('button'); + } } \ No newline at end of file diff --git a/framework/yii/bootstrap/ButtonDropdown.php b/framework/yii/bootstrap/ButtonDropdown.php new file mode 100644 index 0000000..65d0398 --- /dev/null +++ b/framework/yii/bootstrap/ButtonDropdown.php @@ -0,0 +1,168 @@ + 'Action', + * 'items' => Dropdown::widget(array( + * 'clientOptions' => false, + * 'items' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '/', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#', + * ), + * ), + * )), + * )); + * + * // split button dropdown using `items` configuration + * echo ButtonDropdown::widget(array( + * 'label' => 'Action', + * 'split' => true, + * 'items' => array( + * array( + * 'label' => 'DropdownA', + * 'url' => '/', + * ), + * array( + * 'label' => 'DropdownB', + * 'url' => '#', + * ), + * ), + * )); + * ``` + * @see http://twitter.github.io/bootstrap/javascript.html#buttons + * @see http://twitter.github.io/bootstrap/components.html#buttonDropdowns + * @author Antonio Ramirez + * @since 2.0 + */ +class ButtonDropdown extends Widget +{ + /** + * @var string the button label + */ + public $label; + /** + * @var array the HTML attributes of the button. + */ + public $buttonOptions = array(); + /** + * @var array list of menu items in the dropdown. Each array element represents a single + * menu with the following structure: + * + * - label: string, required, the label of the item link + * - url: string, optional, the url of the item link. Defaults to "#". + * - linkOptions: array, optional, the HTML attributes of the item link. + * - options: array, optional, the HTML attributes of the item. + * - items: array, optional, the dropdown items configuration array. + * + * @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 + * @see [[Dropdown]] + */ + public $items = array(); + /** + * @var boolean whether to display a group or split styled button group. + */ + public $split = false; + /** + * @var boolean whether the labels for dropdown items should be HTML-encoded. + */ + public $encodeLabels = true; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + * @throws InvalidConfigException + */ + public function init() + { + if ($this->label === null) { + throw new InvalidConfigException("The 'label' option is required."); + } + parent::init(); + $this->addCssClass($this->options, 'btn-group'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', $this->options) . "\n"; + echo $this->renderButton() . "\n"; + echo $this->renderItems() . "\n"; + echo Html::endTag('div') . "\n"; + $this->registerPlugin('button'); + } + + /** + * Generates the button dropdown. + * @return string the rendering result. + */ + protected function renderButton() + { + $this->addCssClass($this->buttonOptions, 'btn'); + $splitButton = ''; + if ($this->split) { + $tag = 'button'; + $options = $this->buttonOptions; + $this->buttonOptions['data-toggle'] = 'dropdown'; + $this->addCssClass($this->buttonOptions, 'dropdown-toggle'); + $splitButton = Button::widget(array( + 'label' => '', + 'encodeLabel' => false, + 'options' => $this->buttonOptions, + ) + ); + } else { + $tag = 'a'; + $this->label .= ' '; + $options = $this->buttonOptions; + if (!isset($options['href'])) { + $options['href'] = '#'; + } + $this->addCssClass($options, 'dropdown-toggle'); + $options['data-toggle'] = 'dropdown'; + } + return Button::widget(array( + 'tagName' => $tag, + 'label' => $this->label, + 'options' => $options, + 'encodeLabel' => false, + )) . "\n" . $splitButton; + } + + /** + * Generates the dropdown menu as specified on [[items]]. + * @return string the rendering result. + */ + protected function renderItems() + { + if (is_string($this->items)) { + return $this->items; + } + $config = array('items' => $this->items, 'clientOptions' => false); + return Dropdown::widget($config); + } +} \ No newline at end of file diff --git a/framework/yii/bootstrap/ButtonGroup.php b/framework/yii/bootstrap/ButtonGroup.php new file mode 100644 index 0000000..9b98cc8 --- /dev/null +++ b/framework/yii/bootstrap/ButtonGroup.php @@ -0,0 +1,127 @@ + array( + * array('label'=>'A'), + * array('label'=>'B'), + * ) + * )); + * + * // button group with an item as a string + * echo ButtonGroup::::widget(array( + * 'items' => array( + * Button::widget(array('label'=>'A')), + * array('label'=>'B'), + * ) + * )); + * + * // button group with body content as string + * ButtonGroup::beging(); + * Button::widget(array('label'=>'A')), // you can also use plain string + * ButtonGroup::end(); + * ``` + * @see http://twitter.github.io/bootstrap/javascript.html#buttons + * @see http://twitter.github.io/bootstrap/components.html#buttonGroups + * @author Antonio Ramirez + * @since 2.0 + */ +class ButtonGroup extends Widget +{ + /** + * @var array list of buttons. Each array element represents a single + * menu with the following structure: + * + * - label: string, required, the button label. + * - options: array, optional, the HTML attributes of the button. + */ + public $items = array(); + /** + * @var boolean whether the labels for dropdown items should be HTML-encoded. + */ + public $encodeLabels = true; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + $this->clientOptions = false; + $this->addCssClass($this->options, 'btn-group'); + echo $this->renderGroupBegin() . "\n"; + } + + /** + * Renders the widget. + */ + public function run() + { + echo "\n" . $this->renderGroupEnd(); + $this->registerPlugin('button'); + } + + /** + * Renders the opening tag of the button group. + * @return string the rendering result. + */ + protected function renderGroupBegin() + { + return Html::beginTag('div', $this->options); + } + + /** + * Renders the items and closing tag of the button group. + * @return string the rendering result. + */ + protected function renderGroupEnd() + { + return $this->renderItems() . "\n" . Html::endTag('div'); + } + + /** + * Generates the buttons that compound the group as specified on [[items]]. + * @return string the rendering result. + */ + protected function renderItems() + { + if (is_string($this->items)) { + return $this->items; + } + $buttons = array(); + foreach ($this->items as $item) { + if (is_string($item)) { + $buttons[] = $item; + continue; + } + $label = ArrayHelper::getValue($item, 'label'); + $options = ArrayHelper::getValue($item, 'options'); + $buttons[] = Button::widget(array( + 'label' => $label, + 'options' => $options, + 'encodeLabel' => $this->encodeLabels + ) + ); + } + return implode("\n", $buttons); + } +} \ No newline at end of file From d5b878d35e54000ccb96d5375b273c8ecf30f5f9 Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Wed, 29 May 2013 23:53:58 +0200 Subject: [PATCH 051/107] docs minor change --- framework/yii/bootstrap/ButtonGroup.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/framework/yii/bootstrap/ButtonGroup.php b/framework/yii/bootstrap/ButtonGroup.php index 9b98cc8..fa903a1 100644 --- a/framework/yii/bootstrap/ButtonGroup.php +++ b/framework/yii/bootstrap/ButtonGroup.php @@ -51,6 +51,8 @@ class ButtonGroup extends Widget * * - label: string, required, the button label. * - options: array, optional, the HTML attributes of the button. + * + * Optionally, you can also set each item as a string, or even the [[items]] attribute. */ public $items = array(); /** From c1b74ba9459813ceab70f3dc80b702188e34112f Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 29 May 2013 18:01:24 -0400 Subject: [PATCH 052/107] refactored Nav and NavBar. --- framework/yii/bootstrap/Nav.php | 21 ++++++++------------- framework/yii/bootstrap/NavBar.php | 23 +++++++++-------------- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index 7a29ecd..548fe19 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -57,14 +57,8 @@ class Nav extends Widget * - linkOptions: array, optional, the HTML attributes of the item's link. * - options: array, optional, the HTML attributes of the item container (LI). * - active: boolean, optional, whether the item should be on active state or not. - * - items: array, optional, the configuration of specify the item's dropdown menu. You can optionally set this as - * a string (ie. `'items'=> Dropdown::widget(array(...))` - * - important: there is an issue with sub-dropdown menus, and as of 3.0, bootstrap won't support sub-dropdown. - * - * **Note:** Optionally, you can also use a plain string instead of an array element. - * - * @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 - * @see [[Dropdown]] + * - dropdown: array|string, optional, the configuration array for creating a [[Dropdown]] widget, + * or a string representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus. */ public $items = array(); /** @@ -120,7 +114,7 @@ class Nav extends Widget } $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label']; $options = ArrayHelper::getValue($item, 'options', array()); - $dropdown = ArrayHelper::getValue($item, 'items'); + $dropdown = ArrayHelper::getValue($item, 'dropdown'); $url = Html::url(ArrayHelper::getValue($item, 'url', '#')); $linkOptions = ArrayHelper::getValue($item, 'linkOptions', array()); @@ -133,11 +127,12 @@ class Nav extends Widget $this->addCssClass($options, 'dropdown'); $this->addCssClass($urlOptions, 'dropdown-toggle'); $label .= ' ' . Html::tag('b', '', array('class' => 'caret')); - $dropdown = is_string($dropdown) - ? $dropdown - : Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false)); + if (is_array($dropdown)) { + $dropdown['clientOptions'] = false; + $dropdown = Dropdown::widget($dropdown); + } } return Html::tag('li', Html::a($label, $url, $linkOptions) . $dropdown, $options); } -} \ No newline at end of file +} diff --git a/framework/yii/bootstrap/NavBar.php b/framework/yii/bootstrap/NavBar.php index cbcd2d3..17a938c 100644 --- a/framework/yii/bootstrap/NavBar.php +++ b/framework/yii/bootstrap/NavBar.php @@ -73,7 +73,7 @@ class NavBar extends Widget * @param array|string $url the URL for the brand's hyperlink tag. This parameter will be processed by [[Html::url()]] * and will be used for the "href" attribute of the brand link. Defaults to site root. */ - public $brandRoute = '/'; + public $brandUrl = '/'; /** * @var array the HTML attributes of the brand link. */ @@ -98,10 +98,6 @@ class NavBar extends Widget * Optionally, you can also use a plain string instead of an array element. */ public $items = array(); - /** - * @var string the generated brand url if specified by [[brandLabel]] - */ - protected $brand; /** @@ -113,7 +109,6 @@ class NavBar extends Widget $this->clientOptions = false; $this->addCssClass($this->options, 'navbar'); $this->addCssClass($this->brandOptions, 'brand'); - $this->brand = Html::a($this->brandLabel, $this->brandRoute, $this->brandOptions); } /** @@ -124,7 +119,7 @@ class NavBar extends Widget echo Html::beginTag('div', $this->options); echo $this->renderItems(); echo Html::endTag('div'); - $this->getView()->registerAssetBundle('yii/bootstrap'); + $this->getView()->registerAssetBundle(self::$responsive ? 'yii/bootstrap/responsive' : 'yii/bootstrap'); } /** @@ -138,17 +133,17 @@ class NavBar extends Widget $items[] = $this->renderItem($item); } $contents = implode("\n", $items); - if (self::$responsive === true) { + $brand = Html::a($this->brandLabel, $this->brandUrl, $this->brandOptions); + + if (self::$responsive) { $this->getView()->registerAssetBundle('yii/bootstrap/collapse'); - $contents = - Html::tag('div', + $contents = Html::tag('div', $this->renderToggleButton() . - $this->brand . "\n" . + $brand . "\n" . Html::tag('div', $contents, array('class' => 'nav-collapse collapse navbar-collapse')), array('class' => 'container')); - } else { - $contents = $this->brand . "\n" . $contents; + $contents = $brand . "\n" . $contents; } return Html::tag('div', $contents, array('class' => 'navbar-inner')); @@ -190,4 +185,4 @@ class NavBar extends Widget 'data-target' => 'div.navbar-collapse', )); } -} \ No newline at end of file +} From b459990e0064fb891372019cf03972f7e8ed091b Mon Sep 17 00:00:00 2001 From: Antonio Ramirez Date: Thu, 30 May 2013 00:35:34 +0200 Subject: [PATCH 053/107] added progress widget --- framework/yii/bootstrap/Progress.php | 147 +++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 framework/yii/bootstrap/Progress.php diff --git a/framework/yii/bootstrap/Progress.php b/framework/yii/bootstrap/Progress.php new file mode 100644 index 0000000..3bc346f --- /dev/null +++ b/framework/yii/bootstrap/Progress.php @@ -0,0 +1,147 @@ + 60, + * 'label' => 'test', + * )); + * + * // styled + * echo Progress::widget(array( + * 'percent' => 65, + * 'barOptions' => array('class' => 'bar-danger') + * )); + * + * // striped + * echo Progress::widget(array( + * 'percent' => 70, + * 'barOptions' => array('class' => 'bar-warning'), + * 'options' => array('class' => 'progress-striped') + * )); + * + * // striped animated + * echo Progress::widget(array( + * 'percent' => 70, + * 'barOptions' => array('class' => 'bar-success'), + * 'options' => array('class' => 'active progress-striped') + * )); + * + * // stacked and one with label + * echo Progress::widget(array( + * 'stacked' => array( + * array('percent' => 30, 'options' => array('class' => 'bar-danger')), + * array('label'=>'test', 'percent' => 30, 'options' => array('class' => 'bar-success')), + * array('percent' => 35, 'options' => array('class' => 'bar-warning')) + * ) + * )); + * ``` + * @see http://twitter.github.io/bootstrap/components.html#progress + * @author Antonio Ramirez + * @since 2.0 + */ +class Progress extends Widget +{ + /** + * @var string the button label + */ + public $label; + /** + * @var integer the amount of progress as a percentage. + */ + public $percent = 0; + /** + * @var array the HTML attributes of the + */ + public $barOptions = array(); + /** + * @var array $stacked set to an array of progress bar values to display stacked progress bars + * + * ```php + * 'stacked'=>array( + * array('percent'=>'30', 'options'=>array('class'=>'custom')), + * array('percent'=>'30'), + * ) + * ``` + */ + public $stacked = false; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + if ($this->label === null && $this->stacked == false) { + throw new InvalidConfigException("The 'percent' option is required."); + } + parent::init(); + $this->addCssClass($this->options, 'progress'); + $this->getView()->registerAssetBundle(static::$responsive ? 'yii/bootstrap/responsive' : 'yii/bootstrap'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', $this->options) . "\n"; + echo $this->renderProgress() . "\n"; + echo Html::endTag('div') . "\n"; + } + + /** + * Generates a bar + * @param int $percent the percentage of the bar + * @param string $label, optional, the label to display at the bar + * @param array $options the HTML attributes of the bar + * @return string the rendering result. + */ + public function bar($percent, $label = '', $options = array()) + { + $this->addCssClass($options, 'bar'); + $options['style'] = "width:{$percent}%"; + return Html::tag('div', $label, $options); + } + + /** + * @return string the rendering result. + * @throws InvalidConfigException + */ + protected function renderProgress() + { + if ($this->stacked === false) { + return $this->bar($this->percent, $this->label, $this->barOptions); + } + $bars = array(); + foreach ($this->stacked as $item) { + $label = ArrayHelper::getValue($item, 'label', ''); + if (!isset($item['percent'])) { + throw new InvalidConfigException("The 'percent' option is required."); + } + $options = ArrayHelper::getValue($item, 'options', array()); + + $bars[] = $this->bar($item['percent'], $label, $options); + } + return implode("\n", $bars); + } + +} \ No newline at end of file From c77d3193a5ec007a11ebe124ca747ce1f69c628a Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Thu, 30 May 2013 03:30:14 +0400 Subject: [PATCH 054/107] Comments --- framework/yii/jui/Tabs.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/framework/yii/jui/Tabs.php b/framework/yii/jui/Tabs.php index d25eaa2..13bd4eb 100644 --- a/framework/yii/jui/Tabs.php +++ b/framework/yii/jui/Tabs.php @@ -78,10 +78,11 @@ class Tabs extends Widget * - label: string, required, specifies the header link label. When [[encodeLabels]] is true, the label * will be HTML-encoded. * - content: string, the content to show when corresponding tab is clicked. Can be omitted if url is specified. - * - url: mixed, @todo comment - * - template: string, optional, @todo comment - * - options: array, optional, @todo comment - * - headerOptions: array, optional, @todo comment + * - url: mixed, mixed, optional, the url to load tab contents via AJAX. It is required if no content is specified. + * - template: string, optional, the header link template to render the header link. If none specified + * [[linkTemplate]] will be used instead. + * - options: array, optional, the HTML attributes of the header. + * - headerOptions: array, optional, the HTML attributes for the header container tag. */ public $items = array(); /** @@ -97,7 +98,7 @@ class Tabs extends Widget */ public $headerOptions = array(); /** - * @var string @todo comment + * @var string the default header template to render the link. */ public $linkTemplate = '{label}'; /** From 9339edbd45c15edeb5341962b8628668a6892891 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 29 May 2013 20:07:02 -0400 Subject: [PATCH 055/107] Refactored Progress. --- framework/yii/bootstrap/Progress.php | 78 ++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/framework/yii/bootstrap/Progress.php b/framework/yii/bootstrap/Progress.php index 3bc346f..708c0fe 100644 --- a/framework/yii/bootstrap/Progress.php +++ b/framework/yii/bootstrap/Progress.php @@ -8,7 +8,7 @@ namespace yii\bootstrap; use yii\base\InvalidConfigException; -use yii\helpers\base\ArrayHelper; +use yii\helpers\ArrayHelper; use yii\helpers\Html; @@ -44,11 +44,11 @@ use yii\helpers\Html; * 'options' => array('class' => 'active progress-striped') * )); * - * // stacked and one with label + * // stacked bars * echo Progress::widget(array( - * 'stacked' => array( + * 'bars' => array( * array('percent' => 30, 'options' => array('class' => 'bar-danger')), - * array('label'=>'test', 'percent' => 30, 'options' => array('class' => 'bar-success')), + * array('percent' => 30, 'label'=>'test', 'options' => array('class' => 'bar-success')), * array('percent' => 35, 'options' => array('class' => 'bar-warning')) * ) * )); @@ -72,16 +72,20 @@ class Progress extends Widget */ public $barOptions = array(); /** - * @var array $stacked set to an array of progress bar values to display stacked progress bars + * @var array a set of bars that are stacked together to form a single progress bar. + * Each bar is an array of the following structure: * * ```php - * 'stacked'=>array( - * array('percent'=>'30', 'options'=>array('class'=>'custom')), - * array('percent'=>'30'), - * ) - * ``` + * array( + * // required, the amount of progress as a percentage. + * 'percent' => 30, + * // optional, the label to be displayed on the bar + * 'label' => '30%', + * // optional, array, additional HTML attributes for the bar tag + * 'options' => array(), + * ) */ - public $stacked = false; + public $bars; /** @@ -90,12 +94,8 @@ class Progress extends Widget */ public function init() { - if ($this->label === null && $this->stacked == false) { - throw new InvalidConfigException("The 'percent' option is required."); - } parent::init(); $this->addCssClass($this->options, 'progress'); - $this->getView()->registerAssetBundle(static::$responsive ? 'yii/bootstrap/responsive' : 'yii/bootstrap'); } /** @@ -106,42 +106,42 @@ class Progress extends Widget echo Html::beginTag('div', $this->options) . "\n"; echo $this->renderProgress() . "\n"; echo Html::endTag('div') . "\n"; + $this->getView()->registerAssetBundle(static::$responsive ? 'yii/bootstrap/responsive' : 'yii/bootstrap'); } /** - * Generates a bar - * @param int $percent the percentage of the bar - * @param string $label, optional, the label to display at the bar - * @param array $options the HTML attributes of the bar - * @return string the rendering result. - */ - public function bar($percent, $label = '', $options = array()) - { - $this->addCssClass($options, 'bar'); - $options['style'] = "width:{$percent}%"; - return Html::tag('div', $label, $options); - } - - /** + * Renders the progress. * @return string the rendering result. - * @throws InvalidConfigException + * @throws InvalidConfigException if the "percent" option is not set in a stacked progress bar. */ protected function renderProgress() { - if ($this->stacked === false) { - return $this->bar($this->percent, $this->label, $this->barOptions); + if (empty($this->bars)) { + return $this->renderBar($this->percent, $this->label, $this->barOptions); } $bars = array(); - foreach ($this->stacked as $item) { - $label = ArrayHelper::getValue($item, 'label', ''); - if (!isset($item['percent'])) { + foreach ($this->bars as $bar) { + $label = ArrayHelper::getValue($bar, 'label', ''); + if (!isset($bar['percent'])) { throw new InvalidConfigException("The 'percent' option is required."); } - $options = ArrayHelper::getValue($item, 'options', array()); - - $bars[] = $this->bar($item['percent'], $label, $options); + $options = ArrayHelper::getValue($bar, 'options', array()); + $bars[] = $this->renderBar($bar['percent'], $label, $options); } return implode("\n", $bars); } -} \ No newline at end of file + /** + * Generates a bar + * @param int $percent the percentage of the bar + * @param string $label, optional, the label to display at the bar + * @param array $options the HTML attributes of the bar + * @return string the rendering result. + */ + protected function renderBar($percent, $label = '', $options = array()) + { + $this->addCssClass($options, 'bar'); + $options['style'] = "width:{$percent}%"; + return Html::tag('div', $label, $options); + } +} From f05f85fcfe9fd46f37f79d014f2544bc96bb16da Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 29 May 2013 20:12:24 -0400 Subject: [PATCH 056/107] Removed Connection::__sleep. --- framework/yii/db/Connection.php | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index e14eeb7..f8883bb 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -251,16 +251,7 @@ class Connection extends Component */ private $_schema; - /** - * Closes the connection when this component is being serialized. - * @return array - */ - public function __sleep() - { - $this->close(); - return array_keys(get_object_vars($this)); - } - + /** * Returns a value indicating whether the DB connection is established. * @return boolean whether the DB connection is established From 66e698673d1fb95222d160f3948d23b8d6372940 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Wed, 29 May 2013 21:53:25 -0400 Subject: [PATCH 057/107] Fixes issue #457: incorrect HTTPS detection. --- framework/yii/db/Connection.php | 2 +- framework/yii/web/Request.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php index f8883bb..3a4d0ad 100644 --- a/framework/yii/db/Connection.php +++ b/framework/yii/db/Connection.php @@ -251,7 +251,7 @@ class Connection extends Component */ private $_schema; - + /** * Returns a value indicating whether the DB connection is established. * @return boolean whether the DB connection is established diff --git a/framework/yii/web/Request.php b/framework/yii/web/Request.php index 5cd6912..a857926 100644 --- a/framework/yii/web/Request.php +++ b/framework/yii/web/Request.php @@ -533,8 +533,8 @@ class Request extends \yii\base\Request */ public function getIsSecureConnection() { - return isset($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') - || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO']==='https'; + return isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] === 'on' || $_SERVER['HTTPS'] == 1) + || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'; } /** From 09e4229e16cfa174ee83c9217d1d2dbdd19e9b93 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Thu, 30 May 2013 13:33:38 +0200 Subject: [PATCH 058/107] [WIP] RESTful routing syntax for UrlManager issue #303 Here is the proposal for RESTful routing syntax: https://gist.github.com/cebe/5674918 --- framework/yii/web/UrlManager.php | 32 ++++++++++++++++++++++++++++- tests/unit/framework/web/UrlManagerTest.php | 2 ++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/framework/yii/web/UrlManager.php b/framework/yii/web/UrlManager.php index 47f5c5d..a6ef0cf 100644 --- a/framework/yii/web/UrlManager.php +++ b/framework/yii/web/UrlManager.php @@ -43,6 +43,28 @@ class UrlManager extends Component * array, one can use the key to represent the pattern and the value the corresponding route. * For example, `'post/' => 'post/view'`. * + * For RESTful routing this shortcut also allows you to specify the [[UrlRule::verb|HTTP verb]] + * the rule should apply for by prepending it to the pattern, separated by a blank. + * For example, `'PUT post/' => 'post/update'`. + * You may specify multiple verbs by separating them with comma + * like this: `'POST,PUT post/index' => 'post/create'`. + * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way + * so you normally would not specify a verb for normal GET request. + * + * Here is an example configuration for RESTful CRUD controller: + * ~~~php + * array( + * 'dashboard' => 'site/index', + * + * 'POST s' => '/create', + * 's' => '/index', + * + * 'PUT /' => '/update', + * 'DELETE /' => '/delete', + * '/' => '/view', + * ); + * ~~~ + * * Note that if you modify this property after the UrlManager object is created, make sure * you populate the array with rule objects instead of rule configurations. */ @@ -115,9 +137,17 @@ class UrlManager extends Component foreach ($this->rules as $key => $rule) { if (!is_array($rule)) { $rule = array( - 'pattern' => $key, 'route' => $rule, ); + if (($pos = strpos($key, ' ')) !== false) { + $verbs = substr($key, 0, $pos); + if (preg_match('/^((GET|POST|PUT|DELETE),?)*$/', $verbs, $matches)) { + $rule['verb'] = explode(',', $verbs); + $rule['mode'] = UrlRule::PARSING_ONLY; + $key = substr($key, $pos + 1); + } + } + $rule['pattern'] = $key; } $rules[] = Yii::createObject(array_merge($this->ruleConfig, $rule)); } diff --git a/tests/unit/framework/web/UrlManagerTest.php b/tests/unit/framework/web/UrlManagerTest.php index 0f08790..d0e8775 100644 --- a/tests/unit/framework/web/UrlManagerTest.php +++ b/tests/unit/framework/web/UrlManagerTest.php @@ -248,4 +248,6 @@ class UrlManagerTest extends TestCase $result = $manager->parseRequest($request); $this->assertFalse($result); } + + // TODO test RESTful pattern syntax e.g. 'GET index' => 'site/index' } From 94b2d170314f79f15fdd6d4dd41413caace7f02f Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 07:35:03 -0400 Subject: [PATCH 059/107] Fixes issue #460 --- framework/yii/db/mysql/QueryBuilder.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php index 7bf293b..70c6d64 100644 --- a/framework/yii/db/mysql/QueryBuilder.php +++ b/framework/yii/db/mysql/QueryBuilder.php @@ -150,17 +150,20 @@ class QueryBuilder extends \yii\db\QueryBuilder */ public function batchInsert($table, $columns, $rows) { + foreach ($columns as $i => $name) { + $columns[$i] = $this->db->quoteColumnName($name); + } + $values = array(); foreach ($rows as $row) { $vs = array(); foreach ($row as $value) { $vs[] = is_string($value) ? $this->db->quoteValue($value) : $value; } - $values[] = $vs; + $values[] = '(' . implode(', ', $vs) . ')'; } return 'INSERT INTO ' . $this->db->quoteTableName($table) - . ' (' . implode(', ', $columns) . ') VALUES (' - . implode(', ', $values) . ')'; + . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values); } } From e1cde74b7b0cfcffd3613e4caf1a35f1ae430413 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 07:57:06 -0400 Subject: [PATCH 060/107] Fixed doc. --- framework/yii/base/View.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/base/View.php b/framework/yii/base/View.php index 9d6b921..7f965fa 100644 --- a/framework/yii/base/View.php +++ b/framework/yii/base/View.php @@ -130,8 +130,8 @@ class View extends Component */ public $dynamicPlaceholders = array(); /** - * @var array the registered asset bundles. The keys are the bundle names, and the values - * are the corresponding [[AssetBundle]] objects. + * @var array list of the registered asset bundles. The keys are the bundle names, and the values + * are booleans indicating whether the bundles have been registered. * @see registerAssetBundle */ public $assetBundles; From a93f75068e1062ef22896a01329339acd772f9f6 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 10:01:11 -0400 Subject: [PATCH 061/107] refactoring button classes. --- framework/yii/bootstrap/Button.php | 68 +++++++++++++---------------- framework/yii/bootstrap/ButtonDropdown.php | 33 +++++--------- framework/yii/bootstrap/ButtonGroup.php | 70 +++++++++--------------------- 3 files changed, 63 insertions(+), 108 deletions(-) diff --git a/framework/yii/bootstrap/Button.php b/framework/yii/bootstrap/Button.php index be605b9..104e700 100644 --- a/framework/yii/bootstrap/Button.php +++ b/framework/yii/bootstrap/Button.php @@ -6,7 +6,6 @@ */ namespace yii\bootstrap; - use yii\base\InvalidConfigException; use yii\helpers\Html; @@ -28,42 +27,37 @@ use yii\helpers\Html; */ class Button extends Widget { - /** - * @var string the tag to use to render the button - */ - public $tagName = 'button'; - /** - * @var string the button label - */ - public $label; - /** - * @var boolean whether the label should be HTML-encoded. - */ - public $encodeLabel = true; + /** + * @var string the tag to use to render the button + */ + public $tagName = 'button'; + /** + * @var string the button label + */ + public $label = 'Button'; + /** + * @var boolean whether the label should be HTML-encoded. + */ + public $encodeLabel = true; - /** - * Initializes the widget. - * If you override this method, make sure you call the parent implementation first. - * @throws InvalidConfigException - */ - public function init() - { - if ($this->label === null) { - throw new InvalidConfigException("The 'label' option is required."); - } - parent::init(); - $this->clientOptions = false; - $this->addCssClass($this->options, 'btn'); - $this->label = $this->encodeLabel ? Html::encode($this->label) : $this->label; - } + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + $this->clientOptions = false; + $this->addCssClass($this->options, 'btn'); + } - /** - * Renders the widget. - */ - public function run() - { - echo Html::tag($this->tagName, $this->label, $this->options) . "\n"; - $this->registerPlugin('button'); - } -} \ No newline at end of file + /** + * Renders the widget. + */ + public function run() + { + echo Html::tag($this->tagName, $this->encodeLabel ? Html::encode($this->label) : $this->label, $this->options); + $this->registerPlugin('button'); + } +} diff --git a/framework/yii/bootstrap/ButtonDropdown.php b/framework/yii/bootstrap/ButtonDropdown.php index 65d0398..6b9c51d 100644 --- a/framework/yii/bootstrap/ButtonDropdown.php +++ b/framework/yii/bootstrap/ButtonDropdown.php @@ -6,8 +6,6 @@ */ namespace yii\bootstrap; - -use yii\base\InvalidConfigException; use yii\helpers\Html; @@ -61,13 +59,14 @@ class ButtonDropdown extends Widget /** * @var string the button label */ - public $label; + public $label = 'Button'; /** * @var array the HTML attributes of the button. */ public $buttonOptions = array(); /** - * @var array list of menu items in the dropdown. Each array element represents a single + * @var array list of menu items in the dropdown. This will be used to + * set the [[Dropdown::items]] property. Each array element represents a single * menu with the following structure: * * - label: string, required, the label of the item link @@ -81,7 +80,7 @@ class ButtonDropdown extends Widget */ public $items = array(); /** - * @var boolean whether to display a group or split styled button group. + * @var boolean whether to display a group of split-styled button group. */ public $split = false; /** @@ -93,13 +92,9 @@ class ButtonDropdown extends Widget /** * Initializes the widget. * If you override this method, make sure you call the parent implementation first. - * @throws InvalidConfigException */ public function init() { - if ($this->label === null) { - throw new InvalidConfigException("The 'label' option is required."); - } parent::init(); $this->addCssClass($this->options, 'btn-group'); } @@ -111,7 +106,7 @@ class ButtonDropdown extends Widget { echo Html::beginTag('div', $this->options) . "\n"; echo $this->renderButton() . "\n"; - echo $this->renderItems() . "\n"; + echo $this->renderDropdown() . "\n"; echo Html::endTag('div') . "\n"; $this->registerPlugin('button'); } @@ -123,18 +118,16 @@ class ButtonDropdown extends Widget protected function renderButton() { $this->addCssClass($this->buttonOptions, 'btn'); - $splitButton = ''; if ($this->split) { $tag = 'button'; $options = $this->buttonOptions; $this->buttonOptions['data-toggle'] = 'dropdown'; $this->addCssClass($this->buttonOptions, 'dropdown-toggle'); $splitButton = Button::widget(array( - 'label' => '', - 'encodeLabel' => false, - 'options' => $this->buttonOptions, - ) - ); + 'label' => '', + 'encodeLabel' => false, + 'options' => $this->buttonOptions, + )); } else { $tag = 'a'; $this->label .= ' '; @@ -144,6 +137,7 @@ class ButtonDropdown extends Widget } $this->addCssClass($options, 'dropdown-toggle'); $options['data-toggle'] = 'dropdown'; + $splitButton = ''; } return Button::widget(array( 'tagName' => $tag, @@ -157,12 +151,9 @@ class ButtonDropdown extends Widget * Generates the dropdown menu as specified on [[items]]. * @return string the rendering result. */ - protected function renderItems() + protected function renderDropdown() { - if (is_string($this->items)) { - return $this->items; - } $config = array('items' => $this->items, 'clientOptions' => false); return Dropdown::widget($config); } -} \ No newline at end of file +} diff --git a/framework/yii/bootstrap/ButtonGroup.php b/framework/yii/bootstrap/ButtonGroup.php index fa903a1..f50d6a8 100644 --- a/framework/yii/bootstrap/ButtonGroup.php +++ b/framework/yii/bootstrap/ButtonGroup.php @@ -20,23 +20,18 @@ use yii\helpers\Html; * // a button group with items configuration * echo ButtonGroup::::widget(array( * 'items' => array( - * array('label'=>'A'), - * array('label'=>'B'), + * array('label' => 'A'), + * array('label' => 'B'), * ) * )); * * // button group with an item as a string * echo ButtonGroup::::widget(array( * 'items' => array( - * Button::widget(array('label'=>'A')), - * array('label'=>'B'), + * Button::widget(array('label' => 'A')), + * array('label' => 'B'), * ) * )); - * - * // button group with body content as string - * ButtonGroup::beging(); - * Button::widget(array('label'=>'A')), // you can also use plain string - * ButtonGroup::end(); * ``` * @see http://twitter.github.io/bootstrap/javascript.html#buttons * @see http://twitter.github.io/bootstrap/components.html#buttonGroups @@ -46,17 +41,15 @@ use yii\helpers\Html; class ButtonGroup extends Widget { /** - * @var array list of buttons. Each array element represents a single - * menu with the following structure: + * @var array list of buttons. Each array element represents a single button + * which can be specified as a string or an array of the following structure: * * - label: string, required, the button label. * - options: array, optional, the HTML attributes of the button. - * - * Optionally, you can also set each item as a string, or even the [[items]] attribute. */ - public $items = array(); + public $buttons = array(); /** - * @var boolean whether the labels for dropdown items should be HTML-encoded. + * @var boolean whether to HTML-encode the button labels. */ public $encodeLabels = true; @@ -70,7 +63,6 @@ class ButtonGroup extends Widget parent::init(); $this->clientOptions = false; $this->addCssClass($this->options, 'btn-group'); - echo $this->renderGroupBegin() . "\n"; } /** @@ -78,52 +70,30 @@ class ButtonGroup extends Widget */ public function run() { - echo "\n" . $this->renderGroupEnd(); + echo Html::tag('div', $this->renderButtons(), $this->options); $this->registerPlugin('button'); } /** - * Renders the opening tag of the button group. - * @return string the rendering result. - */ - protected function renderGroupBegin() - { - return Html::beginTag('div', $this->options); - } - - /** - * Renders the items and closing tag of the button group. - * @return string the rendering result. - */ - protected function renderGroupEnd() - { - return $this->renderItems() . "\n" . Html::endTag('div'); - } - - /** * Generates the buttons that compound the group as specified on [[items]]. * @return string the rendering result. */ - protected function renderItems() + protected function renderButtons() { - if (is_string($this->items)) { - return $this->items; - } $buttons = array(); - foreach ($this->items as $item) { - if (is_string($item)) { - $buttons[] = $item; - continue; - } - $label = ArrayHelper::getValue($item, 'label'); - $options = ArrayHelper::getValue($item, 'options'); - $buttons[] = Button::widget(array( + foreach ($this->buttons as $button) { + if (is_array($button)) { + $label = ArrayHelper::getValue($button, 'label'); + $options = ArrayHelper::getValue($button, 'options'); + $buttons[] = Button::widget(array( 'label' => $label, 'options' => $options, 'encodeLabel' => $this->encodeLabels - ) - ); + )); + } else { + $buttons[] = $button; + } } return implode("\n", $buttons); } -} \ No newline at end of file +} From 79aaf1fbec5e422062993a6078111997e0c0561f Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 10:13:39 -0400 Subject: [PATCH 062/107] refactored ButtonDropdown and Dropdown. --- framework/yii/bootstrap/ButtonDropdown.php | 45 +++++------------------------- framework/yii/bootstrap/Dropdown.php | 36 +++++++++--------------- 2 files changed, 20 insertions(+), 61 deletions(-) diff --git a/framework/yii/bootstrap/ButtonDropdown.php b/framework/yii/bootstrap/ButtonDropdown.php index 6b9c51d..4168bd5 100644 --- a/framework/yii/bootstrap/ButtonDropdown.php +++ b/framework/yii/bootstrap/ButtonDropdown.php @@ -18,8 +18,7 @@ use yii\helpers\Html; * // a button group using Dropdown widget * echo ButtonDropdown::widget(array( * 'label' => 'Action', - * 'items' => Dropdown::widget(array( - * 'clientOptions' => false, + * 'dropdown' => array( * 'items' => array( * array( * 'label' => 'DropdownA', @@ -30,23 +29,7 @@ use yii\helpers\Html; * 'url' => '#', * ), * ), - * )), - * )); - * - * // split button dropdown using `items` configuration - * echo ButtonDropdown::widget(array( - * 'label' => 'Action', - * 'split' => true, - * 'items' => array( - * array( - * 'label' => 'DropdownA', - * 'url' => '/', - * ), - * array( - * 'label' => 'DropdownB', - * 'url' => '#', - * ), - * ), + * ), * )); * ``` * @see http://twitter.github.io/bootstrap/javascript.html#buttons @@ -65,28 +48,13 @@ class ButtonDropdown extends Widget */ public $buttonOptions = array(); /** - * @var array list of menu items in the dropdown. This will be used to - * set the [[Dropdown::items]] property. Each array element represents a single - * menu with the following structure: - * - * - label: string, required, the label of the item link - * - url: string, optional, the url of the item link. Defaults to "#". - * - linkOptions: array, optional, the HTML attributes of the item link. - * - options: array, optional, the HTML attributes of the item. - * - items: array, optional, the dropdown items configuration array. - * - * @see https://github.com/twitter/bootstrap/issues/5050#issuecomment-11741727 - * @see [[Dropdown]] + * @var array the configuration array for [[Dropdown]]. */ - public $items = array(); + public $dropdown = array(); /** * @var boolean whether to display a group of split-styled button group. */ public $split = false; - /** - * @var boolean whether the labels for dropdown items should be HTML-encoded. - */ - public $encodeLabels = true; /** @@ -148,12 +116,13 @@ class ButtonDropdown extends Widget } /** - * Generates the dropdown menu as specified on [[items]]. + * Generates the dropdown menu. * @return string the rendering result. */ protected function renderDropdown() { - $config = array('items' => $this->items, 'clientOptions' => false); + $config = $this->dropdown; + $config['clientOptions'] = false; return Dropdown::widget($config); } } diff --git a/framework/yii/bootstrap/Dropdown.php b/framework/yii/bootstrap/Dropdown.php index 0594b08..2bee0ff 100644 --- a/framework/yii/bootstrap/Dropdown.php +++ b/framework/yii/bootstrap/Dropdown.php @@ -13,7 +13,7 @@ use yii\helpers\Html; /** - * Dropdown renders a Tab bootstrap javascript component. + * Dropdown renders a Bootstrap dropdown menu component. * * @see http://twitter.github.io/bootstrap/javascript.html#dropdowns * @author Antonio Ramirez @@ -55,21 +55,22 @@ class Dropdown extends Widget */ public function run() { - echo $this->renderItems() . "\n"; + echo $this->renderItems($this->items); $this->registerPlugin('dropdown'); } /** - * Renders dropdown items as specified on [[items]]. + * Renders menu items. + * @param array $items the menu items to be rendered * @return string the rendering result. - * @throws InvalidConfigException + * @throws InvalidConfigException if the label option is not specified in one of the items. */ - protected function renderItems() + protected function renderItems($items) { - $items = array(); - foreach ($this->items as $item) { + $lines = array(); + foreach ($items as $item) { if (is_string($item)) { - $items[] = $item; + $lines[] = $item; continue; } if (!isset($item['label'])) { @@ -82,24 +83,13 @@ class Dropdown extends Widget if (isset($item['items'])) { $this->addCssClass($options, 'dropdown-submenu'); - $content = Html::a($label, '#', $linkOptions) . $this->dropdown($item['items']); + $content = Html::a($label, '#', $linkOptions) . $this->renderItems($item['items']); } else { $content = Html::a($label, ArrayHelper::getValue($item, 'url', '#'), $linkOptions); } - $items[] = Html::tag('li', $content , $options); + $lines[] = Html::tag('li', $content, $options); } - return Html::tag('ul', implode("\n", $items), $this->options); + return Html::tag('ul', implode("\n", $lines), $this->options); } - - /** - * Generates a dropdown menu. - * @param array $items the configuration of the dropdown items. See [[items]]. - * @return string the generated dropdown menu - * @see items - */ - protected function dropdown($items) - { - return static::widget(array('items' => $items, 'clientOptions' => false)); - } -} \ No newline at end of file +} From 60c351ea9348b3e4f88ae5628c3af5e700adf19c Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 10:35:29 -0400 Subject: [PATCH 063/107] refactored Tabs. --- framework/yii/bootstrap/Tabs.php | 72 ++++++++++++---------------------------- 1 file changed, 21 insertions(+), 51 deletions(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 1af79b4..5aeac92 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -26,8 +26,8 @@ use yii\helpers\Html; * ), * array( * 'label' => 'Two', - * 'headerOptions' => array(...), * 'content' => 'Anim pariatur cliche...', + * 'headerOptions' => array(...), * 'options' => array('id'=>'myveryownID'), * ), * array( @@ -61,12 +61,12 @@ class Tabs extends Widget * - headerOptions: array, optional, the HTML attributes of the tab header. * - content: array, required if `items` is not set. The content (HTML) of the tab pane. * - options: array, optional, the HTML attributes of the tab pane container. - * - active: boolean, optional, whether the item tab header and pane should be visibles or not. + * - active: boolean, optional, whether the item tab header and pane should be visible or not. * - items: array, optional, if not set then `content` will be required. The `items` specify a dropdown items * configuration array. Items can also hold two extra keys: - * - active: boolean, optional, whether the item tab header and pane should be visibles or not. - * - content: string, required if `items` is not set. The content (HTML) of the tab pane. - * - contentOptions: optional, array, the HTML attributes of the tab content container. + * * active: boolean, optional, whether the item tab header and pane should be visible or not. + * * content: string, required if `items` is not set. The content (HTML) of the tab pane. + * * contentOptions: optional, array, the HTML attributes of the tab content container. */ public $items = array(); /** @@ -94,7 +94,6 @@ class Tabs extends Widget { parent::init(); $this->addCssClass($this->options, 'nav nav-tabs'); - } /** @@ -119,25 +118,21 @@ class Tabs extends Widget if (!isset($item['label'])) { throw new InvalidConfigException("The 'label' option is required."); } - if (!isset($item['content']) && !isset($item['items'])) { - throw new InvalidConfigException("The 'content' option is required."); - } - $label = $this->label($item['label']); - $headerOptions = $this->mergedOptions($item, 'headerOptions'); + $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label']; + $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', array())); if (isset($item['items'])) { $label .= ' '; $this->addCssClass($headerOptions, 'dropdown'); - if ($this->normalizeItems($item['items'], $panes)) { + if ($this->renderDropdown($item['items'], $panes)) { $this->addCssClass($headerOptions, 'active'); } - $header = Html::a($label, "#", array('class' => 'dropdown-toggle', 'data-toggle' => 'dropdown')) . "\n"; - $header .= Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false)); - - } else { - $options = $this->mergedOptions($item, 'itemOptions', 'options'); + $header = Html::a($label, "#", array('class' => 'dropdown-toggle', 'data-toggle' => 'dropdown')) . "\n" + . Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false)); + } elseif (isset($item['content'])) { + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options')); $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n); $this->addCssClass($options, 'tab-pane'); @@ -147,40 +142,15 @@ class Tabs extends Widget } $header = Html::a($label, '#' . $options['id'], array('data-toggle' => 'tab', 'tabindex' => '-1')); $panes[] = Html::tag('div', $item['content'], $options); - + } else { + throw new InvalidConfigException("Either the 'content' or 'items' option must be set."); } - $headers[] = Html::tag('li', $header, array_merge($this->headerOptions, $headerOptions)); - } - return Html::tag('ul', implode("\n", $headers), $this->options) . "\n" . - Html::tag('div', implode("\n", $panes), array('class' => 'tab-content')); - } - - /** - * Returns encoded if specified on [[encodeLabels]], original string otherwise. - * @param string $content the label text to encode or return - * @return string the resulting label. - */ - protected function label($content) - { - return $this->encodeLabels ? Html::encode($content) : $content; - } - - /** - * Returns array of options merged with specified attribute array. The availabel options are: - * - [[itemOptions]] - * - [[headerOptions]] - * @param array $item the item to merge the options with - * @param string $name the property name. - * @param string $key the key to extract. If null, it is assumed to be the same as `$name`. - * @return array the merged array options. - */ - protected function mergedOptions($item, $name, $key = null) - { - if ($key === null) { - $key = $name; + $headers[] = Html::tag('li', $header, $headerOptions); } - return array_merge($this->{$name}, ArrayHelper::getValue($item, $key, array())); + + return Html::tag('ul', implode("\n", $headers), $this->options) . "\n" + . Html::tag('div', implode("\n", $panes), array('class' => 'tab-content')); } /** @@ -191,7 +161,7 @@ class Tabs extends Widget * @return boolean whether any of the dropdown items is `active` or not. * @throws InvalidConfigException */ - protected function normalizeItems(&$items, &$panes) + protected function renderDropdown(&$items, &$panes) { $itemActive = false; @@ -199,7 +169,7 @@ class Tabs extends Widget if (is_string($item)) { continue; } - if (!isset($item['content']) && !isset($item['items'])) { + if (!isset($item['content'])) { throw new InvalidConfigException("The 'content' option is required."); } @@ -222,4 +192,4 @@ class Tabs extends Widget } return $itemActive; } -} \ No newline at end of file +} From c8df8e2abac4c6e74d3f0232e3b0e3b329ad528a Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 10:37:33 -0400 Subject: [PATCH 064/107] doc fix. --- framework/yii/bootstrap/Tabs.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index 5aeac92..a18eef6 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -32,7 +32,7 @@ use yii\helpers\Html; * ), * array( * 'label' => 'Dropdown', - * 'dropdown' => array( + * 'items' => array( * array( * 'label' => 'DropdownA', * 'content' => 'DropdownA, Anim pariatur cliche...', @@ -63,7 +63,7 @@ class Tabs extends Widget * - options: array, optional, the HTML attributes of the tab pane container. * - active: boolean, optional, whether the item tab header and pane should be visible or not. * - items: array, optional, if not set then `content` will be required. The `items` specify a dropdown items - * configuration array. Items can also hold two extra keys: + * configuration array. Each item can hold two extra keys, besides the above ones: * * active: boolean, optional, whether the item tab header and pane should be visible or not. * * content: string, required if `items` is not set. The content (HTML) of the tab pane. * * contentOptions: optional, array, the HTML attributes of the tab content container. From 5e9c8cb7c4a190f9f696225bd476617b6a18f0aa Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 14:57:28 -0400 Subject: [PATCH 065/107] Fixes issue #446: automatically scroll to first error. --- framework/yii/assets/yii.activeForm.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/framework/yii/assets/yii.activeForm.js b/framework/yii/assets/yii.activeForm.js index 483df96..fb4c16a 100644 --- a/framework/yii/assets/yii.activeForm.js +++ b/framework/yii/assets/yii.activeForm.js @@ -135,12 +135,16 @@ data.submitting = true; if (!data.settings.beforeSubmit || data.settings.beforeSubmit($form)) { validate($form, function (messages) { - var hasError = false; + var errors = []; $.each(data.attributes, function () { - hasError = updateInput($form, this, messages) || hasError; + if (updateInput($form, this, messages)) { + errors.push(this.input); + } }); updateSummary($form, messages); - if (!hasError) { + if (errors.length) { + $(window).scrollTop($form.find(errors.join(',')).first().offset().top); + } else { data.validated = true; var $button = data.submitObject || $form.find(':submit:first'); // TODO: if the submission is caused by "change" event, it will not work From d0ba90c1e4658ff7b22462ad841042161005b4ef Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Thu, 30 May 2013 15:05:42 -0400 Subject: [PATCH 066/107] better auto scrolling. --- framework/yii/assets/yii.activeForm.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/framework/yii/assets/yii.activeForm.js b/framework/yii/assets/yii.activeForm.js index fb4c16a..1a2e58d 100644 --- a/framework/yii/assets/yii.activeForm.js +++ b/framework/yii/assets/yii.activeForm.js @@ -143,7 +143,11 @@ }); updateSummary($form, messages); if (errors.length) { - $(window).scrollTop($form.find(errors.join(',')).first().offset().top); + var top = $form.find(errors.join(',')).first().offset().top; + var wtop = $(window).scrollTop(); + if (top < wtop || top > wtop + $(window).height) { + $(window).scrollTop(top); + } } else { data.validated = true; var $button = data.submitObject || $form.find(':submit:first'); From c9a7119b91beb7908405054569bee8f558d13802 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 31 May 2013 08:43:15 -0400 Subject: [PATCH 067/107] Fixes issue #467: allow view file to be absent as long as the themed version exists. --- framework/yii/base/View.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/base/View.php b/framework/yii/base/View.php index 7f965fa..219a0fb 100644 --- a/framework/yii/base/View.php +++ b/framework/yii/base/View.php @@ -235,10 +235,10 @@ class View extends Component public function renderFile($viewFile, $params = array(), $context = null) { $viewFile = Yii::getAlias($viewFile); + if ($this->theme !== null) { + $viewFile = $this->theme->applyTo($viewFile); + } if (is_file($viewFile)) { - if ($this->theme !== null) { - $viewFile = $this->theme->applyTo($viewFile); - } $viewFile = FileHelper::localize($viewFile); } else { throw new InvalidParamException("The view file does not exist: $viewFile"); From 00dec2bfbbbc30abeda70bc8bebcc2ecf6ce4188 Mon Sep 17 00:00:00 2001 From: Alexander Kochetov Date: Fri, 31 May 2013 19:02:04 +0400 Subject: [PATCH 068/107] \yii\widgets\Menu improvement --- framework/yii/widgets/Menu.php | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/framework/yii/widgets/Menu.php b/framework/yii/widgets/Menu.php index 08088d3..5b5d48c 100644 --- a/framework/yii/widgets/Menu.php +++ b/framework/yii/widgets/Menu.php @@ -9,6 +9,7 @@ namespace yii\widgets; use Yii; use yii\base\Widget; +use yii\helpers\ArrayHelper; use yii\helpers\Html; /** @@ -16,15 +17,15 @@ use yii\helpers\Html; * * 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: - * + * * ~~~ * echo Menu::widget(array( * 'items' => array( @@ -40,7 +41,7 @@ use yii\helpers\Html; * ), * )); * ~~~ - * + * * @author Qiang Xue * @since 2.0 */ @@ -68,6 +69,13 @@ class Menu extends Widget */ public $items = array(); /** + * @var array list of HTML attributes for the menu container tag. This will be overwritten + * by the "options" set in individual [[items]]. The following special options are recognized: + * + * - tag: string, defaults to "li", the tag name of the item container tags. + */ + public $itemOptions = array(); + /** * @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. @@ -110,7 +118,9 @@ class Menu extends Widget */ public $hideEmptyItems = true; /** - * @var array the HTML attributes for the menu's container tag. + * @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. */ public $options = array(); /** @@ -125,7 +135,7 @@ class Menu extends Widget 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. + * If not set, it will use the route of the current request. * @see params * @see isItemActive */ @@ -151,7 +161,9 @@ class Menu extends Widget $this->params = $_GET; } $items = $this->normalizeItems($this->items, $hasActiveChild); - echo Html::tag('ul', $this->renderItems($items), $this->options); + $options = $this->options; + $tag = ArrayHelper::remove($options, 'tag', 'ul'); + echo Html::tag($tag, $this->renderItems($items), $options); } /** @@ -164,7 +176,8 @@ class Menu extends Widget $n = count($items); $lines = array(); foreach ($items as $i => $item) { - $options = isset($item['options']) ? $item['options'] : array(); + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array())); + $tag = ArrayHelper::remove($options, 'tag', 'li'); $class = array(); if ($item['active']) { $class[] = $this->activeCssClass; @@ -189,7 +202,7 @@ class Menu extends Widget '{items}' => $this->renderItems($item['items']), )); } - $lines[] = Html::tag('li', $menu, $options); + $lines[] = Html::tag($tag, $menu, $options); } return implode("\n", $lines); } @@ -203,13 +216,13 @@ class Menu extends Widget protected function renderItem($item) { if (isset($item['url'])) { - $template = isset($item['template']) ? $item['template'] : $this->linkTemplate; + $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate); return strtr($template, array( '{url}' => Html::url($item['url']), '{label}' => $item['label'], )); } else { - $template = isset($item['template']) ? $item['template'] : $this->labelTemplate; + $template = ArrayHelper::getValue($item, 'template', $this->labelTemplate); return strtr($template, array( '{label}' => $item['label'], )); @@ -284,5 +297,4 @@ class Menu extends Widget } return false; } - } From 0d34aab0ab8bf27d27f01d248a37acfce7a3d332 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 31 May 2013 18:54:13 +0200 Subject: [PATCH 069/107] added missing default to getValue in boostrap tabs fixes #465 --- framework/yii/bootstrap/Tabs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php index a18eef6..4a85b9a 100644 --- a/framework/yii/bootstrap/Tabs.php +++ b/framework/yii/bootstrap/Tabs.php @@ -132,7 +132,7 @@ class Tabs extends Widget $header = Html::a($label, "#", array('class' => 'dropdown-toggle', 'data-toggle' => 'dropdown')) . "\n" . Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false)); } elseif (isset($item['content'])) { - $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options')); + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array())); $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n); $this->addCssClass($options, 'tab-pane'); From 800723945b98430fb56fbe36bf74eeac44006f59 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 31 May 2013 21:52:20 -0400 Subject: [PATCH 070/107] Fixes issue #194: Added Application::catchAll. --- framework/yii/web/Application.php | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/framework/yii/web/Application.php b/framework/yii/web/Application.php index a786985..12c9295 100644 --- a/framework/yii/web/Application.php +++ b/framework/yii/web/Application.php @@ -23,6 +23,26 @@ class Application extends \yii\base\Application * @var string the default route of this application. Defaults to 'site'. */ public $defaultRoute = 'site'; + /** + * @var array the configuration specifying a controller action which should handle + * all user requests. This is mainly used when the application is in maintenance mode + * and needs to handle all incoming requests via a single action. + * The configuration is an array whose first element specifies the route of the action. + * The rest of the array elements (key-value pairs) specify the parameters to be bound + * to the action. For example, + * + * ~~~ + * array( + * 'offline/notice', + * 'param1' => 'value1', + * 'param2' => 'value2', + * ) + * ~~~ + * + * Defaults to null, meaning catch-all is not effective. + */ + public $catchAll; + /** * Processes the request. @@ -34,7 +54,12 @@ class Application extends \yii\base\Application $request = $this->getRequest(); Yii::setAlias('@wwwroot', dirname($request->getScriptFile())); Yii::setAlias('@www', $request->getBaseUrl()); - list ($route, $params) = $request->resolve(); + if (empty($this->catchAll)) { + list ($route, $params) = $request->resolve(); + } else { + $route = $this->catchAll[0]; + $params = array_splice($this->catchAll, 1); + } try { return $this->runAction($route, $params); } catch (InvalidRouteException $e) { From 800b3dbed0bc8639a2052986a1c85c1a47b1b2a6 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Fri, 31 May 2013 21:55:11 -0400 Subject: [PATCH 071/107] Fixed build break. --- tests/unit/framework/i18n/FormatterTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/framework/i18n/FormatterTest.php b/tests/unit/framework/i18n/FormatterTest.php index f742f22..2a67422 100644 --- a/tests/unit/framework/i18n/FormatterTest.php +++ b/tests/unit/framework/i18n/FormatterTest.php @@ -83,6 +83,6 @@ class FormatterTest extends TestCase { $time = time(); $this->assertSame(date('n/j/y', $time), $this->formatter->asDate($time)); - $this->assertSame(date('M j, Y', $time), $this->formatter->asDate($time, 'long')); + $this->assertSame(date('F j, Y', $time), $this->formatter->asDate($time, 'long')); } } From 8ae946358949938680cf3b06be93e6f7519e8c77 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 1 Jun 2013 12:45:32 -0400 Subject: [PATCH 072/107] Fixes issue #472: doc fix. --- framework/yii/bootstrap/Nav.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index 548fe19..029f178 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -27,7 +27,7 @@ use yii\helpers\Html; * ), * array( * 'label' => 'Dropdown', - * 'items' => array( + * 'dropdown' => array( * array( * 'label' => 'DropdownA', * 'url' => '#', From bb0e2e84872bff1f668e54ffb737635d606f9a92 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Sun, 2 Jun 2013 00:37:53 +0200 Subject: [PATCH 073/107] updated composer script event for setting permissions --- apps/advanced/composer.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/advanced/composer.json b/apps/advanced/composer.json index 95b3b13..cc3ec2d 100644 --- a/apps/advanced/composer.json +++ b/apps/advanced/composer.json @@ -19,10 +19,7 @@ "yiisoft/yii2-composer": "dev-master" }, "scripts": { - "post-install-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" - ], - "post-update-cmd": [ + "post-create-project-cmd": [ "yii\\composer\\InstallHandler::setPermissions" ] }, From 607f3a59873167f18e8f9fb74455982c899d6dd0 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 2 Jun 2013 07:11:27 -0400 Subject: [PATCH 074/107] Fixes issue #476 --- framework/yii/caching/FileCache.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/framework/yii/caching/FileCache.php b/framework/yii/caching/FileCache.php index 0c6d119..1dbcb09 100644 --- a/framework/yii/caching/FileCache.php +++ b/framework/yii/caching/FileCache.php @@ -25,8 +25,9 @@ class FileCache extends Cache { /** * @var string the directory to store cache files. You may use path alias here. + * If not set, it will use the "cache" subdirectory under the application runtime path. */ - public $cachePath = '@app/runtime/cache'; + public $cachePath; /** * @var string cache file suffix. Defaults to '.bin'. */ @@ -51,7 +52,11 @@ class FileCache extends Cache public function init() { parent::init(); - $this->cachePath = Yii::getAlias($this->cachePath); + if ($this->cachePath === null) { + $this->cachePath = Yii::$app->getRuntimePath() . DIRECTORY_SEPARATOR . 'cache'; + } else { + $this->cachePath = Yii::getAlias($this->cachePath); + } if (!is_dir($this->cachePath)) { mkdir($this->cachePath, 0777, true); } From 36bbfd54caaa4a43c6634475b51f776c7d9af9d7 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 2 Jun 2013 07:29:29 -0400 Subject: [PATCH 075/107] Implemented "@app/runtime" alias. --- framework/yii/base/Application.php | 46 ++++++++++++++++++++++++++----------- framework/yii/caching/FileCache.php | 8 ++----- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index d38f6a9..95993a4 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -67,35 +67,53 @@ class Application extends Module * Constructor. * @param array $config name-value pairs that will be used to initialize the object properties. * Note that the configuration must contain both [[id]] and [[basePath]]. - * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing. */ public function __construct($config = array()) { Yii::$app = $this; + $this->preInit($config); + + $this->registerErrorHandlers(); + $this->registerCoreComponents(); + + Component::__construct($config); + } + + /** + * Pre-initializes the application. + * This method is called at the beginning of the application constructor. + * When this method is called, none of the application properties are initialized yet. + * The default implementation will initialize a few important properties + * that may be referenced during the initialization of the rest of the properties. + * @param array $config the application configuration + * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing. + */ + public function preInit($config) + { if (!isset($config['id'])) { throw new InvalidConfigException('The "id" configuration is required.'); } - - if (isset($config['basePath'])) { - $this->setBasePath($config['basePath']); - Yii::setAlias('@app', $this->getBasePath()); - unset($config['basePath']); - } else { + if (!isset($config['basePath'])) { throw new InvalidConfigException('The "basePath" configuration is required.'); } - + + $this->setBasePath($config['basePath']); + Yii::setAlias('@app', $this->getBasePath()); + unset($config['basePath']); + + if (isset($config['runtime'])) { + $this->setRuntimePath($config['runtime']); + unset($config['runtime']); + } + Yii::setAlias('@app/runtime', $this->getRuntimePath()); + if (isset($config['timeZone'])) { $this->setTimeZone($config['timeZone']); unset($config['timeZone']); } elseif (!ini_get('date.timezone')) { $this->setTimeZone('UTC'); - } - - $this->registerErrorHandlers(); - $this->registerCoreComponents(); - - Component::__construct($config); + } } /** diff --git a/framework/yii/caching/FileCache.php b/framework/yii/caching/FileCache.php index 1dbcb09..8d6a704 100644 --- a/framework/yii/caching/FileCache.php +++ b/framework/yii/caching/FileCache.php @@ -27,7 +27,7 @@ class FileCache extends Cache * @var string the directory to store cache files. You may use path alias here. * If not set, it will use the "cache" subdirectory under the application runtime path. */ - public $cachePath; + public $cachePath = '@app/runtime/cache'; /** * @var string cache file suffix. Defaults to '.bin'. */ @@ -52,11 +52,7 @@ class FileCache extends Cache public function init() { parent::init(); - if ($this->cachePath === null) { - $this->cachePath = Yii::$app->getRuntimePath() . DIRECTORY_SEPARATOR . 'cache'; - } else { - $this->cachePath = Yii::getAlias($this->cachePath); - } + $this->cachePath = Yii::getAlias($this->cachePath); if (!is_dir($this->cachePath)) { mkdir($this->cachePath, 0777, true); } From 5c56c0fc02e54bf1ab73c172ce0e0577ccab57a8 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sun, 2 Jun 2013 16:05:11 +0200 Subject: [PATCH 076/107] Better flexibility for abstract db types Feature was requested in yiisoft/yii#765 --- framework/yii/db/QueryBuilder.php | 6 ++ framework/yii/db/mysql/QueryBuilder.php | 2 +- tests/unit/framework/db/QueryBuilderTest.php | 109 +++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 tests/unit/framework/db/QueryBuilderTest.php diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index c0b4223..3e7f95a 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -464,6 +464,8 @@ class QueryBuilder extends \yii\base\Object * the first part will be converted, and the rest of the parts will be appended to the converted result. * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'. * + // TODO documentation + * * If a type cannot be found in [[typeMap]], it will be returned without any change. * @param string $type abstract column type * @return string physical column type. @@ -472,6 +474,10 @@ class QueryBuilder extends \yii\base\Object { if (isset($this->typeMap[$type])) { return $this->typeMap[$type]; + } elseif ((preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches))) { + if (isset($this->typeMap[$matches[1]])) { + return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3]; + } } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) { if (isset($this->typeMap[$matches[1]])) { return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type); diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php index 70c6d64..4b35e24 100644 --- a/framework/yii/db/mysql/QueryBuilder.php +++ b/framework/yii/db/mysql/QueryBuilder.php @@ -29,7 +29,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_INTEGER => 'int(11)', Schema::TYPE_BIGINT => 'bigint(20)', Schema::TYPE_FLOAT => 'float', - Schema::TYPE_DECIMAL => 'decimal', + Schema::TYPE_DECIMAL => 'decimal(10,0)', Schema::TYPE_DATETIME => 'datetime', Schema::TYPE_TIMESTAMP => 'timestamp', Schema::TYPE_TIME => 'time', diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php new file mode 100644 index 0000000..355f080 --- /dev/null +++ b/tests/unit/framework/db/QueryBuilderTest.php @@ -0,0 +1,109 @@ +driverName) + { + case 'mysql': + return new MysqlQueryBuilder($this->getConnection()); + case 'sqlite': + return new SqliteQueryBuilder($this->getConnection()); + case 'mssql': + return new MssqlQueryBuilder($this->getConnection()); + } + throw new \Exception('Test is not implemented for ' . $this->driverName); + } + + /** + * this is not used as a dataprovider for testGetColumnType to speed up the test + * when used as dataprovider every single line will cause a reconnect with the database which is not needed here + */ + public function columnTypes() + { + return array( + array(Schema::TYPE_PK, 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY'), + array(Schema::TYPE_PK . '(8)', 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY'), + array(Schema::TYPE_PK . ' CHECK (value > 5)', 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'), + array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'int(8) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK (value > 5)'), + array(Schema::TYPE_STRING, 'varchar(255)'), + array(Schema::TYPE_STRING . '(32)', 'varchar(32)'), + array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'), + array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'), + array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'), + array(Schema::TYPE_TEXT, 'text'), + array(Schema::TYPE_TEXT . '(255)', 'text'), + array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'), + array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'), + array(Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'), + array(Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'), + array(Schema::TYPE_SMALLINT, 'smallint(6)'), + array(Schema::TYPE_SMALLINT . '(8)', 'smallint(8)'), + array(Schema::TYPE_INTEGER, 'int(11)'), + array(Schema::TYPE_INTEGER . '(8)', 'int(8)'), + array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'int(11) CHECK (value > 5)'), + array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'int(8) CHECK (value > 5)'), + array(Schema::TYPE_INTEGER . ' NOT NULL', 'int(11) NOT NULL'), + array(Schema::TYPE_BIGINT, 'bigint(20)'), + array(Schema::TYPE_BIGINT . '(8)', 'bigint(8)'), + array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint(20) CHECK (value > 5)'), + array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint(8) CHECK (value > 5)'), + array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint(20) NOT NULL'), + array(Schema::TYPE_FLOAT, 'float'), + array(Schema::TYPE_FLOAT . '(16,5)', 'float'), + array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float CHECK (value > 5.6)'), + array(Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'float CHECK (value > 5.6)'), + array(Schema::TYPE_FLOAT . ' NOT NULL', 'float NOT NULL'), + array(Schema::TYPE_DECIMAL, 'decimal(10,0)'), + array(Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'), + array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'), + array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'), + array(Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'), + array(Schema::TYPE_DATETIME, 'datetime'), + array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'), + array(Schema::TYPE_TIMESTAMP, 'timestamp'), + array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'), + array(Schema::TYPE_TIME, 'time'), + array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"), + array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'), + array(Schema::TYPE_DATE, 'date'), + array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'), + array(Schema::TYPE_BINARY, 'blob'), + array(Schema::TYPE_BOOLEAN, 'tinyint(1)'), + array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'tinyint(1) NOT NULL DEFAULT 1'), + array(Schema::TYPE_MONEY, 'decimal(19,4)'), + array(Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'), + array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'), + array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'), + array(Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'), + ); + } + + /** + * + */ + public function testGetColumnType() + { + $qb = $this->getQueryBuilder(); + foreach($this->columnTypes() as $item) { + list ($column, $expected) = $item; + $this->assertEquals($expected, $qb->getColumnType($column)); + } + } +} From 22357befadbb1ab920f67c9557dff6c0b295e2f0 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 2 Jun 2013 15:19:29 -0400 Subject: [PATCH 077/107] Added "@runtime" and "@vendor" aliases. --- extensions/smarty/yii/smarty/ViewRenderer.php | 4 ++-- extensions/twig/yii/twig/ViewRenderer.php | 2 +- framework/yii/base/Application.php | 8 +++++++- framework/yii/caching/FileCache.php | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/extensions/smarty/yii/smarty/ViewRenderer.php b/extensions/smarty/yii/smarty/ViewRenderer.php index d8c5d30..164ae8c 100644 --- a/extensions/smarty/yii/smarty/ViewRenderer.php +++ b/extensions/smarty/yii/smarty/ViewRenderer.php @@ -26,12 +26,12 @@ class ViewRenderer extends BaseViewRenderer /** * @var string the directory or path alias pointing to where Smarty cache will be stored. */ - public $cachePath = '@app/runtime/Smarty/cache'; + public $cachePath = '@runtime/Smarty/cache'; /** * @var string the directory or path alias pointing to where Smarty compiled templates will be stored. */ - public $compilePath = '@app/runtime/Smarty/compile'; + public $compilePath = '@runtime/Smarty/compile'; /** * @var Smarty diff --git a/extensions/twig/yii/twig/ViewRenderer.php b/extensions/twig/yii/twig/ViewRenderer.php index 7498d86..76bee95 100644 --- a/extensions/twig/yii/twig/ViewRenderer.php +++ b/extensions/twig/yii/twig/ViewRenderer.php @@ -25,7 +25,7 @@ class ViewRenderer extends BaseViewRenderer /** * @var string the directory or path alias pointing to where Twig cache will be stored. */ - public $cachePath = '@app/runtime/Twig/cache'; + public $cachePath = '@runtime/Twig/cache'; /** * @var array Twig options diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php index 95993a4..495c1f8 100644 --- a/framework/yii/base/Application.php +++ b/framework/yii/base/Application.php @@ -102,11 +102,17 @@ class Application extends Module Yii::setAlias('@app', $this->getBasePath()); unset($config['basePath']); + if (isset($config['vendor'])) { + $this->setVendorPath($config['vendor']); + unset($config['vendorPath']); + } + Yii::setAlias('@vendor', $this->getVendorPath()); + if (isset($config['runtime'])) { $this->setRuntimePath($config['runtime']); unset($config['runtime']); } - Yii::setAlias('@app/runtime', $this->getRuntimePath()); + Yii::setAlias('@runtime', $this->getRuntimePath()); if (isset($config['timeZone'])) { $this->setTimeZone($config['timeZone']); diff --git a/framework/yii/caching/FileCache.php b/framework/yii/caching/FileCache.php index 8d6a704..a15751e 100644 --- a/framework/yii/caching/FileCache.php +++ b/framework/yii/caching/FileCache.php @@ -27,7 +27,7 @@ class FileCache extends Cache * @var string the directory to store cache files. You may use path alias here. * If not set, it will use the "cache" subdirectory under the application runtime path. */ - public $cachePath = '@app/runtime/cache'; + public $cachePath = '@runtime/cache'; /** * @var string cache file suffix. Defaults to '.bin'. */ From c28b0183a5188adf1cbb574e4dbb33bb62817ca4 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sun, 2 Jun 2013 22:12:37 +0200 Subject: [PATCH 078/107] whitespace --- tests/unit/framework/db/QueryBuilderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/framework/db/QueryBuilderTest.php b/tests/unit/framework/db/QueryBuilderTest.php index 355f080..7dc4731 100644 --- a/tests/unit/framework/db/QueryBuilderTest.php +++ b/tests/unit/framework/db/QueryBuilderTest.php @@ -96,7 +96,7 @@ class QueryBuilderTest extends DatabaseTestCase } /** - * + * */ public function testGetColumnType() { From 7117a6752b6927038b62e2366d9034b282b3ced0 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Sun, 2 Jun 2013 22:58:42 +0200 Subject: [PATCH 079/107] Added phpdoc about precision constraints for abstract db types --- framework/yii/db/QueryBuilder.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index 3e7f95a..46457c6 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -464,7 +464,11 @@ class QueryBuilder extends \yii\base\Object * the first part will be converted, and the rest of the parts will be appended to the converted result. * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'. * - // TODO documentation + * For some of the abstract types you can also specify a length or precision constraint + * by prepending it in round brackets directly to the type. + * For example `string(32)` will be converted into "varchar(32)" on a MySQL database. + * If the underlying DBMS does not support these kind of constraints for a type it will + * be ignored. * * If a type cannot be found in [[typeMap]], it will be returned without any change. * @param string $type abstract column type From 64e5911b5e1ea27d14334da60aa82e00718b47f8 Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Sun, 2 Jun 2013 23:42:11 +0200 Subject: [PATCH 080/107] added updated composer script event for setting permissions --- apps/basic/composer.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/basic/composer.json b/apps/basic/composer.json index 29b05d1..b01c56c 100644 --- a/apps/basic/composer.json +++ b/apps/basic/composer.json @@ -19,10 +19,7 @@ "yiisoft/yii2-composer": "dev-master" }, "scripts": { - "post-install-cmd": [ - "yii\\composer\\InstallHandler::setPermissions" - ], - "post-update-cmd": [ + "post-create-project-cmd": [ "yii\\composer\\InstallHandler::setPermissions" ] }, From 5615afab2195975261d26926e19ab35fa67e206e Mon Sep 17 00:00:00 2001 From: Tobias Munk Date: Sun, 2 Jun 2013 23:42:31 +0200 Subject: [PATCH 081/107] updated deps --- apps/advanced/composer.lock | 10 +++++----- apps/basic/composer.lock | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/advanced/composer.lock b/apps/advanced/composer.lock index 516ae32..3022597 100644 --- a/apps/advanced/composer.lock +++ b/apps/advanced/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "05f7bcd0e99931b52415eaeb62d54daf", + "hash": "2d1053fbaaf2044054f273a71d0ccde0", "packages": [ { "name": "yiisoft/yii2", @@ -11,12 +11,12 @@ "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d" + "reference": "3ad6334be076a80df3b2ea0b57f38bd0c6901989" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", - "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/3ad6334be076a80df3b2ea0b57f38bd0c6901989", + "reference": "3ad6334be076a80df3b2ea0b57f38bd0c6901989", "shasum": "" }, "require": { @@ -97,7 +97,7 @@ "framework", "yii" ], - "time": "2013-05-29 02:55:14" + "time": "2013-06-02 19:19:29" }, { "name": "yiisoft/yii2-composer", diff --git a/apps/basic/composer.lock b/apps/basic/composer.lock index fe87399..a1cb48d 100644 --- a/apps/basic/composer.lock +++ b/apps/basic/composer.lock @@ -3,7 +3,7 @@ "This file locks the dependencies of your project to a known state", "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" ], - "hash": "0411dbbd774aa1c89256c77c68023940", + "hash": "91ba258de768b93025f86071f3bb4b84", "packages": [ { "name": "yiisoft/yii2", @@ -11,12 +11,12 @@ "source": { "type": "git", "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d" + "reference": "3ad6334be076a80df3b2ea0b57f38bd0c6901989" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", - "reference": "2d93f20ba6044ac3f1957300c4ae85fd98ecf47d", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/3ad6334be076a80df3b2ea0b57f38bd0c6901989", + "reference": "3ad6334be076a80df3b2ea0b57f38bd0c6901989", "shasum": "" }, "require": { @@ -97,7 +97,7 @@ "framework", "yii" ], - "time": "2013-05-29 02:55:14" + "time": "2013-06-02 19:19:29" }, { "name": "yiisoft/yii2-composer", From af68ee9d2745bf7b3fd27ea786c0d9add221e17b Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 3 Jun 2013 00:24:39 +0200 Subject: [PATCH 082/107] Sqlite unit test for abstract db types --- framework/yii/db/sqlite/QueryBuilder.php | 4 +- .../framework/db/sqlite/SqliteQueryBuilderTest.php | 74 ++++++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/yii/db/sqlite/QueryBuilder.php index 72d48f4..52c101b 100644 --- a/framework/yii/db/sqlite/QueryBuilder.php +++ b/framework/yii/db/sqlite/QueryBuilder.php @@ -30,13 +30,13 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_INTEGER => 'integer', Schema::TYPE_BIGINT => 'bigint', Schema::TYPE_FLOAT => 'float', - Schema::TYPE_DECIMAL => 'decimal', + Schema::TYPE_DECIMAL => 'decimal(10,0)', Schema::TYPE_DATETIME => 'datetime', Schema::TYPE_TIMESTAMP => 'timestamp', Schema::TYPE_TIME => 'time', Schema::TYPE_DATE => 'date', Schema::TYPE_BINARY => 'blob', - Schema::TYPE_BOOLEAN => 'tinyint(1)', + Schema::TYPE_BOOLEAN => 'boolean', Schema::TYPE_MONEY => 'decimal(19,4)', ); diff --git a/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php new file mode 100644 index 0000000..c36628f --- /dev/null +++ b/tests/unit/framework/db/sqlite/SqliteQueryBuilderTest.php @@ -0,0 +1,74 @@ + 5)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)'), + array(Schema::TYPE_PK . '(8) CHECK (value > 5)', 'integer PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (value > 5)'), + array(Schema::TYPE_STRING, 'varchar(255)'), + array(Schema::TYPE_STRING . '(32)', 'varchar(32)'), + array(Schema::TYPE_STRING . ' CHECK (value LIKE "test%")', 'varchar(255) CHECK (value LIKE "test%")'), + array(Schema::TYPE_STRING . '(32) CHECK (value LIKE "test%")', 'varchar(32) CHECK (value LIKE "test%")'), + array(Schema::TYPE_STRING . ' NOT NULL', 'varchar(255) NOT NULL'), + array(Schema::TYPE_TEXT, 'text'), + array(Schema::TYPE_TEXT . '(255)', 'text'), + array(Schema::TYPE_TEXT . ' CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'), + array(Schema::TYPE_TEXT . '(255) CHECK (value LIKE "test%")', 'text CHECK (value LIKE "test%")'), + array(Schema::TYPE_TEXT . ' NOT NULL', 'text NOT NULL'), + array(Schema::TYPE_TEXT . '(255) NOT NULL', 'text NOT NULL'), + array(Schema::TYPE_SMALLINT, 'smallint'), + array(Schema::TYPE_SMALLINT . '(8)', 'smallint'), + array(Schema::TYPE_INTEGER, 'integer'), + array(Schema::TYPE_INTEGER . '(8)', 'integer'), + array(Schema::TYPE_INTEGER . ' CHECK (value > 5)', 'integer CHECK (value > 5)'), + array(Schema::TYPE_INTEGER . '(8) CHECK (value > 5)', 'integer CHECK (value > 5)'), + array(Schema::TYPE_INTEGER . ' NOT NULL', 'integer NOT NULL'), + array(Schema::TYPE_BIGINT, 'bigint'), + array(Schema::TYPE_BIGINT . '(8)', 'bigint'), + array(Schema::TYPE_BIGINT . ' CHECK (value > 5)', 'bigint CHECK (value > 5)'), + array(Schema::TYPE_BIGINT . '(8) CHECK (value > 5)', 'bigint CHECK (value > 5)'), + array(Schema::TYPE_BIGINT . ' NOT NULL', 'bigint NOT NULL'), + array(Schema::TYPE_FLOAT, 'float'), + array(Schema::TYPE_FLOAT . '(16,5)', 'float'), + array(Schema::TYPE_FLOAT . ' CHECK (value > 5.6)', 'float CHECK (value > 5.6)'), + array(Schema::TYPE_FLOAT . '(16,5) CHECK (value > 5.6)', 'float CHECK (value > 5.6)'), + array(Schema::TYPE_FLOAT . ' NOT NULL', 'float NOT NULL'), + array(Schema::TYPE_DECIMAL, 'decimal(10,0)'), + array(Schema::TYPE_DECIMAL . '(12,4)', 'decimal(12,4)'), + array(Schema::TYPE_DECIMAL . ' CHECK (value > 5.6)', 'decimal(10,0) CHECK (value > 5.6)'), + array(Schema::TYPE_DECIMAL . '(12,4) CHECK (value > 5.6)', 'decimal(12,4) CHECK (value > 5.6)'), + array(Schema::TYPE_DECIMAL . ' NOT NULL', 'decimal(10,0) NOT NULL'), + array(Schema::TYPE_DATETIME, 'datetime'), + array(Schema::TYPE_DATETIME . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "datetime CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_DATETIME . ' NOT NULL', 'datetime NOT NULL'), + array(Schema::TYPE_TIMESTAMP, 'timestamp'), + array(Schema::TYPE_TIMESTAMP . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "timestamp CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_TIMESTAMP . ' NOT NULL', 'timestamp NOT NULL'), + array(Schema::TYPE_TIME, 'time'), + array(Schema::TYPE_TIME . " CHECK(value BETWEEN '12:00:00' AND '13:01:01')", "time CHECK(value BETWEEN '12:00:00' AND '13:01:01')"), + array(Schema::TYPE_TIME . ' NOT NULL', 'time NOT NULL'), + array(Schema::TYPE_DATE, 'date'), + array(Schema::TYPE_DATE . " CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')", "date CHECK(value BETWEEN '2011-01-01' AND '2013-01-01')"), + array(Schema::TYPE_DATE . ' NOT NULL', 'date NOT NULL'), + array(Schema::TYPE_BINARY, 'blob'), + array(Schema::TYPE_BOOLEAN, 'boolean'), + array(Schema::TYPE_BOOLEAN . ' NOT NULL DEFAULT 1', 'boolean NOT NULL DEFAULT 1'), + array(Schema::TYPE_MONEY, 'decimal(19,4)'), + array(Schema::TYPE_MONEY . '(16,2)', 'decimal(16,2)'), + array(Schema::TYPE_MONEY . ' CHECK (value > 0.0)', 'decimal(19,4) CHECK (value > 0.0)'), + array(Schema::TYPE_MONEY . '(16,2) CHECK (value > 0.0)', 'decimal(16,2) CHECK (value > 0.0)'), + array(Schema::TYPE_MONEY . ' NOT NULL', 'decimal(19,4) NOT NULL'), + ); + } +} \ No newline at end of file From 9b6df54c7df061e08cce580dc9cba4e7c2c80e4f Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 3 Jun 2013 00:28:32 +0200 Subject: [PATCH 083/107] Adjusted type map in MSSQL Querybuilder MSSQL seems to support limits for decimal type, so we add a default limit. --- framework/yii/db/mssql/QueryBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/db/mssql/QueryBuilder.php b/framework/yii/db/mssql/QueryBuilder.php index 45a7507..e7f8f80 100644 --- a/framework/yii/db/mssql/QueryBuilder.php +++ b/framework/yii/db/mssql/QueryBuilder.php @@ -28,7 +28,7 @@ class QueryBuilder extends \yii\db\QueryBuilder Schema::TYPE_INTEGER => 'int(11)', Schema::TYPE_BIGINT => 'bigint(20)', Schema::TYPE_FLOAT => 'float', - Schema::TYPE_DECIMAL => 'decimal', + Schema::TYPE_DECIMAL => 'decimal(10,0)', Schema::TYPE_DATETIME => 'datetime', Schema::TYPE_TIMESTAMP => 'timestamp', Schema::TYPE_TIME => 'time', From 8435285c1944675a47cdfb4af7f41843d1393227 Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 2 Jun 2013 22:19:38 -0400 Subject: [PATCH 084/107] doc fix. --- framework/yii/base/Module.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index ec3001e..060d321 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -409,11 +409,11 @@ abstract class Module extends Component * ~~~ * array( * 'comment' => array( - * 'class' => 'app\modules\CommentModule', + * 'class' => 'app\modules\comment\CommentModule', * 'db' => 'db', * ), * 'booking' => array( - * 'class' => 'app\modules\BookingModule', + * 'class' => 'app\modules\booking\BookingModule', * ), * ) * ~~~ From 21160338d7a2dbad04228028b00172ae3fb6602a Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 2 Jun 2013 22:31:17 -0400 Subject: [PATCH 085/107] Implemented default controller namespace. --- apps/basic/config/main.php | 1 - framework/yii/base/Module.php | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/basic/config/main.php b/apps/basic/config/main.php index 9adfba6..054259e 100644 --- a/apps/basic/config/main.php +++ b/apps/basic/config/main.php @@ -4,7 +4,6 @@ return array( 'id' => 'bootstrap', 'basePath' => dirname(__DIR__), 'preload' => array('log'), - 'controllerNamespace' => 'app\controllers', 'modules' => array( // 'debug' => array( // 'class' => 'yii\debug\Module', diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php index 060d321..fac4164 100644 --- a/framework/yii/base/Module.php +++ b/framework/yii/base/Module.php @@ -88,7 +88,11 @@ abstract class Module extends Component */ public $controllerMap = array(); /** - * @var string the namespace that controller classes are in. Default is to use global namespace. + * @var string the namespace that controller classes are in. If not set, + * it will use the "controllers" sub-namespace under the namespace of this module. + * For example, if the namespace of this module is "foo\bar", then the default + * controller namespace would be "foo\bar\controllers". + * If the module is an application, it will default to "app\controllers". */ public $controllerNamespace; /** @@ -178,6 +182,16 @@ abstract class Module extends Component public function init() { $this->preloadComponents(); + if ($this->controllerNamespace === null) { + if ($this instanceof Application) { + $this->controllerNamespace = 'app\\controllers'; + } else { + $class = get_class($this); + if (($pos = strrpos($class, '\\')) !== false) { + $this->controllerNamespace = substr($class, 0, $pos) . '\\controllers'; + } + } + } } /** From cf47a71db7ffa997478d461330850f71f565f73a Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sun, 2 Jun 2013 22:47:02 -0400 Subject: [PATCH 086/107] Fixes issue #478: Improved the generation of secret key --- framework/yii/helpers/base/SecurityHelper.php | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/framework/yii/helpers/base/SecurityHelper.php b/framework/yii/helpers/base/SecurityHelper.php index 3f69fee..f646a24 100644 --- a/framework/yii/helpers/base/SecurityHelper.php +++ b/framework/yii/helpers/base/SecurityHelper.php @@ -131,15 +131,30 @@ class SecurityHelper $keys = is_file($keyFile) ? require($keyFile) : array(); } if (!isset($keys[$name])) { - // generate a 32-char random key - $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - $keys[$name] = substr(str_shuffle(str_repeat($chars, 5)), 0, $length); + $keys[$name] = static::generateRandomKey($length); file_put_contents($keyFile, " Date: Mon, 3 Jun 2013 08:28:14 +0200 Subject: [PATCH 087/107] removed unneccessary brackets --- framework/yii/db/QueryBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php index 46457c6..04f1969 100644 --- a/framework/yii/db/QueryBuilder.php +++ b/framework/yii/db/QueryBuilder.php @@ -478,7 +478,7 @@ class QueryBuilder extends \yii\base\Object { if (isset($this->typeMap[$type])) { return $this->typeMap[$type]; - } elseif ((preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches))) { + } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) { if (isset($this->typeMap[$matches[1]])) { return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3]; } From fbb93d0b63f8fa91ee9068dc563f27ddae4a530c Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 3 Jun 2013 08:42:50 +0200 Subject: [PATCH 088/107] doc enhancements for #461 --- framework/yii/web/UrlManager.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/framework/yii/web/UrlManager.php b/framework/yii/web/UrlManager.php index a6ef0cf..d7135c5 100644 --- a/framework/yii/web/UrlManager.php +++ b/framework/yii/web/UrlManager.php @@ -43,15 +43,18 @@ class UrlManager extends Component * array, one can use the key to represent the pattern and the value the corresponding route. * For example, `'post/' => 'post/view'`. * - * For RESTful routing this shortcut also allows you to specify the [[UrlRule::verb|HTTP verb]] - * the rule should apply for by prepending it to the pattern, separated by a blank. + * For RESTful routing the mentioned shortcut format also allows you to specify the + * [[UrlRule::verb|HTTP verb]] that the rule should apply for. + * You can do that by prepending it to the pattern, separated by a space. * For example, `'PUT post/' => 'post/update'`. * You may specify multiple verbs by separating them with comma * like this: `'POST,PUT post/index' => 'post/create'`. + * The supported verbs in the shortcut format are: GET, POST, PUT and DELETE. * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way * so you normally would not specify a verb for normal GET request. * * Here is an example configuration for RESTful CRUD controller: + * * ~~~php * array( * 'dashboard' => 'site/index', From 11923064810be47ed24888efcd53a36674e83fd2 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 3 Jun 2013 08:59:41 +0200 Subject: [PATCH 089/107] improved UrlManager RESTful syntax regex --- framework/yii/web/UrlManager.php | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/framework/yii/web/UrlManager.php b/framework/yii/web/UrlManager.php index d7135c5..44c63c5 100644 --- a/framework/yii/web/UrlManager.php +++ b/framework/yii/web/UrlManager.php @@ -45,11 +45,11 @@ class UrlManager extends Component * * For RESTful routing the mentioned shortcut format also allows you to specify the * [[UrlRule::verb|HTTP verb]] that the rule should apply for. - * You can do that by prepending it to the pattern, separated by a space. + * You can do that by prepending it to the pattern, separated by space. * For example, `'PUT post/' => 'post/update'`. * You may specify multiple verbs by separating them with comma * like this: `'POST,PUT post/index' => 'post/create'`. - * The supported verbs in the shortcut format are: GET, POST, PUT and DELETE. + * The supported verbs in the shortcut format are: GET, HEAD, POST, PUT and DELETE. * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way * so you normally would not specify a verb for normal GET request. * @@ -142,13 +142,10 @@ class UrlManager extends Component $rule = array( 'route' => $rule, ); - if (($pos = strpos($key, ' ')) !== false) { - $verbs = substr($key, 0, $pos); - if (preg_match('/^((GET|POST|PUT|DELETE),?)*$/', $verbs, $matches)) { - $rule['verb'] = explode(',', $verbs); - $rule['mode'] = UrlRule::PARSING_ONLY; - $key = substr($key, $pos + 1); - } + if (preg_match('/^((?:(GET|HEAD|POST|PUT|DELETE),)*(GET|HEAD|POST|PUT|DELETE))\s+(.*)$/', $key, $matches)) { + $rule['verb'] = explode(',', $matches[1]); + $rule['mode'] = UrlRule::PARSING_ONLY; + $key = $matches[4]; } $rule['pattern'] = $key; } From e94cc6bb9ea56886975827ee41087569438d2bb9 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Mon, 3 Jun 2013 09:32:14 +0200 Subject: [PATCH 090/107] unit test for UrlManager RESTful routing syntax removed test param 'appClass' as it does not make sense to configure application class for all tests, some explicitly need webapp where some might explicitly need console app. --- tests/unit/TestCase.php | 3 +- tests/unit/data/config.php | 2 -- tests/unit/framework/web/UrlManagerTest.php | 54 ++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/tests/unit/TestCase.php b/tests/unit/TestCase.php index 479f85d..efcedf0 100644 --- a/tests/unit/TestCase.php +++ b/tests/unit/TestCase.php @@ -38,14 +38,13 @@ abstract class TestCase extends \yii\test\TestCase * The application will be destroyed on tearDown() automatically. * @param array $config The application configuration, if needed */ - protected function mockApplication($config=array()) + protected function mockApplication($config = array(), $appClass = '\yii\console\Application') { static $defaultConfig = array( 'id' => 'testapp', 'basePath' => __DIR__, ); - $appClass = $this->getParam( 'appClass', '\yii\web\Application' ); new $appClass(array_merge($defaultConfig,$config)); } diff --git a/tests/unit/data/config.php b/tests/unit/data/config.php index 88c8127..a2cc445 100644 --- a/tests/unit/data/config.php +++ b/tests/unit/data/config.php @@ -1,8 +1,6 @@ '\yii\web\Application', - 'appClass' => '\yii\console\Application', 'databases' => array( 'mysql' => array( 'dsn' => 'mysql:host=127.0.0.1;dbname=yiitest', diff --git a/tests/unit/framework/web/UrlManagerTest.php b/tests/unit/framework/web/UrlManagerTest.php index d0e8775..7da8f34 100644 --- a/tests/unit/framework/web/UrlManagerTest.php +++ b/tests/unit/framework/web/UrlManagerTest.php @@ -249,5 +249,57 @@ class UrlManagerTest extends TestCase $this->assertFalse($result); } - // TODO test RESTful pattern syntax e.g. 'GET index' => 'site/index' + public function testParseRESTRequest() + { + $manager = new UrlManager(array( + 'cache' => null, + )); + $request = new Request; + + // pretty URL rules + $manager = new UrlManager(array( + 'enablePrettyUrl' => true, + 'cache' => null, + 'rules' => array( + 'PUT,POST post//' => 'post/create', + 'DELETE post/<id>' => 'post/delete', + 'post/<id>/<title>' => 'post/view', + 'POST/GET' => 'post/get', + ), + )); + // matching pathinfo GET request + $_SERVER['REQUEST_METHOD'] = 'GET'; + $request->pathInfo = 'post/123/this+is+sample'; + $result = $manager->parseRequest($request); + $this->assertEquals(array('post/view', array('id' => '123', 'title' => 'this+is+sample')), $result); + // matching pathinfo PUT/POST request + $_SERVER['REQUEST_METHOD'] = 'PUT'; + $request->pathInfo = 'post/123/this+is+sample'; + $result = $manager->parseRequest($request); + $this->assertEquals(array('post/create', array('id' => '123', 'title' => 'this+is+sample')), $result); + $_SERVER['REQUEST_METHOD'] = 'POST'; + $request->pathInfo = 'post/123/this+is+sample'; + $result = $manager->parseRequest($request); + $this->assertEquals(array('post/create', array('id' => '123', 'title' => 'this+is+sample')), $result); + + // no wrong matching + $_SERVER['REQUEST_METHOD'] = 'POST'; + $request->pathInfo = 'POST/GET'; + $result = $manager->parseRequest($request); + $this->assertEquals(array('post/get', array()), $result); + + // createUrl should ignore REST rules + $this->mockApplication(array( + 'components' => array( + 'request' => array( + 'hostInfo' => 'http://localhost/', + 'baseUrl' => '/app' + ) + ) + ), \yii\web\Application::className()); + $this->assertEquals('/app/post/delete?id=123', $manager->createUrl('post/delete', array('id' => 123))); + $this->destroyApplication(); + + unset($_SERVER['REQUEST_METHOD']); + } } From 8157ea58048a080fa3755627aafe78015435db12 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Mon, 3 Jun 2013 09:45:41 -0400 Subject: [PATCH 091/107] Fixes issue #481. --- framework/yii/validators/DateValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/yii/validators/DateValidator.php b/framework/yii/validators/DateValidator.php index 7370b78..2f3ce2d 100644 --- a/framework/yii/validators/DateValidator.php +++ b/framework/yii/validators/DateValidator.php @@ -58,7 +58,7 @@ class DateValidator extends Validator $date = DateTime::createFromFormat($this->format, $value); if ($date === false) { $this->addError($object, $attribute, $this->message); - } elseif ($this->timestampAttribute !== false) { + } elseif ($this->timestampAttribute !== null) { $object->{$this->timestampAttribute} = $date->getTimestamp(); } } From 5596e50bf3378be11867777c6e87922d3d44ac05 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Mon, 3 Jun 2013 10:40:09 -0400 Subject: [PATCH 092/107] Fixes issue #472. --- framework/yii/bootstrap/Nav.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php index 029f178..6c091a4 100644 --- a/framework/yii/bootstrap/Nav.php +++ b/framework/yii/bootstrap/Nav.php @@ -128,8 +128,10 @@ class Nav extends Widget $this->addCssClass($urlOptions, 'dropdown-toggle'); $label .= ' ' . Html::tag('b', '', array('class' => 'caret')); if (is_array($dropdown)) { - $dropdown['clientOptions'] = false; - $dropdown = Dropdown::widget($dropdown); + $dropdown = Dropdown::widget(array( + 'items' => $dropdown, + 'clientOptions' => false, + )); } } From 8a25953f1c5405621f66eb17b2562a999dea5834 Mon Sep 17 00:00:00 2001 From: Tobias Munk <schmunk@usrbin.de> Date: Mon, 3 Jun 2013 18:22:59 +0200 Subject: [PATCH 093/107] updated docs added installation from dev repo section --- apps/advanced/README.md | 11 +++++++++++ apps/basic/README.md | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/apps/advanced/README.md b/apps/advanced/README.md index c443c90..cca2451 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -79,6 +79,17 @@ php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-a This is not currently available. We will provide it when Yii 2 is formally released. +### Install from development repository + +If you've cloned the [Yii 2 framework main development reop](https://github.com/yiisoft/yii2) you +can bootstrap your application with: + +~~~ +cd yii2/apps/advanced +php composer.phar create-project +~~~ + + GETTING STARTED --------------- diff --git a/apps/basic/README.md b/apps/basic/README.md index 5300448..ebe2bfe 100644 --- a/apps/basic/README.md +++ b/apps/basic/README.md @@ -59,3 +59,14 @@ assuming `yii-basic` is directly under the document root of your Web server. ### Install from an Archive File This is not currently available. We will provide it when Yii 2 is formally released. + + +### Install from development repository + +If you've cloned the [Yii 2 framework main development reop](https://github.com/yiisoft/yii2) you +can bootstrap your application with: + +~~~ +cd yii2/apps/basic +php composer.phar create-project +~~~ \ No newline at end of file From a2bfdcac187bf6cf3ab3c05d0b62bd471d6a9d99 Mon Sep 17 00:00:00 2001 From: Tobias Munk <schmunk@usrbin.de> Date: Mon, 3 Jun 2013 18:22:59 +0200 Subject: [PATCH 094/107] updated docs added installation from dev repo section --- apps/advanced/README.md | 15 +++++++++++++++ apps/basic/README.md | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/apps/advanced/README.md b/apps/advanced/README.md index c443c90..f021f81 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -79,6 +79,21 @@ php composer.phar create-project --stability=dev yiisoft/yii2-app-advanced yii-a This is not currently available. We will provide it when Yii 2 is formally released. +### Install from development repository + +If you've cloned the [Yii 2 framework main development reop](https://github.com/yiisoft/yii2) you +can bootstrap your application with: + +~~~ +cd yii2/apps/advanced +php composer.phar create-project +~~~ + +*Note: If the above command fails with `[RuntimeException] Not enough arguments.` run +`php composer.phar self-update` to obtain an updated version of composer which supports creating projects +from local packages.* + + GETTING STARTED --------------- diff --git a/apps/basic/README.md b/apps/basic/README.md index 5300448..f6f7f45 100644 --- a/apps/basic/README.md +++ b/apps/basic/README.md @@ -59,3 +59,18 @@ assuming `yii-basic` is directly under the document root of your Web server. ### Install from an Archive File This is not currently available. We will provide it when Yii 2 is formally released. + + +### Install from development repository + +If you've cloned the [Yii 2 framework main development reop](https://github.com/yiisoft/yii2) you +can bootstrap your application with: + +~~~ +cd yii2/apps/basic +php composer.phar create-project +~~~ + +*Note: If the above command fails with `[RuntimeException] Not enough arguments.` run +`php composer.phar self-update` to obtain an updated version of composer which supports creating projects +from local packages.* \ No newline at end of file From 6c179ef3a7fc0dfc787dfc5318ed554cd5124791 Mon Sep 17 00:00:00 2001 From: Klimov Paul <klimov.paul@gmail.com> Date: Mon, 3 Jun 2013 20:47:37 +0300 Subject: [PATCH 095/107] File creation checks at "AssetController" have been updated to use Exceptions. --- .../yii/console/controllers/AssetController.php | 25 +++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index 8e3de29..0d09570 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -159,7 +159,7 @@ class AssetController extends Controller } } - $this->getAssetManager(); // check asset manager configuration + $this->getAssetManager(); // check asset manager configuration is correct } /** @@ -420,7 +420,7 @@ class AssetController extends Controller } $array = var_export($array, true); $version = date('Y-m-d H:i:s', time()); - $bytesWritten = file_put_contents($bundleFile, <<<EOD + $bundleFileContent = <<<EOD <?php /** * This file is generated by the "yii script" command. @@ -428,9 +428,8 @@ class AssetController extends Controller * @version $version */ return $array; -EOD - ); - if ($bytesWritten <= 0) { +EOD; + if (!file_put_contents($bundleFile, $bundleFileContent)) { throw new Exception("Unable to write output bundle configuration at '{$bundleFile}'."); } echo "Output bundle configuration created at '{$bundleFile}'.\n"; @@ -498,6 +497,7 @@ EOD * Combines Java Script files into a single one. * @param array $inputFiles source file names. * @param string $outputFile output file name. + * @throws \yii\console\Exception on failure. */ public function combineJsFiles($inputFiles, $outputFile) { @@ -507,13 +507,16 @@ EOD . file_get_contents($file) . "/*** END FILE: $file ***/\n"; } - file_put_contents($outputFile, $content); + if (!file_put_contents($outputFile, $content)) { + throw new Exception("Unable to write output Java Script file '{$outputFile}'."); + } } /** * Combines CSS files into a single one. * @param array $inputFiles source file names. * @param string $outputFile output file name. + * @throws \yii\console\Exception on failure. */ public function combineCssFiles($inputFiles, $outputFile) { @@ -523,7 +526,9 @@ EOD . $this->adjustCssUrl(file_get_contents($file), dirname($file), dirname($outputFile)) . "/*** END FILE: $file ***/\n"; } - file_put_contents($outputFile, $content); + if (!file_put_contents($outputFile, $content)) { + throw new Exception("Unable to write output CSS file '{$outputFile}'."); + } } /** @@ -590,6 +595,7 @@ EOD /** * Creates template of configuration file for [[actionCompress]]. * @param string $configFile output file name. + * @throws \yii\console\Exception on failure. */ public function actionTemplate($configFile) { @@ -622,9 +628,8 @@ EOD; return; } } - $bytesWritten = file_put_contents($configFile, $template); - if ($bytesWritten<=0) { - echo "Error: unable to write file '{$configFile}'!\n\n"; + if (!file_put_contents($configFile, $template)) { + throw new Exception("Unable to write template file '{$configFile}'."); } else { echo "Configuration file template created at '{$configFile}'.\n\n"; } From 5172dbd4c4748f2913b08768e651522499bc5ccb Mon Sep 17 00:00:00 2001 From: Klimov Paul <klimov.paul@gmail.com> Date: Mon, 3 Jun 2013 21:25:04 +0300 Subject: [PATCH 096/107] Additional comments have been added to the "AssetController::actionTemplate" output. --- framework/yii/console/controllers/AssetController.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index 0d09570..d794b30 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -601,13 +601,17 @@ EOD; { $template = <<<EOD <?php - +/** + * Configuration file for the "yii asset" console command. + * Note: in the console environment some path aliases like "@wwwroot" and "@www" may not exist, + * so corresponding paths should be specified directly. + */ return array( - // + // The list of asset bundles, which files should be included to compression: 'bundles' => require('path/to/bundles.php'), - // + // The list of extensions, which asset files should be included to compression: 'extensions' => require('path/to/namespaces.php'), - // + // Compression result asset bundle: 'targets' => array( 'all' => array( 'basePath' => __DIR__, @@ -616,7 +620,7 @@ return array( 'css' => 'all-{ts}.css', ), ), - + // Asset manager configuration: 'assetManager' => array( 'basePath' => __DIR__, 'baseUrl' => '/test', From 9b8b285eb1b3b639324994f72c6c64d47a5aa09e Mon Sep 17 00:00:00 2001 From: Klimov Paul <klimov.paul@gmail.com> Date: Mon, 3 Jun 2013 21:43:54 +0300 Subject: [PATCH 097/107] Doc comments at "AssetController" have been extended. --- framework/yii/console/controllers/AssetController.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index d794b30..dab7e90 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -14,6 +14,20 @@ use yii\console\Controller; /** * This command allows you to combine and compress your JavaScript and CSS files. * + * Usage: + * 1. Create a configuration file using 'template' action: + * yii asset/template /path/to/myapp/config.php + * 2. Edit the created config file, adjusting it for your web application needs. + * 3. Run the 'compress' action, using created config: + * yii asset /path/to/myapp/config.php /path/to/myapp/config/assets_compressed.php + * 4. Adjust your web application config to use compressed assets. + * + * Note: in the console environment some path aliases like '@wwwroot' and '@www' may not exist, + * so corresponding paths inside the configuration should be specified directly. + * + * Note: by default this command relies on an external tools to perform actual files compression, + * check [[jsCompressor]] and [[cssCompressor]] for more details. + * * @property array|\yii\web\AssetManager $assetManager asset manager, which will be used for assets processing. * * @author Qiang Xue <qiang.xue@gmail.com> From 1f68becb61767fb83cc1310404572ff99d244b3e Mon Sep 17 00:00:00 2001 From: Klimov Paul <klimov.paul@gmail.com> Date: Mon, 3 Jun 2013 21:46:56 +0300 Subject: [PATCH 098/107] Doc comments at "AssetController" have been updated. --- framework/yii/console/controllers/AssetController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index dab7e90..61e54e9 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -57,7 +57,7 @@ class AssetController extends Controller * ~~~ * 'all' => array( * 'css' => 'all.css', - * 'js' => 'js.css', + * 'js' => 'all.js', * 'depends' => array( ... ), * ) * ~~~ @@ -322,7 +322,7 @@ class AssetController extends Controller /** * Builds output asset bundle. * @param \yii\web\AssetBundle $target output asset bundle - * @param string $type either "js" or "css". + * @param string $type either 'js' or 'css'. * @param \yii\web\AssetBundle[] $bundles source asset bundles. * @param integer $timestamp current timestamp. * @throws Exception on failure. @@ -617,7 +617,7 @@ EOD; <?php /** * Configuration file for the "yii asset" console command. - * Note: in the console environment some path aliases like "@wwwroot" and "@www" may not exist, + * Note: in the console environment some path aliases like '@wwwroot' and '@www' may not exist, * so corresponding paths should be specified directly. */ return array( From dfe2d00785d0bc86a1ac92a8f9bbb7a7f8aaea4f Mon Sep 17 00:00:00 2001 From: Klimov Paul <klimov.paul@gmail.com> Date: Mon, 3 Jun 2013 21:53:07 +0300 Subject: [PATCH 099/107] Output of "AssetController::actionCompress" have been adjusted to hold the action id. --- framework/yii/console/controllers/AssetController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index 61e54e9..e525ef1 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -437,11 +437,11 @@ class AssetController extends Controller $bundleFileContent = <<<EOD <?php /** - * This file is generated by the "yii script" command. + * This file is generated by the "yii {$this->id}" command. * DO NOT MODIFY THIS FILE DIRECTLY. - * @version $version + * @version {$version} */ -return $array; +return {$array}; EOD; if (!file_put_contents($bundleFile, $bundleFileContent)) { throw new Exception("Unable to write output bundle configuration at '{$bundleFile}'."); From ec8fecb8b079b3eec4a66df67e91cc3412fc77e5 Mon Sep 17 00:00:00 2001 From: Klimov Paul <klimov.paul@gmail.com> Date: Mon, 3 Jun 2013 21:56:03 +0300 Subject: [PATCH 100/107] Check output bundle file format has been added to "AssetControllerTest::testActionCompress()" unit test. --- tests/unit/framework/console/controllers/AssetControllerTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/framework/console/controllers/AssetControllerTest.php b/tests/unit/framework/console/controllers/AssetControllerTest.php index db6d2a7..9d7dd28 100644 --- a/tests/unit/framework/console/controllers/AssetControllerTest.php +++ b/tests/unit/framework/console/controllers/AssetControllerTest.php @@ -239,6 +239,7 @@ class AssetControllerTest extends TestCase // Then : $this->assertTrue(file_exists($bundleFile), 'Unable to create output bundle file!'); + $this->assertTrue(is_array(require($bundleFile)), 'Output bundle file has incorrect format!'); $compressedCssFileName = $this->testAssetsBasePath . DIRECTORY_SEPARATOR . 'all.css'; $this->assertTrue(file_exists($compressedCssFileName), 'Unable to compress CSS files!'); From 152e5a0e15f1f6edf9106adf61dbaa1e0423d366 Mon Sep 17 00:00:00 2001 From: Klimov Paul <klimov.paul@gmail.com> Date: Mon, 3 Jun 2013 22:34:15 +0300 Subject: [PATCH 101/107] Doc comments and descriptive output at"AssetController" has been adjusted. --- framework/yii/console/controllers/AssetController.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php index e525ef1..4c759de 100644 --- a/framework/yii/console/controllers/AssetController.php +++ b/framework/yii/console/controllers/AssetController.php @@ -71,7 +71,7 @@ class AssetController extends Controller */ private $_assetManager = array(); /** - * @var string|callback Java Script file compressor. + * @var string|callback JavaScript file compressor. * If a string, it is treated as shell command template, which should contain * placeholders {from} - source file name - and {to} - output file name. * Otherwise, it is treated as PHP callback, which should perform the compression. @@ -173,7 +173,7 @@ class AssetController extends Controller } } - $this->getAssetManager(); // check asset manager configuration is correct + $this->getAssetManager(); // check if asset manager configuration is correct } /** @@ -450,7 +450,7 @@ EOD; } /** - * Compresses given Java Script files and combines them into the single one. + * Compresses given JavaScript files and combines them into the single one. * @param array $inputFiles list of source file names. * @param string $outputFile output file name. * @throws \yii\console\Exception on failure @@ -508,7 +508,7 @@ EOD; } /** - * Combines Java Script files into a single one. + * Combines JavaScript files into a single one. * @param array $inputFiles source file names. * @param string $outputFile output file name. * @throws \yii\console\Exception on failure. @@ -522,7 +522,7 @@ EOD; . "/*** END FILE: $file ***/\n"; } if (!file_put_contents($outputFile, $content)) { - throw new Exception("Unable to write output Java Script file '{$outputFile}'."); + throw new Exception("Unable to write output JavaScript file '{$outputFile}'."); } } @@ -621,11 +621,11 @@ EOD; * so corresponding paths should be specified directly. */ return array( - // The list of asset bundles, which files should be included to compression: + // The list of asset bundles to compress: 'bundles' => require('path/to/bundles.php'), - // The list of extensions, which asset files should be included to compression: + // The list of extensions to compress: 'extensions' => require('path/to/namespaces.php'), - // Compression result asset bundle: + // Asset bundle for compression output: 'targets' => array( 'all' => array( 'basePath' => __DIR__, From 084d709e44f8e0b4985e52ac3aa1f4db319d849e Mon Sep 17 00:00:00 2001 From: Alexander Makarov <sam@rmcreative.ru> Date: Mon, 3 Jun 2013 23:39:08 +0400 Subject: [PATCH 102/107] fixed typo in app readmes --- apps/advanced/README.md | 2 +- apps/basic/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/advanced/README.md b/apps/advanced/README.md index f021f81..d8c1e17 100644 --- a/apps/advanced/README.md +++ b/apps/advanced/README.md @@ -81,7 +81,7 @@ This is not currently available. We will provide it when Yii 2 is formally relea ### Install from development repository -If you've cloned the [Yii 2 framework main development reop](https://github.com/yiisoft/yii2) you +If you've cloned the [Yii 2 framework main development repository](https://github.com/yiisoft/yii2) you can bootstrap your application with: ~~~ diff --git a/apps/basic/README.md b/apps/basic/README.md index c7f6abe..2f8f1e8 100644 --- a/apps/basic/README.md +++ b/apps/basic/README.md @@ -63,7 +63,7 @@ This is not currently available. We will provide it when Yii 2 is formally relea ### Install from development repository -If you've cloned the [Yii 2 framework main development reop](https://github.com/yiisoft/yii2) you +If you've cloned the [Yii 2 framework main development repository](https://github.com/yiisoft/yii2) you can bootstrap your application with: ~~~ From 5ecb5e3bd0617b0c291d9ce3b3c59eebd6c3704f Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Tue, 4 Jun 2013 17:08:26 -0400 Subject: [PATCH 103/107] Fixed Formatter doc. --- framework/yii/i18n/Formatter.php | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php index d688a15..4a93397 100644 --- a/framework/yii/i18n/Formatter.php +++ b/framework/yii/i18n/Formatter.php @@ -30,19 +30,28 @@ class Formatter extends \yii\base\Formatter */ public $locale; /** - * @var string the default format string to be used to format a date using PHP date() function. + * @var string the default format string to be used to format a date. + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). */ public $dateFormat = 'short'; /** - * @var string the default format string to be used to format a time using PHP date() function. + * @var string the default format string to be used to format a time. + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). */ public $timeFormat = 'short'; /** - * @var string the default format string to be used to format a date and time using PHP date() function. + * @var string the default format string to be used to format a date and time. + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). */ public $datetimeFormat = 'short'; /** * @var array the options to be set for the NumberFormatter objects. Please refer to + * [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute) + * for the possible options. This property is used by [[createNumberFormatter]] when + * creating a new number formatter to format decimals, currencies, etc. */ public $numberFormatOptions = array(); @@ -81,8 +90,11 @@ class Formatter extends \yii\base\Formatter * - a PHP DateTime object * * @param string $format the format used to convert the value into a date string. - * If null, [[dateFormat]] will be used. The format string should be the one - * that can be recognized by the PHP `date()` function. + * If null, [[dateFormat]] will be used. + * + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). + * * @return string the formatted result * @see dateFormat */ @@ -111,8 +123,11 @@ class Formatter extends \yii\base\Formatter * - a PHP DateTime object * * @param string $format the format used to convert the value into a date string. - * If null, [[dateFormat]] will be used. The format string should be the one - * that can be recognized by the PHP `date()` function. + * If null, [[dateFormat]] will be used. + * + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). + * * @return string the formatted result * @see timeFormat */ @@ -141,8 +156,11 @@ class Formatter extends \yii\base\Formatter * - a PHP DateTime object * * @param string $format the format used to convert the value into a date string. - * If null, [[dateFormat]] will be used. The format string should be the one - * that can be recognized by the PHP `date()` function. + * If null, [[dateFormat]] will be used. + * + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). + * * @return string the formatted result * @see datetimeFormat */ @@ -213,7 +231,7 @@ class Formatter extends \yii\base\Formatter /** * Creates a number formatter based on the given type and format. * @param integer $type the type of the number formatter - * @param string $format the format to be used + * @param string $format the format to be used. Please refer to [ICU manual](http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details) * @return NumberFormatter the created formatter instance */ protected function createNumberFormatter($type, $format) From cbf642d93147442daffdd5eb4e8b184480ab1313 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Tue, 4 Jun 2013 17:21:40 -0400 Subject: [PATCH 104/107] Added support for locale-dependent decimal and grouping separators. --- framework/yii/base/Formatter.php | 18 +++++++++++++----- framework/yii/i18n/Formatter.php | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php index d15e5f2..e62039e 100644 --- a/framework/yii/base/Formatter.php +++ b/framework/yii/base/Formatter.php @@ -39,17 +39,19 @@ class Formatter extends Component public $datetimeFormat = 'Y/m/d h:i:s A'; /** * @var array the text to be displayed when formatting a boolean value. The first element corresponds - * to the text display for false, the second element for true. Defaults to <code>array('No', 'Yes')</code>. + * to the text display for false, the second element for true. Defaults to `array('No', 'Yes')`. */ public $booleanFormat; /** * @var string the character displayed as the decimal point when formatting a number. + * If not set, "." will be used. */ - public $decimalSeparator = '.'; + public $decimalSeparator; /** * @var string the character displayed as the thousands separator character when formatting a number. + * If not set, "," will be used. */ - public $thousandSeparator = ','; + public $thousandSeparator; /** @@ -273,7 +275,11 @@ class Formatter extends Component */ public function asDouble($value, $decimals = 2) { - return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value)); + if ($this->decimalSeparator === null) { + return sprintf("%.{$decimals}f", $value); + } else { + return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value)); + } } /** @@ -287,6 +293,8 @@ class Formatter extends Component */ public function asNumber($value, $decimals = 0) { - return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator); + $ds = isset($this->decimalSeparator) ? $this->decimalSeparator: '.'; + $ts = isset($this->thousandSeparator) ? $this->thousandSeparator: ','; + return number_format($value, $decimals, $ds, $ts); } } diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php index 4a93397..8fa29dd 100644 --- a/framework/yii/i18n/Formatter.php +++ b/framework/yii/i18n/Formatter.php @@ -54,6 +54,16 @@ class Formatter extends \yii\base\Formatter * creating a new number formatter to format decimals, currencies, etc. */ public $numberFormatOptions = array(); + /** + * @var string the character displayed as the decimal point when formatting a number. + * If not set, the decimal separator corresponding to [[locale]] will be used. + */ + public $decimalSeparator; + /** + * @var string the character displayed as the thousands separator character when formatting a number. + * If not set, the thousand separator corresponding to [[locale]] will be used. + */ + public $thousandSeparator; /** @@ -70,6 +80,16 @@ class Formatter extends \yii\base\Formatter if ($this->locale === null) { $this->locale = Yii::$app->language; } + if ($this->decimalSeparator === null || $this->thousandSeparator === null) { + $formatter = new NumberFormatter($this->locale, NumberFormatter::DECIMAL); + if ($this->decimalSeparator === null) { + $this->decimalSeparator = $formatter->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL); + } + if ($this->thousandSeparator === null) { + $this->thousandSeparator = $formatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); + } + } + parent::init(); } From 23767304527a989c2e664f2522c0acacc857b615 Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Tue, 4 Jun 2013 18:11:50 -0400 Subject: [PATCH 105/107] better check of existence of tables. --- framework/yii/db/ActiveRecord.php | 8 +++++++- framework/yii/db/mysql/Schema.php | 8 ++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php index 58411f0..6faebbf 100644 --- a/framework/yii/db/ActiveRecord.php +++ b/framework/yii/db/ActiveRecord.php @@ -275,10 +275,16 @@ class ActiveRecord extends Model /** * Returns the schema information of the DB table associated with this AR class. * @return TableSchema the schema information of the DB table associated with this AR class. + * @throws InvalidConfigException if the table for the AR class does not exist. */ public static function getTableSchema() { - return static::getDb()->getTableSchema(static::tableName()); + $schema = static::getDb()->getTableSchema(static::tableName()); + if ($schema !== null) { + return $schema; + } else { + throw new InvalidConfigException("The table does not exist: " . static::tableName()); + } } /** diff --git a/framework/yii/db/mysql/Schema.php b/framework/yii/db/mysql/Schema.php index 501149a..e7b34e0 100644 --- a/framework/yii/db/mysql/Schema.php +++ b/framework/yii/db/mysql/Schema.php @@ -181,12 +181,12 @@ class Schema extends \yii\db\Schema */ protected function findColumns($table) { - $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name); - try { - $columns = $this->db->createCommand($sql)->queryAll(); - } catch (\Exception $e) { + $rows = $this->db->createCommand("SHOW TABLES LIKE " . $this->quoteValue($table->name))->queryAll(); + if (count($rows) === 0) { return false; } + $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name); + $columns = $this->db->createCommand($sql)->queryAll(); foreach ($columns as $info) { $column = $this->loadColumnSchema($info); $table->columns[$column->name] = $column; From b311b6ecf3b0168024dbe0241faa6c5dea9184fd Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Tue, 4 Jun 2013 18:13:32 -0400 Subject: [PATCH 106/107] typo fix. --- framework/yii/i18n/Formatter.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/yii/i18n/Formatter.php b/framework/yii/i18n/Formatter.php index 8fa29dd..948e277 100644 --- a/framework/yii/i18n/Formatter.php +++ b/framework/yii/i18n/Formatter.php @@ -83,10 +83,10 @@ class Formatter extends \yii\base\Formatter if ($this->decimalSeparator === null || $this->thousandSeparator === null) { $formatter = new NumberFormatter($this->locale, NumberFormatter::DECIMAL); if ($this->decimalSeparator === null) { - $this->decimalSeparator = $formatter->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL); + $this->decimalSeparator = $formatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); } if ($this->thousandSeparator === null) { - $this->thousandSeparator = $formatter->getSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); + $this->thousandSeparator = $formatter->getSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL); } } From 9d6f11d5d524afeacd9cb702612d8667c15a6e3c Mon Sep 17 00:00:00 2001 From: Qiang Xue <qiang.xue@gmail.com> Date: Tue, 4 Jun 2013 19:34:51 -0400 Subject: [PATCH 107/107] new way of detecting if table exists. --- framework/yii/db/mysql/Schema.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/framework/yii/db/mysql/Schema.php b/framework/yii/db/mysql/Schema.php index e7b34e0..b42ef15 100644 --- a/framework/yii/db/mysql/Schema.php +++ b/framework/yii/db/mysql/Schema.php @@ -178,15 +178,21 @@ class Schema extends \yii\db\Schema * Collects the metadata of table columns. * @param TableSchema $table the table metadata * @return boolean whether the table exists in the database + * @throws \Exception if DB query fails */ protected function findColumns($table) { - $rows = $this->db->createCommand("SHOW TABLES LIKE " . $this->quoteValue($table->name))->queryAll(); - if (count($rows) === 0) { - return false; - } $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name); - $columns = $this->db->createCommand($sql)->queryAll(); + try { + $columns = $this->db->createCommand($sql)->queryAll(); + } catch (\Exception $e) { + $previous = $e->getPrevious(); + if ($previous instanceof \PDOException && $previous->getCode() == '42S02') { + // table does not exist + return false; + } + throw $e; + } foreach ($columns as $info) { $column = $this->loadColumnSchema($info); $table->columns[$column->name] = $column;