From 54c539dd5183aac90910973413f0d8f753d49fda Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Thu, 7 Feb 2013 15:46:15 -0500
Subject: [PATCH 01/84] Fixed buildInCondition bug.
---
framework/db/QueryBuilder.php | 26 ++++++++++++--------------
1 file changed, 12 insertions(+), 14 deletions(-)
diff --git a/framework/db/QueryBuilder.php b/framework/db/QueryBuilder.php
index ebca888..67dcbb0 100644
--- a/framework/db/QueryBuilder.php
+++ b/framework/db/QueryBuilder.php
@@ -592,21 +592,19 @@ class QueryBuilder extends \yii\base\Object
return $operator === 'IN' ? '0=1' : '';
}
- if (is_array($column)) {
- if (count($column) > 1) {
- return $this->buildCompositeInCondition($operator, $column, $values);
+ if (count($column) > 1) {
+ return $this->buildCompositeInCondition($operator, $column, $values);
+ } elseif (is_array($column)) {
+ $column = reset($column);
+ }
+ foreach ($values as $i => $value) {
+ if (is_array($value)) {
+ $value = isset($value[$column]) ? $value[$column] : null;
+ }
+ if ($value === null) {
+ $values[$i] = 'NULL';
} else {
- $column = reset($column);
- foreach ($values as $i => $value) {
- if (is_array($value)) {
- $value = isset($value[$column]) ? $value[$column] : null;
- }
- if ($value === null) {
- $values[$i] = 'NULL';
- } else {
- $values[$i] = is_string($value) ? $this->db->quoteValue($value) : (string)$value;
- }
- }
+ $values[$i] = is_string($value) ? $this->db->quoteValue($value) : (string)$value;
}
}
if (strpos($column, '(') === false) {
From 80e29130dc71665768fd67bf5cec148a8dc3482c Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Fri, 8 Feb 2013 01:37:07 +0400
Subject: [PATCH 02/84] more todos
---
todo.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/todo.md b/todo.md
index 7249235..6282ec5 100644
--- a/todo.md
+++ b/todo.md
@@ -6,14 +6,15 @@
* key-value-based (should allow storage-specific methods additionally to generic ones)
* redis (put it under framework/db/redis or perhaps framework/caching?)
- base
- * TwigViewRenderer
- * SmartyViewRenderer
+ * TwigViewRenderer (Alex)
+ * SmartyViewRenderer (Alex)
- logging
* WebTarget (TBD after web is in place): should consider using javascript and make it into a toolbar
* ProfileTarget (TBD after web is in place): should consider using javascript and make it into a toolbar
* unit tests
- caching
* backend-specific unit tests
+ * dependency unit tests
- validators
* FileValidator: depends on CUploadedFile
* CaptchaValidator: depends on CaptchaAction
@@ -27,6 +28,7 @@
- Module should be able to define its own configuration including routes. Application should be able to overwrite it.
* application
* security
+ - backport 1.1 changes
- built-in console commands
+ api doc builder
* support for markdown syntax
From 45020ebc72370b5611f35b5800578ccd1f6e8825 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Fri, 8 Feb 2013 03:23:48 +0400
Subject: [PATCH 03/84] autoloader draft docs
---
docs/autoloader.md | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
create mode 100644 docs/autoloader.md
diff --git a/docs/autoloader.md b/docs/autoloader.md
new file mode 100644
index 0000000..b7696d7
--- /dev/null
+++ b/docs/autoloader.md
@@ -0,0 +1,19 @@
+Yii2 class loader
+=================
+
+Yii 2 class loader is PSR-0 compliant. That means it can handle most of the PHP
+libraries and frameworks out there.
+
+In order to autoload a library you need to set a root alias for it.
+
+PEAR-style libraries
+--------------------
+
+```php
+\Yii::setAlias('@Twig', '@app/vendors/Twig');
+```
+
+References
+----------
+
+- YiiBase::autoload
\ No newline at end of file
From 379fddfec35f3045a55211264d0b73afc6b9a675 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Fri, 8 Feb 2013 08:41:22 -0500
Subject: [PATCH 04/84] URL manager WIP
---
framework/web/UrlManager.php | 5 +++
framework/web/UrlRule.php | 95 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 100 insertions(+)
create mode 100644 framework/web/UrlRule.php
diff --git a/framework/web/UrlManager.php b/framework/web/UrlManager.php
index e890740..1c80938 100644
--- a/framework/web/UrlManager.php
+++ b/framework/web/UrlManager.php
@@ -49,4 +49,9 @@ class UrlManager extends Component
else
return '';
}
+
+ public function createUrl($route, $params = array(), $ampersand = '&')
+ {
+
+ }
}
diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php
new file mode 100644
index 0000000..096c9ba
--- /dev/null
+++ b/framework/web/UrlRule.php
@@ -0,0 +1,95 @@
+
+ * @since 2.0
+ */
+class UrlRule extends Object
+{
+ /**
+ * @var string the URL suffix used for this rule.
+ * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
+ * Defaults to null, meaning using the value of {@link CUrlManager::urlSuffix}.
+ */
+ public $urlSuffix;
+ /**
+ * @var boolean whether the rule is case sensitive. Defaults to null, meaning
+ * using the value of {@link CUrlManager::caseSensitive}.
+ */
+ public $caseSensitive;
+ /**
+ * @var array the default GET parameters (name=>value) that this rule provides.
+ * When this rule is used to parse the incoming request, the values declared in this property
+ * will be injected into $_GET.
+ */
+ public $defaultParams = array();
+ /**
+ * @var boolean whether the GET parameter values should match the corresponding
+ * sub-patterns in the rule when creating a URL. Defaults to null, meaning using the value
+ * of {@link CUrlManager::matchValue}. When this property is false, it means
+ * a rule will be used for creating a URL if its route and parameter names match the given ones.
+ * If this property is set true, then the given parameter values must also match the corresponding
+ * parameter sub-patterns. Note that setting this property to true will degrade performance.
+ * @since 1.1.0
+ */
+ public $matchValue;
+ /**
+ * @var string the HTTP verb (e.g. GET, POST, DELETE) that this rule should match.
+ * If this rule can match multiple verbs, please separate them with commas.
+ * If this property is not set, the rule can match any verb.
+ * Note that this property is only used when parsing a request. It is ignored for URL creation.
+ * @since 1.1.7
+ */
+ public $verb;
+ /**
+ * @var boolean whether this rule is only used for request parsing.
+ * Defaults to false, meaning the rule is used for both URL parsing and creation.
+ * @since 1.1.7
+ */
+ public $parsingOnly = false;
+ /**
+ * @var string the controller/action pair
+ */
+ public $route;
+ /**
+ * @var array the mapping from route param name to token name (e.g. _r1=><1>)
+ */
+ public $references = array();
+ /**
+ * @var string the pattern used to match route
+ */
+ public $routePattern;
+ /**
+ * @var string regular expression used to parse a URL
+ */
+ public $pattern;
+ /**
+ * @var string template used to construct a URL
+ */
+ public $template;
+ /**
+ * @var array list of parameters (name=>regular expression)
+ */
+ public $params = array();
+ /**
+ * @var boolean whether the URL allows additional parameters at the end of the path info.
+ */
+ public $append;
+ /**
+ * @var boolean whether host info should be considered for this rule
+ */
+ public $hasHostInfo;
+}
From ab42ab2d52c96661d802a0e01b43b6988e0da10a Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Fri, 8 Feb 2013 16:32:43 -0500
Subject: [PATCH 05/84] url wip
---
framework/web/UrlManager.php | 68 +++++++++++++++++++++++++++++++++--
framework/web/UrlRule.php | 84 +++++++++++---------------------------------
2 files changed, 87 insertions(+), 65 deletions(-)
diff --git a/framework/web/UrlManager.php b/framework/web/UrlManager.php
index 1c80938..4b9a606 100644
--- a/framework/web/UrlManager.php
+++ b/framework/web/UrlManager.php
@@ -19,7 +19,70 @@ use \yii\base\Component;
*/
class UrlManager extends Component
{
+ /**
+ * @var array the URL rules (pattern=>route).
+ */
+ public $rules = array();
+ /**
+ * @var string the URL suffix used when in 'path' format.
+ * For example, ".html" can be used so that the URL looks like pointing to a static HTML page. Defaults to empty.
+ */
+ public $urlSuffix = '';
+ /**
+ * @var boolean whether to show entry script name in the constructed URL. Defaults to true.
+ */
+ public $showScriptName = true;
+ /**
+ * @var boolean whether to append GET parameters to the path info part. Defaults to true.
+ * This property is only effective when {@link urlFormat} is 'path' and is mainly used when
+ * creating URLs. When it is true, GET parameters will be appended to the path info and
+ * separate from each other using slashes. If this is false, GET parameters will be in query part.
+ */
+ public $appendParams = true;
+ /**
+ * @var string the GET variable name for route. Defaults to 'r'.
+ */
public $routeVar = 'r';
+ /**
+ * @var boolean whether routes are case-sensitive. Defaults to true. By setting this to false,
+ * the route in the incoming request will be turned to lower case first before further processing.
+ * As a result, you should follow the convention that you use lower case when specifying
+ * controller mapping ({@link CWebApplication::controllerMap}) and action mapping
+ * ({@link CController::actions}). Also, the directory names for organizing controllers should
+ * be in lower case.
+ */
+ public $caseSensitive = true;
+ /**
+ * @var boolean whether the GET parameter values should match the corresponding
+ * sub-patterns in a rule before using it to create a URL. Defaults to false, meaning
+ * a rule will be used for creating a URL only if its route and parameter names match the given ones.
+ * If this property is set true, then the given parameter values must also match the corresponding
+ * parameter sub-patterns. Note that setting this property to true will degrade performance.
+ * @since 1.1.0
+ */
+ public $matchValue = false;
+ /**
+ * @var string the ID of the cache application component that is used to cache the parsed URL rules.
+ * Defaults to 'cache' which refers to the primary cache application component.
+ * Set this property to false if you want to disable caching URL rules.
+ */
+ public $cacheID = 'cache';
+ /**
+ * @var boolean whether to enable strict URL parsing.
+ * This property is only effective when {@link urlFormat} is 'path'.
+ * If it is set true, then an incoming URL must match one of the {@link rules URL rules}.
+ * Otherwise, it will be treated as an invalid request and trigger a 404 HTTP exception.
+ * Defaults to false.
+ */
+ public $useStrictParsing = false;
+ /**
+ * @var string the class name or path alias for the URL rule instances. Defaults to 'CUrlRule'.
+ * If you change this to something else, please make sure that the new class must extend from
+ * {@link CBaseUrlRule} and have the same constructor signature as {@link CUrlRule}.
+ * It must also be serializable and autoloadable.
+ * @since 1.1.8
+ */
+ public $urlRuleClass = 'CUrlRule';
/**
* Initializes the application component.
@@ -44,10 +107,11 @@ class UrlManager extends Component
*/
public function parseUrl($request)
{
- if(isset($_GET[$this->routeVar]))
+ if (isset($_GET[$this->routeVar])) {
return $_GET[$this->routeVar];
- else
+ } else {
return '';
+ }
}
public function createUrl($route, $params = array(), $ampersand = '&')
diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php
index 096c9ba..1a4e77e 100644
--- a/framework/web/UrlRule.php
+++ b/framework/web/UrlRule.php
@@ -13,6 +13,11 @@ use yii\base\Object;
/**
* UrlManager manages the URLs of Yii applications.
+ * array(
+ * 'pattern' => 'post/',
+ * 'route' => 'post/view',
+ * 'params' => array('id' => 1),
+ * )
*
* @author Qiang Xue
* @since 2.0
@@ -20,76 +25,29 @@ use yii\base\Object;
class UrlRule extends Object
{
/**
- * @var string the URL suffix used for this rule.
- * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
- * Defaults to null, meaning using the value of {@link CUrlManager::urlSuffix}.
- */
- public $urlSuffix;
- /**
- * @var boolean whether the rule is case sensitive. Defaults to null, meaning
- * using the value of {@link CUrlManager::caseSensitive}.
+ * @var string regular expression used to parse a URL
*/
- public $caseSensitive;
+ public $pattern;
/**
* @var array the default GET parameters (name=>value) that this rule provides.
* When this rule is used to parse the incoming request, the values declared in this property
* will be injected into $_GET.
*/
- public $defaultParams = array();
- /**
- * @var boolean whether the GET parameter values should match the corresponding
- * sub-patterns in the rule when creating a URL. Defaults to null, meaning using the value
- * of {@link CUrlManager::matchValue}. When this property is false, it means
- * a rule will be used for creating a URL if its route and parameter names match the given ones.
- * If this property is set true, then the given parameter values must also match the corresponding
- * parameter sub-patterns. Note that setting this property to true will degrade performance.
- * @since 1.1.0
- */
- public $matchValue;
- /**
- * @var string the HTTP verb (e.g. GET, POST, DELETE) that this rule should match.
- * If this rule can match multiple verbs, please separate them with commas.
- * If this property is not set, the rule can match any verb.
- * Note that this property is only used when parsing a request. It is ignored for URL creation.
- * @since 1.1.7
- */
- public $verb;
- /**
- * @var boolean whether this rule is only used for request parsing.
- * Defaults to false, meaning the rule is used for both URL parsing and creation.
- * @since 1.1.7
- */
- public $parsingOnly = false;
- /**
- * @var string the controller/action pair
- */
- public $route;
- /**
- * @var array the mapping from route param name to token name (e.g. _r1=><1>)
- */
- public $references = array();
- /**
- * @var string the pattern used to match route
- */
- public $routePattern;
- /**
- * @var string regular expression used to parse a URL
- */
- public $pattern;
- /**
- * @var string template used to construct a URL
- */
- public $template;
- /**
- * @var array list of parameters (name=>regular expression)
- */
public $params = array();
/**
- * @var boolean whether the URL allows additional parameters at the end of the path info.
- */
- public $append;
- /**
- * @var boolean whether host info should be considered for this rule
+ * @var string the route to the controller action
*/
- public $hasHostInfo;
+ public $route;
+
+ public function createUrl($route, $params, $ampersand)
+ {
+
+ }
+
+ public function parse($path)
+ {
+ $route = '';
+ $params = array();
+ return array($route, $params);
+ }
}
From 712f4dae0d4a8ba87b8a8f91d02281703d4f038f Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Sun, 10 Feb 2013 14:53:35 -0500
Subject: [PATCH 06/84] url wip
---
docs/api/db/ActiveRecord.md | 4 +-
framework/web/UrlManager.php | 38 ++-
framework/web/UrlRule.php | 130 +++++++-
tests/unit/framework/web/UrlManagerTest.php | 7 +
tests/unit/framework/web/UrlRuleTest.php | 441 ++++++++++++++++++++++++++++
5 files changed, 603 insertions(+), 17 deletions(-)
create mode 100644 tests/unit/framework/web/UrlManagerTest.php
create mode 100644 tests/unit/framework/web/UrlRuleTest.php
diff --git a/docs/api/db/ActiveRecord.md b/docs/api/db/ActiveRecord.md
index da281d8..822c548 100644
--- a/docs/api/db/ActiveRecord.md
+++ b/docs/api/db/ActiveRecord.md
@@ -300,7 +300,7 @@ foreach ($customers as $customer) {
~~~
How many SQL queries will be performed in the above code, assuming there are more than 100 customers in
-the database? 101! The first SQL query brings back 100 customers. Then for each customer, another SQL query
+the database? 101! The first SQL query brings back 100 customers. Then for each customer, a SQL query
is performed to bring back the customer's orders.
To solve the above performance problem, you can use the so-called *eager loading* by calling [[ActiveQuery::with()]]:
@@ -318,7 +318,7 @@ foreach ($customers as $customer) {
}
~~~
-As you can see, only two SQL queries were needed for the same task.
+As you can see, only two SQL queries are needed for the same task.
Sometimes, you may want to customize the relational queries on the fly. It can be
diff --git a/framework/web/UrlManager.php b/framework/web/UrlManager.php
index 4b9a606..e03f086 100644
--- a/framework/web/UrlManager.php
+++ b/framework/web/UrlManager.php
@@ -98,24 +98,50 @@ class UrlManager extends Component
*/
protected function processRules()
{
+ foreach ($this->rules as $i => $rule) {
+ if (!isset($rule['class'])) {
+ $rule['class'] = 'yii\web\UrlRule';
+ }
+ $this->rules[$i] = \Yii::createObject($rule);
+ }
}
/**
* Parses the user request.
- * @param HttpRequest $request the request application component
+ * @param Request $request the request application component
* @return string the route (controllerID/actionID) and perhaps GET parameters in path format.
*/
public function parseUrl($request)
{
- if (isset($_GET[$this->routeVar])) {
- return $_GET[$this->routeVar];
- } else {
- return '';
+ }
+
+ public function createUrl($route, $params = array())
+ {
+ $anchor = isset($params['#']) ? '#' . $params['#'] : '';
+ unset($anchor['#']);
+
+ /** @var $rule UrlRule */
+ foreach ($this->rules as $rule) {
+ if (($url = $rule->createUrl($route, $params)) !== false) {
+ return $this->getBaseUrl() . $url . $anchor;
+ }
}
+
+ if ($params !== array()) {
+ $route .= '?' . http_build_query($params);
+ }
+ return $this->getBaseUrl() . '/' . $route . $anchor;
}
- public function createUrl($route, $params = array(), $ampersand = '&')
+ private $_baseUrl;
+
+ public function getBaseUrl()
{
+ return $this->_baseUrl;
+ }
+ public function setBaseUrl($value)
+ {
+ $this->_baseUrl = trim($value, '/');
}
}
diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php
index 1a4e77e..2961275 100644
--- a/framework/web/UrlRule.php
+++ b/framework/web/UrlRule.php
@@ -14,9 +14,15 @@ use yii\base\Object;
/**
* UrlManager manages the URLs of Yii applications.
* array(
- * 'pattern' => 'post/',
+ * 'pattern' => 'post/',
* 'route' => 'post/view',
- * 'params' => array('id' => 1),
+ * 'defaults' => array('page' => 1),
+ * )
+ *
+ * array(
+ * 'pattern' => 'about',
+ * 'route' => 'site/page',
+ * 'defaults' => array('view' => 'about'),
* )
*
* @author Qiang Xue
@@ -29,22 +35,128 @@ class UrlRule extends Object
*/
public $pattern;
/**
+ * @var string the route to the controller action
+ */
+ public $route;
+ /**
* @var array the default GET parameters (name=>value) that this rule provides.
* When this rule is used to parse the incoming request, the values declared in this property
* will be injected into $_GET.
*/
- public $params = array();
- /**
- * @var string the route to the controller action
- */
- public $route;
+ public $defaults = array();
+
+ protected $paramRules = array();
+ protected $routeRule;
+ protected $template;
+ protected $routeParams = array();
+
+ public function init()
+ {
+ $this->pattern = trim($this->pattern, '/');
+ if ($this->pattern === '') {
+ $this->template = '';
+ $this->pattern = '#^$#u';
+ return;
+ } else {
+ $this->pattern = '/' . $this->pattern . '/';
+ }
+
+ $this->route = trim($this->route, '/');
+ if (strpos($this->route, '<') !== false && preg_match_all('/<(\w+)>/', $this->route, $matches)) {
+ foreach ($matches[1] as $name) {
+ $this->routeParams[$name] = "<$name>";
+ }
+ }
+
+ $tr = $tr2 = array();
+ if (preg_match_all('/<(\w+):?([^>]+)?>/', $this->pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
+ foreach ($matches as $match) {
+ $name = $match[1][0];
+ $pattern = isset($match[2][0]) ? $match[2][0] : '[^\/]+';
+ if (isset($this->defaults[$name])) {
+ $length = strlen($match[0][0]);
+ $offset = $match[0][1];
+ if ($this->pattern[$offset - 1] === '/' && $this->pattern[$offset + $length] === '/') {
+ $tr["<$name>"] = "(?P<$name>(?:/$pattern)?)";
+ } else {
+ $tr["<$name>"] = "(?P<$name>(?:$pattern)?)";
+ }
+ } else {
+ $tr["<$name>"] = "(?P<$name>$pattern)";
+ }
+ if (isset($this->routeParams[$name])) {
+ $tr2["<$name>"] = "(?P<$name>$pattern)";
+ } else {
+ $this->paramRules[$name] = $pattern === '[^\/]+' ? '' : "#^$pattern$#";
+ }
+ }
+ }
+
+ $this->template = preg_replace('/<(\w+):?([^>]+)?>/', '<$1>', $this->pattern);
+ $this->pattern = '#^' . strtr($this->template, $tr) . '$#u';
- public function createUrl($route, $params, $ampersand)
+ if ($this->routeParams !== array()) {
+ $this->routeRule = '#^' . strtr($this->route, $tr2) . '$#u';
+ }
+ }
+
+ public function parseUrl($pathInfo)
{
}
- public function parse($path)
+ public function createUrl($route, $params)
+ {
+ $tr = array();
+
+ // match the route part first
+ if ($route !== $this->route) {
+ if ($this->routeRule !== null && preg_match($this->routeRule, $route, $matches)) {
+ foreach ($this->routeParams as $key => $name) {
+ $tr[$name] = $matches[$key];
+ }
+ } else {
+ return false;
+ }
+ }
+
+ // match default params
+ // if a default param is not in the route pattern, its value must also be matched
+ foreach ($this->defaults as $name => $value) {
+ if (!isset($params[$name])) {
+ return false;
+ } elseif (strcmp($params[$name], $value) === 0) { // strcmp will do string conversion automatically
+ unset($params[$name]);
+ if (isset($this->paramRules[$name])) {
+ $tr["<$name>"] = '';
+ $tr["/<$name>/"] = '/';
+ }
+ } elseif (!isset($this->paramRules[$name])) {
+ return false;
+ }
+ }
+
+ // match params in the pattern
+ foreach ($this->paramRules as $name => $rule) {
+ if (isset($params[$name]) && ($rule === '' || preg_match($rule, $params[$name]))) {
+ $tr["<$name>"] = urlencode($params[$name]);
+ unset($params[$name]);
+ } elseif (!isset($this->defaults[$name]) || isset($params[$name])) {
+ return false;
+ }
+ }
+
+ $url = trim(strtr($this->template, $tr), '/');
+ if (strpos($url, '//') !== false) {
+ $url = preg_replace('#/+#', '/', $url);
+ }
+ if ($params !== array()) {
+ $url .= '?' . http_build_query($params);
+ }
+ return $url;
+ }
+
+ public function parse($pathInfo)
{
$route = '';
$params = array();
diff --git a/tests/unit/framework/web/UrlManagerTest.php b/tests/unit/framework/web/UrlManagerTest.php
new file mode 100644
index 0000000..3bd3007
--- /dev/null
+++ b/tests/unit/framework/web/UrlManagerTest.php
@@ -0,0 +1,7 @@
+getTestsForCreateUrl();
+ foreach ($suites as $i => $suite) {
+ list ($name, $config, $tests) = $suite;
+ $rule = new UrlRule($config);
+ foreach ($tests as $j => $test) {
+ list ($route, $params, $expected) = $test;
+ $url = $rule->createUrl($route, $params);
+ $this->assertEquals($expected, $url, "Test#$i-$j: $name");
+ }
+ }
+ }
+
+ public function testParseUrl()
+ {
+ $suites = $this->getTestsForParseUrl();
+ foreach ($suites as $i => $suite) {
+ list ($name, $config, $tests) = $suite;
+ $rule = new UrlRule($config);
+ foreach ($tests as $j => $test) {
+ $pathInfo = $test[0];
+ $route = $test[1];
+ $params = isset($test[2]) ? $test[2] : array();
+ $result = $rule->parseUrl($pathInfo);
+ if ($route === false) {
+ $this->assertFalse($result, "Test#$i-$j: $name");
+ } else {
+ $this->assertEquals(array($route, $params), $result, "Test#$i-$j: $name");
+ }
+ }
+ }
+ }
+
+ protected function getTestsForCreateUrl()
+ {
+ // structure of each test
+ // message for the test
+ // config for the URL rule
+ // list of inputs and outputs
+ // route
+ // params
+ // expected output
+ return array(
+ array(
+ 'empty pattern',
+ array(
+ 'pattern' => '',
+ 'route' => 'post/index',
+ ),
+ array(
+ array('post/index', array(), ''),
+ array('comment/index', array(), false),
+ array('post/index', array('page' => 1), '?page=1'),
+ ),
+ ),
+ array(
+ 'without param',
+ array(
+ 'pattern' => 'posts',
+ 'route' => 'post/index',
+ ),
+ array(
+ array('post/index', array(), 'posts'),
+ array('comment/index', array(), false),
+ array('post/index', array('page' => 1), 'posts?page=1'),
+ ),
+ ),
+ array(
+ 'with param',
+ array(
+ 'pattern' => 'post/',
+ 'route' => 'post/index',
+ ),
+ array(
+ array('post/index', array(), false),
+ array('comment/index', array(), false),
+ array('post/index', array('page' => 1), 'post/1'),
+ array('post/index', array('page' => 1, 'tag' => 'a'), 'post/1?tag=a'),
+ ),
+ ),
+ array(
+ 'with param requirement',
+ array(
+ 'pattern' => 'post/',
+ 'route' => 'post/index',
+ ),
+ array(
+ array('post/index', array('page' => 'abc'), false),
+ array('post/index', array('page' => 1), 'post/1'),
+ array('post/index', array('page' => 1, 'tag' => 'a'), 'post/1?tag=a'),
+ ),
+ ),
+ array(
+ 'with multiple params',
+ array(
+ 'pattern' => 'post/-',
+ 'route' => 'post/index',
+ ),
+ array(
+ array('post/index', array('page' => '1abc'), false),
+ array('post/index', array('page' => 1), false),
+ array('post/index', array('page' => 1, 'tag' => 'a'), 'post/1-a'),
+ ),
+ ),
+ array(
+ 'with optional param',
+ array(
+ 'pattern' => 'post//',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1),
+ ),
+ array(
+ array('post/index', array('page' => 1), false),
+ array('post/index', array('page' => '1abc', 'tag' => 'a'), false),
+ array('post/index', array('page' => 1, 'tag' => 'a'), 'post/a'),
+ array('post/index', array('page' => 2, 'tag' => 'a'), 'post/2/a'),
+ ),
+ ),
+ array(
+ 'with optional param not in pattern',
+ array(
+ 'pattern' => 'post/',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1),
+ ),
+ array(
+ array('post/index', array('page' => 1), false),
+ array('post/index', array('page' => '1abc', 'tag' => 'a'), false),
+ array('post/index', array('page' => 2, 'tag' => 'a'), false),
+ array('post/index', array('page' => 1, 'tag' => 'a'), 'post/a'),
+ ),
+ ),
+ array(
+ 'multiple optional params',
+ array(
+ 'pattern' => 'post///',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1, 'sort' => 'yes'),
+ ),
+ array(
+ array('post/index', array('page' => 1), false),
+ array('post/index', array('page' => '1abc', 'tag' => 'a'), false),
+ array('post/index', array('page' => 1, 'tag' => 'a', 'sort' => 'YES'), false),
+ array('post/index', array('page' => 1, 'tag' => 'a', 'sort' => 'yes'), 'post/a'),
+ array('post/index', array('page' => 2, 'tag' => 'a', 'sort' => 'yes'), 'post/2/a'),
+ array('post/index', array('page' => 2, 'tag' => 'a', 'sort' => 'no'), 'post/2/a/no'),
+ array('post/index', array('page' => 1, 'tag' => 'a', 'sort' => 'no'), 'post/a/no'),
+ ),
+ ),
+ array(
+ 'optional param and required param separated by dashes',
+ array(
+ 'pattern' => 'post/-',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1),
+ ),
+ array(
+ array('post/index', array('page' => 1), false),
+ array('post/index', array('page' => '1abc', 'tag' => 'a'), false),
+ array('post/index', array('page' => 1, 'tag' => 'a'), 'post/-a'),
+ array('post/index', array('page' => 2, 'tag' => 'a'), 'post/2-a'),
+ ),
+ ),
+ array(
+ 'optional param at the end',
+ array(
+ 'pattern' => 'post//',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1),
+ ),
+ array(
+ array('post/index', array('page' => 1), false),
+ array('post/index', array('page' => '1abc', 'tag' => 'a'), false),
+ array('post/index', array('page' => 1, 'tag' => 'a'), 'post/a'),
+ array('post/index', array('page' => 2, 'tag' => 'a'), 'post/a/2'),
+ ),
+ ),
+ array(
+ 'consecutive optional params',
+ array(
+ 'pattern' => 'post//',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1, 'tag' => 'a'),
+ ),
+ array(
+ array('post/index', array('page' => 1), false),
+ array('post/index', array('page' => '1abc', 'tag' => 'a'), false),
+ array('post/index', array('page' => 1, 'tag' => 'a'), 'post'),
+ array('post/index', array('page' => 2, 'tag' => 'a'), 'post/2'),
+ array('post/index', array('page' => 1, 'tag' => 'b'), 'post/b'),
+ array('post/index', array('page' => 2, 'tag' => 'b'), 'post/2/b'),
+ ),
+ ),
+ array(
+ 'consecutive optional params separated by dash',
+ array(
+ 'pattern' => 'post/-',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1, 'tag' => 'a'),
+ ),
+ array(
+ array('post/index', array('page' => 1), false),
+ array('post/index', array('page' => '1abc', 'tag' => 'a'), false),
+ array('post/index', array('page' => 1, 'tag' => 'a'), 'post/-'),
+ array('post/index', array('page' => 1, 'tag' => 'b'), 'post/-b'),
+ array('post/index', array('page' => 2, 'tag' => 'a'), 'post/2-'),
+ array('post/index', array('page' => 2, 'tag' => 'b'), 'post/2-b'),
+ ),
+ ),
+ array(
+ 'route has parameters',
+ array(
+ 'pattern' => '/',
+ 'route' => '/',
+ 'defaults' => array(),
+ ),
+ array(
+ array('post/index', array('page' => 1), 'post/index?page=1'),
+ array('module/post/index', array(), false),
+ ),
+ ),
+ array(
+ 'route has parameters with regex',
+ array(
+ 'pattern' => '/',
+ 'route' => '/',
+ 'defaults' => array(),
+ ),
+ array(
+ array('post/index', array('page' => 1), 'post/index?page=1'),
+ array('comment/index', array('page' => 1), 'comment/index?page=1'),
+ array('test/index', array('page' => 1), false),
+ array('post', array(), false),
+ array('module/post/index', array(), false),
+ array('post/index', array('controller' => 'comment'), 'post/index?controller=comment'),
+ ),
+ ),
+ /* this is not supported
+ array(
+ 'route has default parameter',
+ array(
+ 'pattern' => '/',
+ 'route' => '/',
+ 'defaults' => array('action' => 'index'),
+ ),
+ array(
+ array('post/view', array('page' => 1), 'post/view?page=1'),
+ array('comment/view', array('page' => 1), 'comment/view?page=1'),
+ array('test/view', array('page' => 1), false),
+ array('post/index', array('page' => 1), 'post?page=1'),
+ ),
+ ),
+ */
+ );
+ }
+
+ protected function getTestsForParseUrl()
+ {
+ // structure of each test
+ // message for the test
+ // config for the URL rule
+ // list of inputs and outputs
+ // pathInfo
+ // expected route, or false if the rule doesn't apply
+ // expected params, or not set if empty
+ return array(
+ array(
+ 'empty pattern',
+ array(
+ 'pattern' => '',
+ 'route' => 'post/index',
+ ),
+ array(
+ array('', 'post/index'),
+ array('a', false),
+ ),
+ ),
+ array(
+ 'without param',
+ array(
+ 'pattern' => 'posts',
+ 'route' => 'post/index',
+ ),
+ array(
+ array('posts', 'post/index'),
+ array('a', false),
+ ),
+ ),
+ array(
+ 'with param',
+ array(
+ 'pattern' => 'post/',
+ 'route' => 'post/index',
+ ),
+ array(
+ array('post/1', 'post/index', array('page' => '1')),
+ array('post/a', 'post/index', array('page' => 'a')),
+ array('post', false),
+ array('posts', false),
+ ),
+ ),
+ array(
+ 'with param requirement',
+ array(
+ 'pattern' => 'post/',
+ 'route' => 'post/index',
+ ),
+ array(
+ array('post/1', 'post/index', array('page' => '1')),
+ array('post/a', false),
+ array('post/1/a', false),
+ ),
+ ),
+ array(
+ 'with multiple params',
+ array(
+ 'pattern' => 'post/-',
+ 'route' => 'post/index',
+ ),
+ array(
+ array('post/1-a', 'post/index', array('page' => '1', 'tag' => 'a')),
+ array('post/a', false),
+ array('post/1', false),
+ array('post/1/a', false),
+ ),
+ ),
+ array(
+ 'with optional param',
+ array(
+ 'pattern' => 'post//',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1),
+ ),
+ array(
+ array('post/1/a', 'post/index', array('page' => '1', 'tag' => 'a')),
+ array('post/2/a', 'post/index', array('page' => '2', 'tag' => 'a')),
+ array('post/a', 'post/index', array('page' => '1', 'tag' => 'a')),
+ array('post/1', 'post/index', array('page' => '1', 'tag' => '1')),
+ ),
+ ),
+ array(
+ 'with optional param not in pattern',
+ array(
+ 'pattern' => 'post/',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1),
+ ),
+ array(
+ array('post/a', 'post/index', array('page' => '1', 'tag' => 'a')),
+ array('post/1', 'post/index', array('page' => '1', 'tag' => '1')),
+ array('post', false),
+ ),
+ ),
+ array(
+ 'multiple optional params',
+ array(
+ 'pattern' => 'post///',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1, 'sort' => 'yes'),
+ ),
+ array(
+ array('post/1/a/yes', 'post/index', array('page' => '1', 'tag' => 'a', 'sort' => 'yes')),
+ array('post/2/a/no', 'post/index', array('page' => '2', 'tag' => 'a', 'sort' => 'no')),
+ array('post/2/a', 'post/index', array('page' => '2', 'tag' => 'a', 'sort' => 'yes')),
+ array('post/a/no', 'post/index', array('page' => '1', 'tag' => 'a', 'sort' => 'no')),
+ array('post/a', 'post/index', array('page' => '1', 'tag' => 'a', 'sort' => 'yes')),
+ array('post', false),
+ ),
+ ),
+ array(
+ 'optional param and required param separated by dashes',
+ array(
+ 'pattern' => 'post/-',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1),
+ ),
+ array(
+ array('post/1-a', 'post/index', array('page' => '1', 'tag' => 'a')),
+ array('post/2-a', 'post/index', array('page' => '2', 'tag' => 'a')),
+ array('post/-a', 'post/index', array('page' => '1', 'tag' => 'a')),
+ array('post/a', false),
+ array('post-a', false),
+ ),
+ ),
+ array(
+ 'optional param at the end',
+ array(
+ 'pattern' => 'post//',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1),
+ ),
+ array(
+ array('post/a/1', 'post/index', array('page' => '1', 'tag' => 'a')),
+ array('post/a/2', 'post/index', array('page' => '2', 'tag' => 'a')),
+ array('post/a', 'post/index', array('page' => '1', 'tag' => 'a')),
+ array('post/2', 'post/index', array('page' => '1', 'tag' => '2')),
+ array('post', false),
+ ),
+ ),
+ array(
+ 'consecutive optional params',
+ array(
+ 'pattern' => 'post//',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1, 'tag' => 'a'),
+ ),
+ array(
+ array('post/2/b', 'post/index', array('page' => '2', 'tag' => 'b')),
+ array('post/2', 'post/index', array('page' => '2', 'tag' => 'a')),
+ array('post', 'post/index', array('page' => '1', 'tag' => 'a')),
+ array('post/b', 'post/index', array('page' => '1', 'tag' => 'b')),
+ array('post//b', false),
+ ),
+ ),
+ array(
+ 'consecutive optional params separated by dash',
+ array(
+ 'pattern' => 'post/-',
+ 'route' => 'post/index',
+ 'defaults' => array('page' => 1, 'tag' => 'a'),
+ ),
+ array(
+ array('post/2-b', 'post/index', array('page' => '2', 'tag' => 'b')),
+ array('post/2-', 'post/index', array('page' => '2', 'tag' => 'a')),
+ array('post/-b', 'post/index', array('page' => '1', 'tag' => 'b')),
+ array('post/-', 'post/index', array('page' => '1', 'tag' => 'a')),
+ array('post', false),
+ ),
+ ),
+ );
+ }
+}
From f31369e7c177d45eb94a82964ac8cbc1b08fc1f0 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Sun, 10 Feb 2013 21:55:15 -0500
Subject: [PATCH 07/84] url wip
---
framework/web/UrlRule.php | 12 ++++++++++--
tests/unit/framework/web/UrlRuleTest.php | 3 +--
2 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php
index 2961275..457b735 100644
--- a/framework/web/UrlRule.php
+++ b/framework/web/UrlRule.php
@@ -112,8 +112,13 @@ class UrlRule extends Object
// match the route part first
if ($route !== $this->route) {
if ($this->routeRule !== null && preg_match($this->routeRule, $route, $matches)) {
- foreach ($this->routeParams as $key => $name) {
- $tr[$name] = $matches[$key];
+ foreach ($this->routeParams as $name => $token) {
+ if (isset($this->defaults[$name]) && strcmp($this->defaults[$name], $matches[$name]) === 0) {
+ $tr[$token] = '';
+ $tr["/$token/"] = '/';
+ } else {
+ $tr[$token] = $matches[$name];
+ }
}
} else {
return false;
@@ -123,6 +128,9 @@ class UrlRule extends Object
// match default params
// if a default param is not in the route pattern, its value must also be matched
foreach ($this->defaults as $name => $value) {
+ if (isset($this->routeParams[$name])) {
+ continue;
+ }
if (!isset($params[$name])) {
return false;
} elseif (strcmp($params[$name], $value) === 0) { // strcmp will do string conversion automatically
diff --git a/tests/unit/framework/web/UrlRuleTest.php b/tests/unit/framework/web/UrlRuleTest.php
index a73e395..1467454 100644
--- a/tests/unit/framework/web/UrlRuleTest.php
+++ b/tests/unit/framework/web/UrlRuleTest.php
@@ -244,7 +244,6 @@ class UrlRuleTest extends \yiiunit\TestCase
array('post/index', array('controller' => 'comment'), 'post/index?controller=comment'),
),
),
- /* this is not supported
array(
'route has default parameter',
array(
@@ -256,10 +255,10 @@ class UrlRuleTest extends \yiiunit\TestCase
array('post/view', array('page' => 1), 'post/view?page=1'),
array('comment/view', array('page' => 1), 'comment/view?page=1'),
array('test/view', array('page' => 1), false),
+ array('test/index', array('page' => 1), false),
array('post/index', array('page' => 1), 'post?page=1'),
),
),
- */
);
}
From a946a863861f42f7f13c2a334015152acb24bbf9 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Mon, 11 Feb 2013 08:49:44 -0500
Subject: [PATCH 08/84] fixed parseUrl.
---
framework/web/UrlRule.php | 36 +++++++++++++++++++++++-------------
1 file changed, 23 insertions(+), 13 deletions(-)
diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php
index 457b735..247633e 100644
--- a/framework/web/UrlRule.php
+++ b/framework/web/UrlRule.php
@@ -77,9 +77,9 @@ class UrlRule extends Object
$length = strlen($match[0][0]);
$offset = $match[0][1];
if ($this->pattern[$offset - 1] === '/' && $this->pattern[$offset + $length] === '/') {
- $tr["<$name>"] = "(?P<$name>(?:/$pattern)?)";
+ $tr["/<$name>"] = "(/(?P<$name>$pattern))?";
} else {
- $tr["<$name>"] = "(?P<$name>(?:$pattern)?)";
+ $tr["<$name>"] = "(?P<$name>$pattern)?";
}
} else {
$tr["<$name>"] = "(?P<$name>$pattern)";
@@ -93,7 +93,7 @@ class UrlRule extends Object
}
$this->template = preg_replace('/<(\w+):?([^>]+)?>/', '<$1>', $this->pattern);
- $this->pattern = '#^' . strtr($this->template, $tr) . '$#u';
+ $this->pattern = '#^' . trim(strtr($this->template, $tr), '/') . '$#u';
if ($this->routeParams !== array()) {
$this->routeRule = '#^' . strtr($this->route, $tr2) . '$#u';
@@ -102,7 +102,26 @@ class UrlRule extends Object
public function parseUrl($pathInfo)
{
-
+ if (!preg_match($this->pattern, $pathInfo, $matches)) {
+ return false;
+ }
+ $params = $this->defaults;
+ $tr = array();
+ foreach ($matches as $name => $value) {
+ if ($value !== '') {
+ if (isset($this->routeParams[$name])) {
+ $tr[$this->routeParams[$name]] = $value;
+ } elseif (isset($this->paramRules[$name])) {
+ $params[$name] = $value;
+ }
+ }
+ }
+ if ($this->routeRule !== null) {
+ $route = strtr($this->route, $tr);
+ } else {
+ $route = $this->route;
+ }
+ return array($route, $params);
}
public function createUrl($route, $params)
@@ -115,7 +134,6 @@ class UrlRule extends Object
foreach ($this->routeParams as $name => $token) {
if (isset($this->defaults[$name]) && strcmp($this->defaults[$name], $matches[$name]) === 0) {
$tr[$token] = '';
- $tr["/$token/"] = '/';
} else {
$tr[$token] = $matches[$name];
}
@@ -137,7 +155,6 @@ class UrlRule extends Object
unset($params[$name]);
if (isset($this->paramRules[$name])) {
$tr["<$name>"] = '';
- $tr["/<$name>/"] = '/';
}
} elseif (!isset($this->paramRules[$name])) {
return false;
@@ -163,11 +180,4 @@ class UrlRule extends Object
}
return $url;
}
-
- public function parse($pathInfo)
- {
- $route = '';
- $params = array();
- return array($route, $params);
- }
}
From e25ad4bc88810e97f55585c9204a72eb7ea19470 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Mon, 11 Feb 2013 13:29:53 -0500
Subject: [PATCH 09/84] URL wip.
---
framework/web/UrlRule.php | 58 ++++++++++++++++++++------------
tests/unit/framework/web/UrlRuleTest.php | 44 ++++++++++++++++++++++++
2 files changed, 81 insertions(+), 21 deletions(-)
diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php
index 247633e..612a793 100644
--- a/framework/web/UrlRule.php
+++ b/framework/web/UrlRule.php
@@ -1,6 +1,6 @@
'post/',
- * 'route' => 'post/view',
- * 'defaults' => array('page' => 1),
- * )
- *
- * array(
- * 'pattern' => 'about',
- * 'route' => 'site/page',
- * 'defaults' => array('view' => 'about'),
- * )
+ * UrlRule represents a rule used for parsing and generating URLs.
*
* @author Qiang Xue
* @since 2.0
@@ -45,13 +34,36 @@ class UrlRule extends Object
*/
public $defaults = array();
- protected $paramRules = array();
- protected $routeRule;
+ /**
+ * @var string the template for generating a new URL. This is derived from [[pattern]] and is used in generating URL.
+ */
protected $template;
+ /**
+ * @var string the regex for matching the route part. This is used in generating URL.
+ */
+ protected $routeRule;
+ /**
+ * @var array list of regex for matching parameters. This is used in generating URL.
+ */
+ protected $paramRules = array();
+ /**
+ * @var array list of parameters used in the route.
+ */
protected $routeParams = array();
+ /**
+ * Initializes this rule.
+ */
public function init()
{
+ $this->compileRule();
+ }
+
+ /**
+ * Compiles the rule using the current configuration.
+ */
+ protected function compileRule()
+ {
$this->pattern = trim($this->pattern, '/');
if ($this->pattern === '') {
$this->template = '';
@@ -105,15 +117,19 @@ class UrlRule extends Object
if (!preg_match($this->pattern, $pathInfo, $matches)) {
return false;
}
+ foreach ($this->defaults as $name => $value) {
+ if (!isset($matches[$name]) || $matches[$name] === '') {
+ $matches[$name] = $value;
+ }
+ }
$params = $this->defaults;
$tr = array();
foreach ($matches as $name => $value) {
- if ($value !== '') {
- if (isset($this->routeParams[$name])) {
- $tr[$this->routeParams[$name]] = $value;
- } elseif (isset($this->paramRules[$name])) {
- $params[$name] = $value;
- }
+ if (isset($this->routeParams[$name])) {
+ $tr[$this->routeParams[$name]] = $value;
+ unset($params[$name]);
+ } elseif (isset($this->paramRules[$name])) {
+ $params[$name] = $value;
}
}
if ($this->routeRule !== null) {
diff --git a/tests/unit/framework/web/UrlRuleTest.php b/tests/unit/framework/web/UrlRuleTest.php
index 1467454..e9148c7 100644
--- a/tests/unit/framework/web/UrlRuleTest.php
+++ b/tests/unit/framework/web/UrlRuleTest.php
@@ -435,6 +435,50 @@ class UrlRuleTest extends \yiiunit\TestCase
array('post', false),
),
),
+ array(
+ 'route has parameters',
+ array(
+ 'pattern' => '/',
+ 'route' => '/',
+ 'defaults' => array(),
+ ),
+ array(
+ array('post/index', 'post/index'),
+ array('module/post/index', false),
+ ),
+ ),
+ array(
+ 'route has parameters with regex',
+ array(
+ 'pattern' => '/',
+ 'route' => '/',
+ 'defaults' => array(),
+ ),
+ array(
+ array('post/index', 'post/index'),
+ array('comment/index', 'comment/index'),
+ array('test/index', false),
+ array('post', false),
+ array('module/post/index', false),
+ ),
+ ),
+ array(
+ 'route has default parameter',
+ array(
+ 'pattern' => '/',
+ 'route' => '/',
+ 'defaults' => array('action' => 'index'),
+ ),
+ array(
+ array('post/view', 'post/view'),
+ array('comment/view', 'comment/view'),
+ array('test/view', false),
+ array('post', 'post/index'),
+ array('posts', false),
+ array('test', false),
+ array('index', false),
+ ),
+ ),
);
}
}
From 842caa3aaedfde0477ae21fc6f6bf5a7225479d6 Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Tue, 12 Feb 2013 00:08:28 +0400
Subject: [PATCH 10/84] Yii-style for PHP fatal and parse errors
---
framework/base/Application.php | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/framework/base/Application.php b/framework/base/Application.php
index 55dc0cc..b0167df 100644
--- a/framework/base/Application.php
+++ b/framework/base/Application.php
@@ -126,6 +126,7 @@ class Application extends Module
*/
public function init()
{
+ ob_start();
$this->preloadComponents();
}
@@ -138,10 +139,27 @@ class Application extends Module
*/
public function end($status = 0, $exit = true)
{
+ $lastError = error_get_last();
+
+ if(isset($lastError['type']) && in_array($lastError['type'], array(E_ERROR, E_PARSE))) {
+ ob_end_clean();
+ $exception = new \ErrorException($lastError['message'], 0, $lastError['type'], $lastError['file'], $lastError['line']);
+ $this->logException($exception);
+
+ if (($handler = $this->getErrorHandler()) !== null) {
+ $handler->handle($exception);
+ } else {
+ $this->renderException($exception);
+ }
+
+ die(1);
+ }
+
if (!$this->_ended) {
$this->_ended = true;
$this->afterRequest();
}
+ ob_end_flush();
if ($exit) {
exit($status);
}
@@ -155,6 +173,7 @@ class Application extends Module
public function run()
{
$this->beforeRequest();
+ register_shutdown_function(array($this,'end'),0,false);
$status = $this->processRequest();
$this->afterRequest();
return $status;
From f65a0bd9acc946ab068152a31d7783ce0b47ef4a Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Mon, 11 Feb 2013 16:34:02 -0500
Subject: [PATCH 11/84] url wip
---
framework/web/UrlRule.php | 94 ++++++++++++++++++++++++++++++++---------------
1 file changed, 64 insertions(+), 30 deletions(-)
diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php
index 612a793..f11fd16 100644
--- a/framework/web/UrlRule.php
+++ b/framework/web/UrlRule.php
@@ -33,40 +33,66 @@ class UrlRule extends Object
* will be injected into $_GET.
*/
public $defaults = array();
+ /**
+ * @var boolean whether this rule is only used for request parsing.
+ * Defaults to false, meaning the rule is used for both URL parsing and creation.
+ */
+ public $parsingOnly = false;
+ /**
+ * @var string the URL suffix used for this rule.
+ * For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
+ * If not, the value of [[UrlManager::suffix]] will be used.
+ */
+ public $suffix;
+ /**
+ * @var string|array the HTTP verb (e.g. GET, POST, DELETE) that this rule should match.
+ * Use array to represent multiple verbs that this rule may match.
+ * If this property is not set, the rule can match any verb.
+ * Note that this property is only used when parsing a request. It is ignored for URL creation.
+ * @see parsingOnly
+ */
+ public $verb;
+ /**
+ * @var string the host info (e.g. `http://www.example.com`) that this rule should match.
+ * If not set, it means the host info is ignored.
+ */
+ public $hostInfo;
/**
* @var string the template for generating a new URL. This is derived from [[pattern]] and is used in generating URL.
*/
- protected $template;
+ private $_template;
/**
* @var string the regex for matching the route part. This is used in generating URL.
*/
- protected $routeRule;
+ private $_routeRule;
/**
* @var array list of regex for matching parameters. This is used in generating URL.
*/
- protected $paramRules = array();
+ private $_paramRules = array();
/**
* @var array list of parameters used in the route.
*/
- protected $routeParams = array();
+ private $_routeParams = array();
/**
* Initializes this rule.
*/
public function init()
{
- $this->compileRule();
- }
+ if ($this->verb !== null) {
+ if (is_array($this->verb)) {
+ foreach ($this->verb as $i => $verb) {
+ $this->verb[$i] = strtoupper($verb);
+ }
+ } else {
+ $this->verb = array(strtoupper($this->verb));
+ }
+ }
- /**
- * Compiles the rule using the current configuration.
- */
- protected function compileRule()
- {
$this->pattern = trim($this->pattern, '/');
if ($this->pattern === '') {
- $this->template = '';
+ $this->_template = '';
$this->pattern = '#^$#u';
return;
} else {
@@ -76,7 +102,7 @@ class UrlRule extends Object
$this->route = trim($this->route, '/');
if (strpos($this->route, '<') !== false && preg_match_all('/<(\w+)>/', $this->route, $matches)) {
foreach ($matches[1] as $name) {
- $this->routeParams[$name] = "<$name>";
+ $this->_routeParams[$name] = "<$name>";
}
}
@@ -96,24 +122,28 @@ class UrlRule extends Object
} else {
$tr["<$name>"] = "(?P<$name>$pattern)";
}
- if (isset($this->routeParams[$name])) {
+ if (isset($this->_routeParams[$name])) {
$tr2["<$name>"] = "(?P<$name>$pattern)";
} else {
- $this->paramRules[$name] = $pattern === '[^\/]+' ? '' : "#^$pattern$#";
+ $this->_paramRules[$name] = $pattern === '[^\/]+' ? '' : "#^$pattern$#";
}
}
}
- $this->template = preg_replace('/<(\w+):?([^>]+)?>/', '<$1>', $this->pattern);
- $this->pattern = '#^' . trim(strtr($this->template, $tr), '/') . '$#u';
+ $this->_template = preg_replace('/<(\w+):?([^>]+)?>/', '<$1>', $this->pattern);
+ $this->pattern = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u';
- if ($this->routeParams !== array()) {
- $this->routeRule = '#^' . strtr($this->route, $tr2) . '$#u';
+ if ($this->_routeParams !== array()) {
+ $this->_routeRule = '#^' . strtr($this->route, $tr2) . '$#u';
}
}
public function parseUrl($pathInfo)
{
+ if ($this->verb !== null && !in_array(\Yii::$app->getRequest()->verb, $this->verb, true)) {
+ return false;
+ }
+
if (!preg_match($this->pattern, $pathInfo, $matches)) {
return false;
}
@@ -125,14 +155,14 @@ class UrlRule extends Object
$params = $this->defaults;
$tr = array();
foreach ($matches as $name => $value) {
- if (isset($this->routeParams[$name])) {
- $tr[$this->routeParams[$name]] = $value;
+ if (isset($this->_routeParams[$name])) {
+ $tr[$this->_routeParams[$name]] = $value;
unset($params[$name]);
- } elseif (isset($this->paramRules[$name])) {
+ } elseif (isset($this->_paramRules[$name])) {
$params[$name] = $value;
}
}
- if ($this->routeRule !== null) {
+ if ($this->_routeRule !== null) {
$route = strtr($this->route, $tr);
} else {
$route = $this->route;
@@ -142,12 +172,16 @@ class UrlRule extends Object
public function createUrl($route, $params)
{
+ if ($this->parsingOnly) {
+ return false;
+ }
+
$tr = array();
// match the route part first
if ($route !== $this->route) {
- if ($this->routeRule !== null && preg_match($this->routeRule, $route, $matches)) {
- foreach ($this->routeParams as $name => $token) {
+ if ($this->_routeRule !== null && preg_match($this->_routeRule, $route, $matches)) {
+ foreach ($this->_routeParams as $name => $token) {
if (isset($this->defaults[$name]) && strcmp($this->defaults[$name], $matches[$name]) === 0) {
$tr[$token] = '';
} else {
@@ -162,23 +196,23 @@ class UrlRule extends Object
// match default params
// if a default param is not in the route pattern, its value must also be matched
foreach ($this->defaults as $name => $value) {
- if (isset($this->routeParams[$name])) {
+ if (isset($this->_routeParams[$name])) {
continue;
}
if (!isset($params[$name])) {
return false;
} elseif (strcmp($params[$name], $value) === 0) { // strcmp will do string conversion automatically
unset($params[$name]);
- if (isset($this->paramRules[$name])) {
+ if (isset($this->_paramRules[$name])) {
$tr["<$name>"] = '';
}
- } elseif (!isset($this->paramRules[$name])) {
+ } elseif (!isset($this->_paramRules[$name])) {
return false;
}
}
// match params in the pattern
- foreach ($this->paramRules as $name => $rule) {
+ foreach ($this->_paramRules as $name => $rule) {
if (isset($params[$name]) && ($rule === '' || preg_match($rule, $params[$name]))) {
$tr["<$name>"] = urlencode($params[$name]);
unset($params[$name]);
@@ -187,7 +221,7 @@ class UrlRule extends Object
}
}
- $url = trim(strtr($this->template, $tr), '/');
+ $url = trim(strtr($this->_template, $tr), '/');
if (strpos($url, '//') !== false) {
$url = preg_replace('#/+#', '/', $url);
}
From 12fbb0f71d2357df5d4ad776160e3b34fafeb8f7 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Mon, 11 Feb 2013 19:51:18 -0500
Subject: [PATCH 12/84] url WIP
---
framework/web/UrlManager.php | 20 ++++-
framework/web/UrlRule.php | 67 +++++++++++++---
tests/unit/framework/web/UrlRuleTest.php | 133 ++++++++++++++++++++++++++++++-
3 files changed, 207 insertions(+), 13 deletions(-)
diff --git a/framework/web/UrlManager.php b/framework/web/UrlManager.php
index e03f086..8f927e8 100644
--- a/framework/web/UrlManager.php
+++ b/framework/web/UrlManager.php
@@ -27,7 +27,7 @@ class UrlManager extends Component
* @var string the URL suffix used when in 'path' format.
* For example, ".html" can be used so that the URL looks like pointing to a static HTML page. Defaults to empty.
*/
- public $urlSuffix = '';
+ public $suffix;
/**
* @var boolean whether to show entry script name in the constructed URL. Defaults to true.
*/
@@ -122,7 +122,7 @@ class UrlManager extends Component
/** @var $rule UrlRule */
foreach ($this->rules as $rule) {
- if (($url = $rule->createUrl($route, $params)) !== false) {
+ if (($url = $rule->createUrl($this, $route, $params)) !== false) {
return $this->getBaseUrl() . $url . $anchor;
}
}
@@ -144,4 +144,20 @@ class UrlManager extends Component
{
$this->_baseUrl = trim($value, '/');
}
+
+ /**
+ * Removes the URL suffix from path info.
+ * @param string $pathInfo path info part in the URL
+ * @param string $suffix the URL suffix to be removed
+ * @return string path info with URL suffix removed.
+ */
+ public function removeSuffix($pathInfo, $suffix)
+ {
+ $n = strlen($suffix);
+ if ($n > 0 && substr($pathInfo, -$n) === $suffix) {
+ return substr($pathInfo, 0, -$n);
+ } else {
+ return $pathInfo;
+ }
+ }
}
diff --git a/framework/web/UrlRule.php b/framework/web/UrlRule.php
index f11fd16..471712b 100644
--- a/framework/web/UrlRule.php
+++ b/framework/web/UrlRule.php
@@ -20,6 +20,15 @@ use yii\base\Object;
class UrlRule extends Object
{
/**
+ * Set [[mode]] with this value to mark that this rule is for URL parsing only
+ */
+ const PARSING_ONLY = 1;
+ /**
+ * Set [[mode]] with this value to mark that this rule is for URL creation only
+ */
+ const CREATION_ONLY = 2;
+
+ /**
* @var string regular expression used to parse a URL
*/
public $pattern;
@@ -34,11 +43,6 @@ class UrlRule extends Object
*/
public $defaults = array();
/**
- * @var boolean whether this rule is only used for request parsing.
- * Defaults to false, meaning the rule is used for both URL parsing and creation.
- */
- public $parsingOnly = false;
- /**
* @var string the URL suffix used for this rule.
* For example, ".html" can be used so that the URL looks like pointing to a static HTML page.
* If not, the value of [[UrlManager::suffix]] will be used.
@@ -49,7 +53,6 @@ class UrlRule extends Object
* Use array to represent multiple verbs that this rule may match.
* If this property is not set, the rule can match any verb.
* Note that this property is only used when parsing a request. It is ignored for URL creation.
- * @see parsingOnly
*/
public $verb;
/**
@@ -57,6 +60,14 @@ class UrlRule extends Object
* If not set, it means the host info is ignored.
*/
public $hostInfo;
+ /**
+ * @var integer a value indicating if this rule should be used for both URL parsing and creation,
+ * parsing only, or creation only.
+ * If not set, it means the rule is both URL parsing and creation.
+ * If it is [[PARSING_ONLY]], the rule is for URL parsing only.
+ * If it is [[CREATION_ONLY]], the rule is for URL creation only.
+ */
+ public $mode;
/**
* @var string the template for generating a new URL. This is derived from [[pattern]] and is used in generating URL.
@@ -138,12 +149,38 @@ class UrlRule extends Object
}
}
- public function parseUrl($pathInfo)
+ /**
+ * Parses the given path info and returns the corresponding route and parameters.
+ * @param UrlManager $manager the URL manager
+ * @param string $pathInfo the path info to be parsed. It should not have slashes at the beginning or the end.
+ * @return array|boolean the parsing result. The route and the parameters are returned as an array.
+ * If false, it means this rule cannot be used to parse this path info.
+ */
+ public function parseUrl($manager, $pathInfo)
{
+ if ($this->mode === self::CREATION_ONLY) {
+ return false;
+ }
+
if ($this->verb !== null && !in_array(\Yii::$app->getRequest()->verb, $this->verb, true)) {
return false;
}
+ $suffix = (string)($this->suffix === null ? $manager->suffix : $this->suffix);
+ if ($suffix !== '' && $pathInfo !== '') {
+ $n = strlen($suffix);
+ if (substr($pathInfo, -$n) === $suffix) {
+ $pathInfo = substr($pathInfo, 0, -$n);
+ if ($pathInfo === '') {
+ // suffix alone is not allowed
+ return false;
+ }
+ } elseif ($suffix !== '/') {
+ // we allow the ending '/' to be optional if it is a suffix
+ return false;
+ }
+ }
+
if (!preg_match($this->pattern, $pathInfo, $matches)) {
return false;
}
@@ -170,9 +207,16 @@ class UrlRule extends Object
return array($route, $params);
}
- public function createUrl($route, $params)
+ /**
+ * Creates a URL according to the given route and parameters.
+ * @param UrlManager $manager the URL manager
+ * @param string $route the route. It should not have slashes at the beginning or the end.
+ * @param array $params the parameters
+ * @return string|boolean the created URL, or false if this rule cannot be used for creating this URL.
+ */
+ public function createUrl($manager, $route, $params)
{
- if ($this->parsingOnly) {
+ if ($this->mode === self::PARSING_ONLY) {
return false;
}
@@ -225,6 +269,11 @@ class UrlRule extends Object
if (strpos($url, '//') !== false) {
$url = preg_replace('#/+#', '/', $url);
}
+
+ if ($url !== '') {
+ $url .= ($this->suffix === null ? $manager->suffix : $this->suffix);
+ }
+
if ($params !== array()) {
$url .= '?' . http_build_query($params);
}
diff --git a/tests/unit/framework/web/UrlRuleTest.php b/tests/unit/framework/web/UrlRuleTest.php
index e9148c7..fd9a8bd 100644
--- a/tests/unit/framework/web/UrlRuleTest.php
+++ b/tests/unit/framework/web/UrlRuleTest.php
@@ -2,19 +2,21 @@
namespace yiiunit\framework\web;
+use yii\web\UrlManager;
use yii\web\UrlRule;
class UrlRuleTest extends \yiiunit\TestCase
{
public function testCreateUrl()
{
+ $manager = new UrlManager;
$suites = $this->getTestsForCreateUrl();
foreach ($suites as $i => $suite) {
list ($name, $config, $tests) = $suite;
$rule = new UrlRule($config);
foreach ($tests as $j => $test) {
list ($route, $params, $expected) = $test;
- $url = $rule->createUrl($route, $params);
+ $url = $rule->createUrl($manager, $route, $params);
$this->assertEquals($expected, $url, "Test#$i-$j: $name");
}
}
@@ -22,6 +24,7 @@ class UrlRuleTest extends \yiiunit\TestCase
public function testParseUrl()
{
+ $manager = new UrlManager;
$suites = $this->getTestsForParseUrl();
foreach ($suites as $i => $suite) {
list ($name, $config, $tests) = $suite;
@@ -30,7 +33,7 @@ class UrlRuleTest extends \yiiunit\TestCase
$pathInfo = $test[0];
$route = $test[1];
$params = isset($test[2]) ? $test[2] : array();
- $result = $rule->parseUrl($pathInfo);
+ $result = $rule->parseUrl($manager, $pathInfo);
if ($route === false) {
$this->assertFalse($result, "Test#$i-$j: $name");
} else {
@@ -75,6 +78,17 @@ class UrlRuleTest extends \yiiunit\TestCase
),
),
array(
+ 'parsing only',
+ array(
+ 'pattern' => 'posts',
+ 'route' => 'post/index',
+ 'mode' => UrlRule::PARSING_ONLY,
+ ),
+ array(
+ array('post/index', array(), false),
+ ),
+ ),
+ array(
'with param',
array(
'pattern' => 'post/',
@@ -259,6 +273,58 @@ class UrlRuleTest extends \yiiunit\TestCase
array('post/index', array('page' => 1), 'post?page=1'),
),
),
+ array(
+ 'empty pattern with suffix',
+ array(
+ 'pattern' => '',
+ 'route' => 'post/index',
+ 'suffix' => '.html',
+ ),
+ array(
+ array('post/index', array(), ''),
+ array('comment/index', array(), false),
+ array('post/index', array('page' => 1), '?page=1'),
+ ),
+ ),
+ array(
+ 'regular pattern with suffix',
+ array(
+ 'pattern' => 'posts',
+ 'route' => 'post/index',
+ 'suffix' => '.html',
+ ),
+ array(
+ array('post/index', array(), 'posts.html'),
+ array('comment/index', array(), false),
+ array('post/index', array('page' => 1), 'posts.html?page=1'),
+ ),
+ ),
+ array(
+ 'empty pattern with slash suffix',
+ array(
+ 'pattern' => '',
+ 'route' => 'post/index',
+ 'suffix' => '/',
+ ),
+ array(
+ array('post/index', array(), ''),
+ array('comment/index', array(), false),
+ array('post/index', array('page' => 1), '?page=1'),
+ ),
+ ),
+ array(
+ 'regular pattern with slash suffix',
+ array(
+ 'pattern' => 'posts',
+ 'route' => 'post/index',
+ 'suffix' => '/',
+ ),
+ array(
+ array('post/index', array(), 'posts/'),
+ array('comment/index', array(), false),
+ array('post/index', array('page' => 1), 'posts/?page=1'),
+ ),
+ ),
);
}
@@ -295,6 +361,17 @@ class UrlRuleTest extends \yiiunit\TestCase
),
),
array(
+ 'creation only',
+ array(
+ 'pattern' => 'posts',
+ 'route' => 'post/index',
+ 'mode' => UrlRule::CREATION_ONLY,
+ ),
+ array(
+ array('posts', false),
+ ),
+ ),
+ array(
'with param',
array(
'pattern' => 'post/',
@@ -479,6 +556,58 @@ class UrlRuleTest extends \yiiunit\TestCase
array('index', false),
),
),
+ array(
+ 'empty pattern with suffix',
+ array(
+ 'pattern' => '',
+ 'route' => 'post/index',
+ 'suffix' => '.html',
+ ),
+ array(
+ array('', 'post/index'),
+ array('.html', false),
+ array('a.html', false),
+ ),
+ ),
+ array(
+ 'regular pattern with suffix',
+ array(
+ 'pattern' => 'posts',
+ 'route' => 'post/index',
+ 'suffix' => '.html',
+ ),
+ array(
+ array('posts.html', 'post/index'),
+ array('posts', false),
+ array('posts.HTML', false),
+ array('a.html', false),
+ array('a', false),
+ ),
+ ),
+ array(
+ 'empty pattern with slash suffix',
+ array(
+ 'pattern' => '',
+ 'route' => 'post/index',
+ 'suffix' => '/',
+ ),
+ array(
+ array('', 'post/index'),
+ array('a', false),
+ ),
+ ),
+ array(
+ 'regular pattern with slash suffix',
+ array(
+ 'pattern' => 'posts',
+ 'route' => 'post/index',
+ 'suffix' => '/',
+ ),
+ array(
+ array('posts', 'post/index'),
+ array('a', false),
+ ),
+ ),
);
}
}
From 2d5db95149ed711ceca24144aa2208cd4561674b Mon Sep 17 00:00:00 2001
From: Alexander Makarov
Date: Wed, 13 Feb 2013 03:51:29 +0400
Subject: [PATCH 13/84] More on error handling:
- Correct exit code for require errors in CLI mode. It was 0 even in case of error.
- Solved handling fatals and displaying custom error template w/o output buffering.
- If XDEBUG is available, error screen will display trace (implementation inspired by Kohana and Nette).
- Added ErrorException to store/display type of the error and user-friendly messages.
- Fatals are still logged in PHP log.
- Turned off native display_errors since we're now catching all errors.
- Added reserving memory (256kb) . Can be removed safely. In case of removal we're losing only memory exhausted error when allocating last very small chunk of memory.
- Properly handled errors in __toString (exception can't be thrown in this case).
- In the YII_DEBUG===false mode it's bad to display exception name even in page title.
- In the YII_DEBUG===true mode it's still useful to get user-friendly message additionally to exception name.
---
framework/base/Application.php | 88 ++++++++++++++++++++++++++++++---------
framework/base/ErrorException.php | 47 +++++++++++++++++++++
framework/base/Exception.php | 3 +-
framework/views/error.php | 7 ++--
framework/views/exception.php | 5 ++-
5 files changed, 123 insertions(+), 27 deletions(-)
create mode 100644 framework/base/ErrorException.php
diff --git a/framework/base/Application.php b/framework/base/Application.php
index b0167df..7c06e79 100644
--- a/framework/base/Application.php
+++ b/framework/base/Application.php
@@ -97,6 +97,12 @@ class Application extends Module
private $_language;
/**
+ * @var string Used to reserve memory for fatal error handler. This memory
+ * reserve can be removed if it's OK to write to PHP log only in this particular case.
+ */
+ private $_memoryReserve;
+
+ /**
* Constructor.
* @param string $id the ID of this application. The ID should uniquely identify the application from others.
* @param string $basePath the base path of this application. This should point to
@@ -110,6 +116,7 @@ class Application extends Module
$this->setBasePath($basePath);
if (YII_ENABLE_ERROR_HANDLER) {
+ ini_set('display_errors', 0);
set_exception_handler(array($this, 'handleException'));
set_error_handler(array($this, 'handleError'), error_reporting());
}
@@ -126,7 +133,6 @@ class Application extends Module
*/
public function init()
{
- ob_start();
$this->preloadComponents();
}
@@ -139,27 +145,54 @@ class Application extends Module
*/
public function end($status = 0, $exit = true)
{
- $lastError = error_get_last();
-
- if(isset($lastError['type']) && in_array($lastError['type'], array(E_ERROR, E_PARSE))) {
- ob_end_clean();
- $exception = new \ErrorException($lastError['message'], 0, $lastError['type'], $lastError['file'], $lastError['line']);
- $this->logException($exception);
-
- if (($handler = $this->getErrorHandler()) !== null) {
- $handler->handle($exception);
- } else {
- $this->renderException($exception);
- }
-
- die(1);
- }
-
if (!$this->_ended) {
$this->_ended = true;
$this->afterRequest();
}
- ob_end_flush();
+
+ if(YII_ENABLE_ERROR_HANDLER) {
+ $error = error_get_last();
+
+ if(isset($error['type']) && in_array($error['type'], ErrorException::getFatalCodes())) {
+ unset($this->_memoryReserve);
+ $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']);
+
+ if(function_exists('xdebug_get_function_stack')) {
+ $trace = array_slice(array_reverse(xdebug_get_function_stack()), 4, -1);
+ foreach($trace as &$frame) {
+ if(!isset($frame['function'])) {
+ $frame['function'] = 'unknown';
+ }
+
+ // XDebug < 2.1.1: http://bugs.xdebug.org/view.php?id=695
+ if(!isset($frame['type'])) {
+ $frame['type'] = '::';
+ }
+
+ // XDebug has a different key name
+ $frame['args'] = array();
+ if(isset($frame['params']) && !isset($frame['args'])) {
+ $frame['args'] = $frame['params'];
+ }
+ }
+
+ $ref = new \ReflectionProperty('Exception', 'trace');
+ $ref->setAccessible(true);
+ $ref->setValue($exception, $trace);
+ }
+
+ $this->logException($exception);
+
+ if (($handler = $this->getErrorHandler()) !== null) {
+ $handler->handle($exception);
+ } else {
+ $this->renderException($exception);
+ }
+
+ $status = 1;
+ }
+ }
+
if ($exit) {
exit($status);
}
@@ -173,6 +206,9 @@ class Application extends Module
public function run()
{
$this->beforeRequest();
+ // Allocating twice more than required to display memory exhausted error
+ // in case of trying to allocate last 1 byte while all memory is taken.
+ $this->_memoryReserve = str_repeat('x', 1024*256);
register_shutdown_function(array($this,'end'),0,false);
$status = $this->processRequest();
$this->afterRequest();
@@ -394,12 +430,24 @@ class Application extends Module
* @param string $message the error message
* @param string $file the filename that the error was raised in
* @param integer $line the line number the error was raised at
- * @throws \ErrorException the error exception
+ *
+ * @throws ErrorException
*/
public function handleError($code, $message, $file, $line)
{
if (error_reporting() !== 0) {
- throw new \ErrorException($message, 0, $code, $file, $line);
+ $exception = new ErrorException($message, $code, $code, $file, $line);
+
+ // in case error appeared in __toString method we can't throw any exception
+ $trace = debug_backtrace(false);
+ array_shift($trace);
+ foreach($trace as $frame) {
+ if($frame['function'] == '__toString') {
+ $this->handleException($exception);
+ }
+ }
+
+ throw $exception;
}
}
diff --git a/framework/base/ErrorException.php b/framework/base/ErrorException.php
new file mode 100644
index 0000000..073c1c3
--- /dev/null
+++ b/framework/base/ErrorException.php
@@ -0,0 +1,47 @@
+
+ * @since 2.0
+ */
+class ErrorException extends \ErrorException
+{
+ public static function getFatalCodes()
+ {
+ return array(E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING);
+ }
+
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ $names = array(
+ E_ERROR => \Yii::t('yii|Fatal Error'),
+ E_PARSE => \Yii::t('yii|Parse Error'),
+ E_CORE_ERROR => \Yii::t('yii|Core Error'),
+ E_COMPILE_ERROR => \Yii::t('yii|Compile Error'),
+ E_USER_ERROR => \Yii::t('yii|User Error'),
+ E_WARNING => \Yii::t('yii|Warning'),
+ E_CORE_WARNING => \Yii::t('yii|Core Warning'),
+ E_COMPILE_WARNING => \Yii::t('yii|Compile Warning'),
+ E_USER_WARNING => \Yii::t('yii|User Warning'),
+ E_STRICT => \Yii::t('yii|Strict'),
+ E_NOTICE => \Yii::t('yii|Notice'),
+ E_RECOVERABLE_ERROR => \Yii::t('yii|Recoverable Error'),
+ E_DEPRECATED => \Yii::t('yii|Deprecated'),
+ );
+ return isset($names[$this->getCode()]) ? $names[$this->getCode()] : \Yii::t('yii|Error');
+ }
+}
diff --git a/framework/base/Exception.php b/framework/base/Exception.php
index 0088a55..7dcd7c5 100644
--- a/framework/base/Exception.php
+++ b/framework/base/Exception.php
@@ -24,5 +24,4 @@ class Exception extends \Exception
{
return \Yii::t('yii|Exception');
}
-}
-
+}
\ No newline at end of file
diff --git a/framework/views/error.php b/framework/views/error.php
index c1700f2..28c50f1 100644
--- a/framework/views/error.php
+++ b/framework/views/error.php
@@ -4,12 +4,13 @@
* @var \yii\base\ErrorHandler $owner
*/
$owner = $this->owner;
+$title = $owner->htmlEncode($exception instanceof \yii\base\Exception || $exception instanceof \yii\base\ErrorException ? $exception->getName() : get_class($exception));
?>
-
+
";
+ }
+
+ /**
+ * Registers a 'refresh' meta tag.
+ * This method can be invoked anywhere in a view. It will register a 'refresh'
+ * meta tag with {@link CClientScript} so that the page can be refreshed in
+ * the specified seconds.
+ * @param integer $seconds the number of seconds to wait before refreshing the page
+ * @param string $url the URL to which the page should be redirected to. If empty, it means the current page.
+ * @since 1.1.1
+ */
+ public static function refresh($seconds, $url = '')
+ {
+ $content = "$seconds";
+ if ($url !== '') {
+ $content .= ';' . static::normalizeUrl($url);
+ }
+ Yii::app()->clientScript->registerMetaTag($content, null, 'refresh');
+ }
+
+ /**
+ * Links to the specified CSS file.
+ * @param string $url the CSS URL
+ * @param string $media the media that this CSS should apply to.
+ * @return string the CSS link.
+ */
+ public static function cssFile($url, $media = '')
+ {
+ return CHtml::linkTag('stylesheet', 'text/css', $url, $media !== '' ? $media : null);
+ }
+
+ /**
+ * Encloses the given JavaScript within a script tag.
+ * @param string $text the JavaScript to be enclosed
+ * @return string the enclosed JavaScript
+ */
+ public static function script($text)
+ {
+ return "";
+ }
+
+ /**
+ * Includes a JavaScript file.
+ * @param string $url URL for the JavaScript file
+ * @return string the JavaScript file tag
+ */
+ public static function scriptFile($url)
+ {
+ return '';
+ }
+
+ /**
+ * Generates an opening form tag.
+ * This is a shortcut to {@link beginForm}.
+ * @param mixed $action the form action URL (see {@link normalizeUrl} for details about this parameter.)
+ * @param string $method form method (e.g. post, get)
+ * @param array $htmlOptions additional HTML attributes (see {@link tag}).
+ * @return string the generated form tag.
+ */
+ public static function form($action = '', $method = 'post', $htmlOptions = array())
+ {
+ return static::beginForm($action, $method, $htmlOptions);
+ }
+
+ /**
+ * Generates an opening form tag.
+ * Note, only the open tag is generated. A close tag should be placed manually
+ * at the end of the form.
+ * @param mixed $action the form action URL (see {@link normalizeUrl} for details about this parameter.)
+ * @param string $method form method (e.g. post, get)
+ * @param array $htmlOptions additional HTML attributes (see {@link tag}).
+ * @return string the generated form tag.
+ * @see endForm
+ */
+ public static function beginForm($action = '', $method = 'post', $htmlOptions = array())
+ {
+ $htmlOptions['action'] = $url = static::normalizeUrl($action);
+ $htmlOptions['method'] = $method;
+ $form = static::tag('form', $htmlOptions, false, false);
+ $hiddens = array();
+ if (!strcasecmp($method, 'get') && ($pos = strpos($url, '?')) !== false) {
+ foreach (explode('&', substr($url, $pos + 1)) as $pair) {
+ if (($pos = strpos($pair, '=')) !== false) {
+ $hiddens[] = static::hiddenField(urldecode(substr($pair, 0, $pos)), urldecode(substr($pair, $pos + 1)), array('id' => false));
+ } else {
+ $hiddens[] = static::hiddenField(urldecode($pair), '', array('id' => false));
+ }
+ }
+ }
+ $request = Yii::app()->request;
+ if ($request->enableCsrfValidation && !strcasecmp($method, 'post')) {
+ $hiddens[] = static::hiddenField($request->csrfTokenName, $request->getCsrfToken(), array('id' => false));
+ }
+ if ($hiddens !== array()) {
+ $form .= "\n" . static::tag('div', array('style' => 'display:none'), implode("\n", $hiddens));
+ }
+ return $form;
+ }
+
+ /**
+ * Generates a closing form tag.
+ * @return string the generated tag
+ * @see beginForm
+ */
+ public static function endForm()
+ {
+ return '';
+ }
+
+ /**
+ * Generates a hyperlink tag.
+ * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code such as an image tag.
+ * @param mixed $url a URL or an action route that can be used to create a URL.
+ * See {@link normalizeUrl} for more details about how to specify this parameter.
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * @return string the generated hyperlink
+ * @see normalizeUrl
+ * @see clientChange
+ */
+ public static function link($text, $url = '#', $htmlOptions = array())
+ {
+ if ($url !== '') {
+ $htmlOptions['href'] = static::normalizeUrl($url);
+ }
+ static::clientChange('click', $htmlOptions);
+ return static::tag('a', $htmlOptions, $text);
+ }
+
+ /**
+ * Generates a mailto link.
+ * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code such as an image tag.
+ * @param string $email email address. If this is empty, the first parameter (link body) will be treated as the email address.
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * @return string the generated mailto link
+ * @see clientChange
+ */
+ public static function mailto($text, $email = '', $htmlOptions = array())
+ {
+ if ($email === '') {
+ $email = $text;
+ }
+ return static::link($text, 'mailto:' . $email, $htmlOptions);
+ }
+
+ /**
+ * Generates an image tag.
+ * @param string $src the image URL
+ * @param string $alt the alternative text display
+ * @param array $htmlOptions additional HTML attributes (see {@link tag}).
+ * @return string the generated image tag
+ */
+ public static function image($src, $alt = '', $htmlOptions = array())
+ {
+ $htmlOptions['src'] = $src;
+ $htmlOptions['alt'] = $alt;
+ return static::tag('img', $htmlOptions);
+ }
+
+ /**
+ * Generates a button.
+ * @param string $label the button label
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * @return string the generated button tag
+ * @see clientChange
+ */
+ public static function button($name, $label = 'button', $htmlOptions = array())
+ {
+ if (!isset($htmlOptions['name'])) {
+ if (!array_key_exists('name', $htmlOptions)) {
+ $htmlOptions['name'] = static::ID_PREFIX . static::$count++;
+ }
+ }
+ if (!isset($htmlOptions['type'])) {
+ $htmlOptions['type'] = 'button';
+ }
+ if (!isset($htmlOptions['value'])) {
+ $htmlOptions['value'] = $label;
+ }
+ static::clientChange('click', $htmlOptions);
+ return static::tag('input', $htmlOptions);
+ }
+
+ /**
+ * Generates a button using HTML button tag.
+ * This method is similar to {@link button} except that it generates a 'button'
+ * tag instead of 'input' tag.
+ * @param string $label the button label. Note that this value will be directly inserted in the button element
+ * without being HTML-encoded.
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * @return string the generated button tag
+ * @see clientChange
+ */
+ public static function htmlButton($label = 'button', $htmlOptions = array())
+ {
+ if (!isset($htmlOptions['name'])) {
+ $htmlOptions['name'] = static::ID_PREFIX . static::$count++;
+ }
+ if (!isset($htmlOptions['type'])) {
+ $htmlOptions['type'] = 'button';
+ }
+ static::clientChange('click', $htmlOptions);
+ return static::tag('button', $htmlOptions, $label);
+ }
+
+ /**
+ * Generates a submit button.
+ * @param string $label the button label
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * @return string the generated button tag
+ * @see clientChange
+ */
+ public static function submitButton($label = 'submit', $htmlOptions = array())
+ {
+ $htmlOptions['type'] = 'submit';
+ return static::button($label, $htmlOptions);
+ }
+
+ /**
+ * Generates a reset button.
+ * @param string $label the button label
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * @return string the generated button tag
+ * @see clientChange
+ */
+ public static function resetButton($label = 'reset', $htmlOptions = array())
+ {
+ $htmlOptions['type'] = 'reset';
+ return static::button($label, $htmlOptions);
+ }
+
+ /**
+ * Generates an image submit button.
+ * @param string $src the image URL
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * @return string the generated button tag
+ * @see clientChange
+ */
+ public static function imageButton($src, $htmlOptions = array())
+ {
+ $htmlOptions['src'] = $src;
+ $htmlOptions['type'] = 'image';
+ return static::button('submit', $htmlOptions);
+ }
+
+ /**
+ * Generates a link submit button.
+ * @param string $label the button label
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * @return string the generated button tag
+ * @see clientChange
+ */
+ public static function linkButton($label = 'submit', $htmlOptions = array())
+ {
+ if (!isset($htmlOptions['submit'])) {
+ $htmlOptions['submit'] = isset($htmlOptions['href']) ? $htmlOptions['href'] : '';
+ }
+ return static::link($label, '#', $htmlOptions);
+ }
+
+ /**
+ * Generates a label tag.
+ * @param string $label label text. Note, you should HTML-encode the text if needed.
+ * @param string $for the ID of the HTML element that this label is associated with.
+ * If this is false, the 'for' attribute for the label tag will not be rendered.
+ * @param array $htmlOptions additional HTML attributes.
+ * The following HTML option is recognized:
+ *
+ *
required: if this is set and is true, the label will be styled
+ * with CSS class 'required' (customizable with CHtml::$requiredCss),
+ * and be decorated with {@link CHtml::beforeRequiredLabel} and
+ * {@link CHtml::afterRequiredLabel}.
+ *
+ * @return string the generated label tag
+ */
+ public static function label($label, $for, $htmlOptions = array())
+ {
+ if ($for === false) {
+ unset($htmlOptions['for']);
+ } else {
+ $htmlOptions['for'] = $for;
+ }
+ if (isset($htmlOptions['required'])) {
+ if ($htmlOptions['required']) {
+ if (isset($htmlOptions['class'])) {
+ $htmlOptions['class'] .= ' ' . static::$requiredCss;
+ } else {
+ $htmlOptions['class'] = static::$requiredCss;
+ }
+ $label = static::$beforeRequiredLabel . $label . static::$afterRequiredLabel;
+ }
+ unset($htmlOptions['required']);
+ }
+ return static::tag('label', $htmlOptions, $label);
+ }
+
+ /**
+ * Generates a text field input.
+ * @param string $name the input name
+ * @param string $value the input value
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * @return string the generated input field
+ * @see clientChange
+ * @see inputField
+ */
+ public static function textField($name, $value = '', $htmlOptions = array())
+ {
+ static::clientChange('change', $htmlOptions);
+ return static::inputField('text', $name, $value, $htmlOptions);
+ }
+
+ /**
+ * Generates a hidden input.
+ * @param string $name the input name
+ * @param string $value the input value
+ * @param array $htmlOptions additional HTML attributes (see {@link tag}).
+ * @return string the generated input field
+ * @see inputField
+ */
+ public static function hiddenField($name, $value = '', $htmlOptions = array())
+ {
+ return static::inputField('hidden', $name, $value, $htmlOptions);
+ }
+
+ /**
+ * Generates a password field input.
+ * @param string $name the input name
+ * @param string $value the input value
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * @return string the generated input field
+ * @see clientChange
+ * @see inputField
+ */
+ public static function passwordField($name, $value = '', $htmlOptions = array())
+ {
+ static::clientChange('change', $htmlOptions);
+ return static::inputField('password', $name, $value, $htmlOptions);
+ }
+
+ /**
+ * Generates a file input.
+ * Note, you have to set the enclosing form's 'enctype' attribute to be 'multipart/form-data'.
+ * After the form is submitted, the uploaded file information can be obtained via $_FILES[$name] (see
+ * PHP documentation).
+ * @param string $name the input name
+ * @param string $value the input value
+ * @param array $htmlOptions additional HTML attributes (see {@link tag}).
+ * @return string the generated input field
+ * @see inputField
+ */
+ public static function fileField($name, $value = '', $htmlOptions = array())
+ {
+ return static::inputField('file', $name, $value, $htmlOptions);
+ }
+
+ /**
+ * Generates a text area input.
+ * @param string $name the input name
+ * @param string $value the input value
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * @return string the generated text area
+ * @see clientChange
+ * @see inputField
+ */
+ public static function textArea($name, $value = '', $htmlOptions = array())
+ {
+ $htmlOptions['name'] = $name;
+ if (!isset($htmlOptions['id'])) {
+ $htmlOptions['id'] = static::getIdByName($name);
+ } elseif ($htmlOptions['id'] === false) {
+ unset($htmlOptions['id']);
+ }
+ static::clientChange('change', $htmlOptions);
+ return static::tag('textarea', $htmlOptions, isset($htmlOptions['encode']) && !$htmlOptions['encode'] ? $value : static::encode($value));
+ }
+
+ /**
+ * Generates a radio button.
+ * @param string $name the input name
+ * @param boolean $checked whether the radio button is checked
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * Since version 1.1.2, a special option named 'uncheckValue' is available that can be used to specify
+ * the value returned when the radio button is not checked. When set, a hidden field is rendered so that
+ * when the radio button is not checked, we can still obtain the posted uncheck value.
+ * If 'uncheckValue' is not set or set to NULL, the hidden field will not be rendered.
+ * @return string the generated radio button
+ * @see clientChange
+ * @see inputField
+ */
+ public static function radioButton($name, $checked = false, $htmlOptions = array())
+ {
+ if ($checked) {
+ $htmlOptions['checked'] = 'checked';
+ } else {
+ unset($htmlOptions['checked']);
+ }
+ $value = isset($htmlOptions['value']) ? $htmlOptions['value'] : 1;
+ static::clientChange('click', $htmlOptions);
+
+ if (array_key_exists('uncheckValue', $htmlOptions)) {
+ $uncheck = $htmlOptions['uncheckValue'];
+ unset($htmlOptions['uncheckValue']);
+ } else {
+ $uncheck = null;
+ }
+
+ if ($uncheck !== null) {
+ // add a hidden field so that if the radio button is not selected, it still submits a value
+ if (isset($htmlOptions['id']) && $htmlOptions['id'] !== false) {
+ $uncheckOptions = array('id' => static::ID_PREFIX . $htmlOptions['id']);
+ } else {
+ $uncheckOptions = array('id' => false);
+ }
+ $hidden = static::hiddenField($name, $uncheck, $uncheckOptions);
+ } else {
+ $hidden = '';
+ }
+
+ // add a hidden field so that if the radio button is not selected, it still submits a value
+ return $hidden . static::inputField('radio', $name, $value, $htmlOptions);
+ }
+
+ /**
+ * Generates a check box.
+ * @param string $name the input name
+ * @param boolean $checked whether the check box is checked
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * Since version 1.1.2, a special option named 'uncheckValue' is available that can be used to specify
+ * the value returned when the checkbox is not checked. When set, a hidden field is rendered so that
+ * when the checkbox is not checked, we can still obtain the posted uncheck value.
+ * If 'uncheckValue' is not set or set to NULL, the hidden field will not be rendered.
+ * @return string the generated check box
+ * @see clientChange
+ * @see inputField
+ */
+ public static function checkBox($name, $checked = false, $htmlOptions = array())
+ {
+ if ($checked) {
+ $htmlOptions['checked'] = 'checked';
+ } else {
+ unset($htmlOptions['checked']);
+ }
+ $value = isset($htmlOptions['value']) ? $htmlOptions['value'] : 1;
+ static::clientChange('click', $htmlOptions);
+
+ if (array_key_exists('uncheckValue', $htmlOptions)) {
+ $uncheck = $htmlOptions['uncheckValue'];
+ unset($htmlOptions['uncheckValue']);
+ } else {
+ $uncheck = null;
+ }
+
+ if ($uncheck !== null) {
+ // add a hidden field so that if the check box is not checked, it still submits a value
+ if (isset($htmlOptions['id']) && $htmlOptions['id'] !== false) {
+ $uncheckOptions = array('id' => static::ID_PREFIX . $htmlOptions['id']);
+ } else {
+ $uncheckOptions = array('id' => false);
+ }
+ $hidden = static::hiddenField($name, $uncheck, $uncheckOptions);
+ } else {
+ $hidden = '';
+ }
+
+ // add a hidden field so that if the check box is not checked, it still submits a value
+ return $hidden . static::inputField('checkbox', $name, $value, $htmlOptions);
+ }
+
+ /**
+ * Generates a drop down list.
+ * @param string $name the input name
+ * @param string $select the selected value
+ * @param array $data data for generating the list options (value=>display).
+ * You may use {@link listData} to generate this data.
+ * Please refer to {@link listOptions} on how this data is used to generate the list options.
+ * Note, the values and labels will be automatically HTML-encoded by this method.
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are recognized. See {@link clientChange} and {@link tag} for more details.
+ * In addition, the following options are also supported specifically for dropdown list:
+ *
+ *
encode: boolean, specifies whether to encode the values. Defaults to true.
+ *
prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.
+ *
empty: string, specifies the text corresponding to empty selection. Its value is empty.
+ * The 'empty' option can also be an array of value-label pairs.
+ * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.
+ *
options: array, specifies additional attributes for each OPTION tag.
+ * The array keys must be the option values, and the array values are the extra
+ * OPTION tag attributes in the name-value pairs. For example,
+ *
+ * Since 1.1.13, a special option named 'unselectValue' is available. It can be used to set the value
+ * that will be returned when no option is selected in multiple mode. When set, a hidden field is
+ * rendered so that if no option is selected in multiple mode, we can still obtain the posted
+ * unselect value. If 'unselectValue' is not set or set to NULL, the hidden field will not be rendered.
+ * @return string the generated drop down list
+ * @see clientChange
+ * @see inputField
+ * @see listData
+ */
+ public static function dropDownList($name, $select, $data, $htmlOptions = array())
+ {
+ $htmlOptions['name'] = $name;
+
+ if (!isset($htmlOptions['id'])) {
+ $htmlOptions['id'] = static::getIdByName($name);
+ } elseif ($htmlOptions['id'] === false) {
+ unset($htmlOptions['id']);
+ }
+
+ static::clientChange('change', $htmlOptions);
+ $options = "\n" . static::listOptions($select, $data, $htmlOptions);
+ $hidden = '';
+
+ if (isset($htmlOptions['multiple'])) {
+ if (substr($htmlOptions['name'], -2) !== '[]') {
+ $htmlOptions['name'] .= '[]';
+ }
+
+ if (isset($htmlOptions['unselectValue'])) {
+ $hiddenOptions = isset($htmlOptions['id']) ? array('id' => static::ID_PREFIX . $htmlOptions['id']) : array('id' => false);
+ $hidden = static::hiddenField(substr($htmlOptions['name'], 0, -2), $htmlOptions['unselectValue'], $hiddenOptions);
+ unset($htmlOptions['unselectValue']);
+ }
+ }
+ // add a hidden field so that if the option is not selected, it still submits a value
+ return $hidden . static::tag('select', $htmlOptions, $options);
+ }
+
+ /**
+ * Generates a list box.
+ * @param string $name the input name
+ * @param mixed $select the selected value(s). This can be either a string for single selection or an array for multiple selections.
+ * @param array $data data for generating the list options (value=>display)
+ * You may use {@link listData} to generate this data.
+ * Please refer to {@link listOptions} on how this data is used to generate the list options.
+ * Note, the values and labels will be automatically HTML-encoded by this method.
+ * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
+ * attributes are also recognized. See {@link clientChange} and {@link tag} for more details.
+ * In addition, the following options are also supported specifically for list box:
+ *
+ *
encode: boolean, specifies whether to encode the values. Defaults to true.
+ *
prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.
+ *
empty: string, specifies the text corresponding to empty selection. Its value is empty.
+ * The 'empty' option can also be an array of value-label pairs.
+ * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.
+ *
options: array, specifies additional attributes for each OPTION tag.
+ * The array keys must be the option values, and the array values are the extra
+ * OPTION tag attributes in the name-value pairs. For example,
+ *
+ * @return string the generated list box
+ * @see clientChange
+ * @see inputField
+ * @see listData
+ */
+ public static function listBox($name, $select, $data, $htmlOptions = array())
+ {
+ if (!isset($htmlOptions['size'])) {
+ $htmlOptions['size'] = 4;
+ }
+ if (isset($htmlOptions['multiple'])) {
+ if (substr($name, -2) !== '[]') {
+ $name .= '[]';
+ }
+ }
+ return static::dropDownList($name, $select, $data, $htmlOptions);
+ }
+
+ /**
+ * Generates a check box list.
+ * A check box list allows multiple selection, like {@link listBox}.
+ * As a result, the corresponding POST value is an array.
+ * @param string $name name of the check box list. You can use this name to retrieve
+ * the selected value(s) once the form is submitted.
+ * @param mixed $select selection of the check boxes. This can be either a string
+ * for single selection or an array for multiple selections.
+ * @param array $data value-label pairs used to generate the check box list.
+ * Note, the values will be automatically HTML-encoded, while the labels will not.
+ * @param array $htmlOptions additional HTML options. The options will be applied to
+ * each checkbox input. The following special options are recognized:
+ *
+ *
template: string, specifies how each checkbox is rendered. Defaults
+ * to "{input} {label}", where "{input}" will be replaced by the generated
+ * check box input tag while "{label}" be replaced by the corresponding check box label.
+ *
separator: string, specifies the string that separates the generated check boxes.
+ *
checkAll: string, specifies the label for the "check all" checkbox.
+ * If this option is specified, a 'check all' checkbox will be displayed. Clicking on
+ * this checkbox will cause all checkboxes checked or unchecked.
+ *
checkAllLast: boolean, specifies whether the 'check all' checkbox should be
+ * displayed at the end of the checkbox list. If this option is not set (default)
+ * or is false, the 'check all' checkbox will be displayed at the beginning of
+ * the checkbox list.
+ *
labelOptions: array, specifies the additional HTML attributes to be rendered
+ * for every label tag in the list.
+ *
container: string, specifies the checkboxes enclosing tag. Defaults to 'span'.
+ * If the value is an empty string, no enclosing tag will be generated
+ *
baseID: string, specifies the base ID prefix to be used for checkboxes in the list.
+ * This option is available since version 1.1.13.
+ *
+ * @return string the generated check box list
+ */
+ public static function checkBoxList($name, $select, $data, $htmlOptions = array())
+ {
+ $template = isset($htmlOptions['template']) ? $htmlOptions['template'] : '{input} {label}';
+ $separator = isset($htmlOptions['separator']) ? $htmlOptions['separator'] : " \n";
+ $container = isset($htmlOptions['container']) ? $htmlOptions['container'] : 'span';
+ unset($htmlOptions['template'], $htmlOptions['separator'], $htmlOptions['container']);
+
+ if (substr($name, -2) !== '[]') {
+ $name .= '[]';
+ }
+
+ if (isset($htmlOptions['checkAll'])) {
+ $checkAllLabel = $htmlOptions['checkAll'];
+ $checkAllLast = isset($htmlOptions['checkAllLast']) && $htmlOptions['checkAllLast'];
+ }
+ unset($htmlOptions['checkAll'], $htmlOptions['checkAllLast']);
+
+ $labelOptions = isset($htmlOptions['labelOptions']) ? $htmlOptions['labelOptions'] : array();
+ unset($htmlOptions['labelOptions']);
+
+ $items = array();
+ $baseID = isset($htmlOptions['baseID']) ? $htmlOptions['baseID'] : static::getIdByName($name);
+ unset($htmlOptions['baseID']);
+ $id = 0;
+ $checkAll = true;
+
+ foreach ($data as $value => $label) {
+ $checked = !is_array($select) && !strcmp($value, $select) || is_array($select) && in_array($value, $select);
+ $checkAll = $checkAll && $checked;
+ $htmlOptions['value'] = $value;
+ $htmlOptions['id'] = $baseID . '_' . $id++;
+ $option = static::checkBox($name, $checked, $htmlOptions);
+ $label = static::label($label, $htmlOptions['id'], $labelOptions);
+ $items[] = strtr($template, array('{input}' => $option, '{label}' => $label));
+ }
+
+ if (isset($checkAllLabel)) {
+ $htmlOptions['value'] = 1;
+ $htmlOptions['id'] = $id = $baseID . '_all';
+ $option = static::checkBox($id, $checkAll, $htmlOptions);
+ $label = static::label($checkAllLabel, $id, $labelOptions);
+ $item = strtr($template, array('{input}' => $option, '{label}' => $label));
+ if ($checkAllLast) {
+ $items[] = $item;
+ } else {
+ array_unshift($items, $item);
+ }
+ $name = strtr($name, array('[' => '\\[', ']' => '\\]'));
+ $js = <<getClientScript();
+ $cs->registerCoreScript('jquery');
+ $cs->registerScript($id, $js);
+ }
+
+ if (empty($container)) {
+ return implode($separator, $items);
+ } else {
+ return static::tag($container, array('id' => $baseID), implode($separator, $items));
+ }
+ }
+
+ /**
+ * Generates a radio button list.
+ * A radio button list is like a {@link checkBoxList check box list}, except that
+ * it only allows single selection.
+ * @param string $name name of the radio button list. You can use this name to retrieve
+ * the selected value(s) once the form is submitted.
+ * @param string $select selection of the radio buttons.
+ * @param array $data value-label pairs used to generate the radio button list.
+ * Note, the values will be automatically HTML-encoded, while the labels will not.
+ * @param array $htmlOptions additional HTML options. The options will be applied to
+ * each radio button input. The following special options are recognized:
+ *
+ *
template: string, specifies how each radio button is rendered. Defaults
+ * to "{input} {label}", where "{input}" will be replaced by the generated
+ * radio button input tag while "{label}" will be replaced by the corresponding radio button label.
+ *
separator: string, specifies the string that separates the generated radio buttons. Defaults to new line ( ).
+ *
labelOptions: array, specifies the additional HTML attributes to be rendered
+ * for every label tag in the list.
+ *
container: string, specifies the radio buttons enclosing tag. Defaults to 'span'.
+ * If the value is an empty string, no enclosing tag will be generated
+ *
baseID: string, specifies the base ID prefix to be used for radio buttons in the list.
+ * This option is available since version 1.1.13.
+ *
+ * @return string the generated radio button list
+ */
+ public static function radioButtonList($name, $select, $data, $htmlOptions = array())
+ {
+ $template = isset($htmlOptions['template']) ? $htmlOptions['template'] : '{input} {label}';
+ $separator = isset($htmlOptions['separator']) ? $htmlOptions['separator'] : " \n";
+ $container = isset($htmlOptions['container']) ? $htmlOptions['container'] : 'span';
+ unset($htmlOptions['template'], $htmlOptions['separator'], $htmlOptions['container']);
+
+ $labelOptions = isset($htmlOptions['labelOptions']) ? $htmlOptions['labelOptions'] : array();
+ unset($htmlOptions['labelOptions']);
+
+ $items = array();
+ $baseID = isset($htmlOptions['baseID']) ? $htmlOptions['baseID'] : static::getIdByName($name);
+ unset($htmlOptions['baseID']);
+ $id = 0;
+ foreach ($data as $value => $label) {
+ $checked = !strcmp($value, $select);
+ $htmlOptions['value'] = $value;
+ $htmlOptions['id'] = $baseID . '_' . $id++;
+ $option = static::radioButton($name, $checked, $htmlOptions);
+ $label = static::label($label, $htmlOptions['id'], $labelOptions);
+ $items[] = strtr($template, array('{input}' => $option, '{label}' => $label));
+ }
+ if (empty($container)) {
+ return implode($separator, $items);
+ } else {
+ return static::tag($container, array('id' => $baseID), implode($separator, $items));
+ }
+ }
+
+ /**
+ * Normalizes the input parameter to be a valid URL.
+ *
+ * If the input parameter is an empty string, the currently requested URL will be returned.
+ *
+ * If the input parameter is a non-empty string, it is treated as a valid URL and will
+ * be returned without any change.
+ *
+ * If the input parameter is an array, it is treated as a controller route and a list of
+ * GET parameters, and the {@link CController::createUrl} method will be invoked to
+ * create a URL. In this case, the first array element refers to the controller route,
+ * and the rest key-value pairs refer to the additional GET parameters for the URL.
+ * For example, array('post/list', 'page'=>3) may be used to generate the URL
+ * /index.php?r=post/list&page=3.
+ *
+ * @param mixed $url the parameter to be used to generate a valid URL
+ * @return string the normalized URL
+ */
+ public static function normalizeUrl($url)
+ {
+ if (is_array($url)) {
+ if (isset($url[0])) {
+ if (($c = Yii::app()->getController()) !== null) {
+ $url = $c->createUrl($url[0], array_splice($url, 1));
+ } else {
+ $url = Yii::app()->createUrl($url[0], array_splice($url, 1));
+ }
+ } else {
+ $url = '';
+ }
+ }
+ return $url === '' ? Yii::app()->getRequest()->getUrl() : $url;
+ }
+
+ /**
+ * Generates an input HTML tag.
+ * This method generates an input HTML tag based on the given input name and value.
+ * @param string $type the input type (e.g. 'text', 'radio')
+ * @param string $name the input name
+ * @param string $value the input value
+ * @param array $htmlOptions additional HTML attributes for the HTML tag (see {@link tag}).
+ * @return string the generated input tag
+ */
+ protected static function inputField($type, $name, $value, $htmlOptions)
+ {
+ $htmlOptions['type'] = $type;
+ $htmlOptions['value'] = $value;
+ $htmlOptions['name'] = $name;
+ if (!isset($htmlOptions['id'])) {
+ $htmlOptions['id'] = static::getIdByName($name);
+ } elseif ($htmlOptions['id'] === false) {
+ unset($htmlOptions['id']);
+ }
+ return static::tag('input', $htmlOptions);
+ }
+
+ /**
+ * Generates the list options.
+ * @param mixed $selection the selected value(s). This can be either a string for single selection or an array for multiple selections.
+ * @param array $listData the option data (see {@link listData})
+ * @param array $htmlOptions additional HTML attributes. The following two special attributes are recognized:
+ *
+ *
encode: boolean, specifies whether to encode the values. Defaults to true.
+ *
prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.
+ *
empty: string, specifies the text corresponding to empty selection. Its value is empty.
+ * The 'empty' option can also be an array of value-label pairs.
+ * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.
+ *
options: array, specifies additional attributes for each OPTION tag.
+ * The array keys must be the option values, and the array values are the extra
+ * OPTION tag attributes in the name-value pairs. For example,
+ *
key: string, specifies the name of key attribute of the selection object(s).
+ * This is used when the selection is represented in terms of objects. In this case,
+ * the property named by the key option of the objects will be treated as the actual selection value.
+ * This option defaults to 'primaryKey', meaning using the 'primaryKey' property value of the objects in the selection.
+ * This option has been available since version 1.1.3.
+ *
+ * @return string the generated list options
+ */
+ public static function listOptions($selection, $listData, &$htmlOptions)
+ {
+ $raw = isset($htmlOptions['encode']) && !$htmlOptions['encode'];
+ $content = '';
+ if (isset($htmlOptions['prompt'])) {
+ $content .= '\n";
+ unset($htmlOptions['prompt']);
+ }
+ if (isset($htmlOptions['empty'])) {
+ if (!is_array($htmlOptions['empty'])) {
+ $htmlOptions['empty'] = array('' => $htmlOptions['empty']);
+ }
+ foreach ($htmlOptions['empty'] as $value => $label) {
+ $content .= '\n";
+ }
+ unset($htmlOptions['empty']);
+ }
+
+ if (isset($htmlOptions['options'])) {
+ $options = $htmlOptions['options'];
+ unset($htmlOptions['options']);
+ } else {
+ $options = array();
+ }
+
+ $key = isset($htmlOptions['key']) ? $htmlOptions['key'] : 'primaryKey';
+ if (is_array($selection)) {
+ foreach ($selection as $i => $item) {
+ if (is_object($item)) {
+ $selection[$i] = $item->$key;
+ }
+ }
+ } elseif (is_object($selection)) {
+ $selection = $selection->$key;
+ }
+
+ foreach ($listData as $key => $value) {
+ if (is_array($value)) {
+ $content .= '' . "\n";
+ } else {
+ $attributes = array('value' => (string)$key, 'encode' => !$raw);
+ if (!is_array($selection) && !strcmp($key, $selection) || is_array($selection) && in_array($key, $selection)) {
+ $attributes['selected'] = 'selected';
+ }
+ if (isset($options[$key])) {
+ $attributes = array_merge($attributes, $options[$key]);
+ }
+ $content .= static::tag('option', $attributes, $raw ? (string)$value : static::encode((string)$value)) . "\n";
+ }
+ }
+
+ unset($htmlOptions['key']);
+
+ return $content;
+ }
+
+ /**
+ * Renders the HTML tag attributes.
+ * Since version 1.1.5, attributes whose value is null will not be rendered.
+ * Special attributes, such as 'checked', 'disabled', 'readonly', will be rendered
+ * properly based on their corresponding boolean value.
+ * @param array $attributes attributes to be rendered
+ * @return string the rendering result
+ */
+ public static function renderAttributes($attributes)
+ {
+ static $specialAttributes = array(
+ 'async' => 1,
+ 'autofocus' => 1,
+ 'autoplay' => 1,
+ 'checked' => 1,
+ 'controls' => 1,
+ 'declare' => 1,
+ 'default' => 1,
+ 'defer' => 1,
+ 'disabled' => 1,
+ 'formnovalidate' => 1,
+ 'hidden' => 1,
+ 'ismap' => 1,
+ 'loop' => 1,
+ 'multiple' => 1,
+ 'muted' => 1,
+ 'nohref' => 1,
+ 'noresize' => 1,
+ 'novalidate' => 1,
+ 'open' => 1,
+ 'readonly' => 1,
+ 'required' => 1,
+ 'reversed' => 1,
+ 'scoped' => 1,
+ 'seamless' => 1,
+ 'selected' => 1,
+ 'typemustmatch' => 1,
+ );
+
+ if ($attributes === array()) {
+ return '';
+ }
+
+ $html = '';
+ if (isset($attributes['encode'])) {
+ $raw = !$attributes['encode'];
+ unset($attributes['encode']);
+ } else {
+ $raw = false;
+ }
+
+ foreach ($attributes as $name => $value) {
+ if (isset($specialAttributes[$name])) {
+ if ($value) {
+ $html .= ' ' . $name;
+ if (static::$renderSpecialAttributesValue) {
+ $html .= '="' . $name . '"';
+ }
+ }
+ } elseif ($value !== null) {
+ $html .= ' ' . $name . '="' . ($raw ? $value : static::encode($value)) . '"';
+ }
+ }
+ return $html;
+ }
}
From d2fcc69b34e27b258d8ac3a516106e4f5c359e63 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Fri, 8 Mar 2013 17:32:19 -0500
Subject: [PATCH 60/84] Html WIP
---
framework/util/ArrayHelper.php | 32 ++++++
framework/util/Html.php | 215 ++++++++++++++++++-----------------------
2 files changed, 125 insertions(+), 122 deletions(-)
diff --git a/framework/util/ArrayHelper.php b/framework/util/ArrayHelper.php
index 8bea4b6..fdcd691 100644
--- a/framework/util/ArrayHelper.php
+++ b/framework/util/ArrayHelper.php
@@ -7,6 +7,7 @@
namespace yii\util;
+use Yii;
use yii\base\InvalidParamException;
/**
@@ -279,4 +280,35 @@ class ArrayHelper
$args[] = &$array;
call_user_func_array('array_multisort', $args);
}
+
+
+ /**
+ * Encodes special characters in an array of strings into HTML entities.
+ * Both the array keys and values will be encoded if needed.
+ * If a value is an array, this method will also encode it recursively.
+ * @param array $data data to be encoded
+ * @param string $charset the charset that the data is using. If not set,
+ * [[\yii\base\Application::charset]] will be used.
+ * @return array the encoded data
+ * @see http://www.php.net/manual/en/function.htmlspecialchars.php
+ */
+ public static function htmlEncode($data, $charset = null)
+ {
+ if ($charset === null) {
+ $charset = Yii::$app->charset;
+ }
+ $d = array();
+ foreach ($data as $key => $value) {
+ if (is_string($key)) {
+ $key = htmlspecialchars($key, ENT_QUOTES, $charset);
+ }
+ if (is_string($value)) {
+ $value = htmlspecialchars($value, ENT_QUOTES, $charset);
+ } elseif (is_array($value)) {
+ $value = static::htmlEncode($value);
+ }
+ $d[$key] = $value;
+ }
+ return $d;
+ }
}
\ No newline at end of file
diff --git a/framework/util/Html.php b/framework/util/Html.php
index 11b4da6..3a3ee9c 100644
--- a/framework/util/Html.php
+++ b/framework/util/Html.php
@@ -16,183 +16,154 @@ use Yii;
class Html
{
/**
- * @var integer the counter for generating automatic input field names.
+ * @var boolean whether to close void (empty) elements. Defaults to true.
+ * @see voidElements
*/
- public static $count = 0;
+ public static $closeVoidElements = true;
/**
- * @var boolean whether to close single tags. Defaults to true. Can be set to false for HTML5.
+ * @var array list of void elements (element name => 1)
+ * @see http://www.w3.org/TR/html-markup/syntax.html#void-element
*/
- public static $closeSingleTags = true;
+ public static $voidElements = array(
+ 'area' => 1,
+ 'base' => 1,
+ 'br' => 1,
+ 'col' => 1,
+ 'command' => 1,
+ 'embed' => 1,
+ 'hr' => 1,
+ 'img' => 1,
+ 'input' => 1,
+ 'keygen' => 1,
+ 'link' => 1,
+ 'meta' => 1,
+ 'param' => 1,
+ 'source' => 1,
+ 'track' => 1,
+ 'wbr' => 1,
+ );
/**
* @var boolean whether to render special attributes value. Defaults to true. Can be set to false for HTML5.
*/
public static $renderSpecialAttributesValue = true;
+
/**
* Encodes special characters into HTML entities.
- * The {@link CApplication::charset application charset} will be used for encoding.
- * @param string $text data to be encoded
- * @return string the encoded data
+ * The [[yii\base\Application::charset|application charset]] will be used for encoding.
+ * @param string $content the content to be encoded
+ * @return string the encoded content
+ * @see decode
* @see http://www.php.net/manual/en/function.htmlspecialchars.php
*/
- public static function encode($text)
+ public static function encode($content)
{
- return htmlspecialchars($text, ENT_QUOTES, Yii::$app->charset);
+ return htmlspecialchars($content, ENT_QUOTES, Yii::$app->charset);
}
/**
* Decodes special HTML entities back to the corresponding characters.
- * This is the opposite of {@link encode()}.
- * @param string $text data to be decoded
- * @return string the decoded data
+ * This is the opposite of [[encode()]].
+ * @param string $content the content to be decoded
+ * @return string the decoded content
+ * @see encode
* @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php
*/
- public static function decode($text)
- {
- return htmlspecialchars_decode($text, ENT_QUOTES);
- }
-
- /**
- * Encodes special characters in an array of strings into HTML entities.
- * Both the array keys and values will be encoded if needed.
- * If a value is an array, this method will also encode it recursively.
- * The {@link CApplication::charset application charset} will be used for encoding.
- * @param array $data data to be encoded
- * @return array the encoded data
- * @see http://www.php.net/manual/en/function.htmlspecialchars.php
- */
- public static function encodeArray($data)
+ public static function decode($content)
{
- $d = array();
- foreach ($data as $key => $value) {
- if (is_string($key)) {
- $key = htmlspecialchars($key, ENT_QUOTES, Yii::$app->charset);
- }
- if (is_string($value)) {
- $value = htmlspecialchars($value, ENT_QUOTES, Yii::$app->charset);
- } elseif (is_array($value)) {
- $value = static::encodeArray($value);
- }
- $d[$key] = $value;
- }
- return $d;
+ return htmlspecialchars_decode($content, ENT_QUOTES);
}
/**
- * Generates an HTML element.
- * @param string $tag the tag name
- * @param array $htmlOptions the element attributes. The values will be HTML-encoded using {@link encode()}.
- * If an 'encode' attribute is given and its value is false,
- * the rest of the attribute values will NOT be HTML-encoded.
- * Since version 1.1.5, attributes whose value is null will not be rendered.
- * @param mixed $content the content to be enclosed between open and close element tags. It will not be HTML-encoded.
- * If false, it means there is no body content.
- * @param boolean $closeTag whether to generate the close tag.
- * @return string the generated HTML element tag
+ * Generates a complete HTML tag.
+ * @param string $name the tag name
+ * @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded.
+ * @param array $attributes the element attributes. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated HTML tag
+ * @see beginTag
+ * @see endTag
*/
- public static function tag($tag, $htmlOptions = array(), $content = false, $closeTag = true)
+ public static function tag($name, $content = '', $attributes = array())
{
- $html = '<' . $tag . static::renderAttributes($htmlOptions);
- if ($content === false) {
- return $closeTag && static::$closeSingleTags ? $html . ' />' : $html . '>';
+ $html = '<' . $name . static::renderAttributes($attributes);
+ if (isset(static::$voidElements[strtolower($name)])) {
+ return $html . (static::$closeVoidElements ? ' />' : '>');
} else {
- return $closeTag ? $html . '>' . $content . '' . $tag . '>' : $html . '>' . $content;
+ return $html . ">$content$name>";
}
}
/**
- * Generates an open HTML element.
- * @param string $tag the tag name
- * @param array $htmlOptions the element attributes. The values will be HTML-encoded using {@link encode()}.
- * If an 'encode' attribute is given and its value is false,
- * the rest of the attribute values will NOT be HTML-encoded.
- * Since version 1.1.5, attributes whose value is null will not be rendered.
- * @return string the generated HTML element tag
+ * Generates a start tag.
+ * @param string $name the tag name
+ * @param array $attributes the element attributes. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated start tag
+ * @see endTag
+ * @see tag
*/
- public static function openTag($tag, $htmlOptions = array())
+ public static function beginTag($name, $attributes = array())
{
- return '<' . $tag . static::renderAttributes($htmlOptions) . '>';
+ return '<' . $name . static::renderAttributes($attributes) . '>';
}
/**
- * Generates a close HTML element.
- * @param string $tag the tag name
- * @return string the generated HTML element tag
+ * Generates an end tag.
+ * @param string $name the tag name
+ * @return string the generated end tag
+ * @see beginTag
+ * @see tag
*/
- public static function closeTag($tag)
+ public static function endTag($name)
{
- return '' . $tag . '>';
+ return !static::$closeVoidElements && isset(static::$voidElements[strtolower($name)]) ? '' : "$name>";
}
/**
- * Encloses the given string within a CDATA tag.
- * @param string $text the string to be enclosed
+ * Encloses the given content within a CDATA tag.
+ * @param string $content the content to be enclosed within the CDATA tag
* @return string the CDATA tag with the enclosed content.
*/
- public static function cdata($text)
+ public static function cdata($content)
{
- return '';
+ return '';
}
/**
- * Generates a meta tag that can be inserted in the head section of HTML page.
- * @param string $content content attribute of the meta tag
- * @param string $name name attribute of the meta tag. If null, the attribute will not be generated
- * @param string $httpEquiv http-equiv attribute of the meta tag. If null, the attribute will not be generated
- * @param array $options other options in name-value pairs (e.g. 'scheme', 'lang')
- * @return string the generated meta tag
+ * Generates a style tag.
+ * @param string $content the style content
+ * @param array $attributes the attributes of the style tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * If the attributes does not contain "type", a default one with value "text/css" will be used.
+ * @return string the generated style tag
*/
- public static function metaTag($content, $name = null, $httpEquiv = null, $options = array())
+ public static function style($content, $attributes = array())
{
- if ($name !== null) {
- $options['name'] = $name;
+ if (!isset($attributes['type'])) {
+ $attributes['type'] = 'text/css';
}
- if ($httpEquiv !== null) {
- $options['http-equiv'] = $httpEquiv;
- }
- $options['content'] = $content;
- return static::tag('meta', $options);
+ return static::beginTag('style', $attributes)
+ . "\n/**/\n"
+ . static::endTag('style');
}
/**
- * Generates a link tag that can be inserted in the head section of HTML page.
- * Do not confuse this method with {@link link()}. The latter generates a hyperlink.
- * @param string $relation rel attribute of the link tag. If null, the attribute will not be generated.
- * @param string $type type attribute of the link tag. If null, the attribute will not be generated.
- * @param string $href href attribute of the link tag. If null, the attribute will not be generated.
- * @param string $media media attribute of the link tag. If null, the attribute will not be generated.
- * @param array $options other options in name-value pairs
- * @return string the generated link tag
- */
- public static function linkTag($relation = null, $type = null, $href = null, $media = null, $options = array())
- {
- if ($relation !== null) {
- $options['rel'] = $relation;
- }
- if ($type !== null) {
- $options['type'] = $type;
- }
- if ($href !== null) {
- $options['href'] = $href;
- }
- if ($media !== null) {
- $options['media'] = $media;
- }
- return static::tag('link', $options);
- }
-
- /**
- * Encloses the given CSS content with a CSS tag.
- * @param string $text the CSS content
- * @param string $media the media that this CSS should apply to.
- * @return string the CSS properly enclosed
+ * Generates a script tag.
+ * @param string $content the script content
+ * @param array $attributes the attributes of the script tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * If the attributes does not contain "type", a default one with value "text/javascript" will be used.
+ * @return string the generated script tag
*/
- public static function css($text, $media = '')
+ public static function script($content, $attributes = array())
{
- if ($media !== '') {
- $media = ' media="' . $media . '"';
+ if (!isset($attributes['type'])) {
+ $attributes['type'] = 'text/javascript';
}
- return "";
+ return static::beginTag('script', $attributes)
+ . "\n/**/\n"
+ . static::endTag('script');
}
/**
From 30d70be071e942b9eb70748b373f0a98f285ff8d Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Sun, 10 Mar 2013 22:00:26 -0400
Subject: [PATCH 61/84] Finished Html helper.
---
framework/base/Application.php | 13 +-
framework/db/ActiveRelation.php | 16 +-
framework/util/ArrayHelper.php | 40 +-
framework/util/Html.php | 1308 ++++++++++++++++-----------------------
framework/web/Application.php | 47 +-
framework/web/Controller.php | 4 +
framework/web/Request.php | 39 +-
todo.md | 1 +
8 files changed, 663 insertions(+), 805 deletions(-)
diff --git a/framework/base/Application.php b/framework/base/Application.php
index 3c1a1fb..a60bf90 100644
--- a/framework/base/Application.php
+++ b/framework/base/Application.php
@@ -382,6 +382,15 @@ class Application extends Module
}
/**
+ * Returns the URL manager for this application.
+ * @return \yii\web\UrlManager the URL manager for this application.
+ */
+ public function getUrlManager()
+ {
+ return $this->getComponent('urlManager');
+ }
+
+ /**
* Returns the internationalization (i18n) component
* @return \yii\i18n\I18N the internationalization component
*/
@@ -411,8 +420,8 @@ class Application extends Module
'i18n' => array(
'class' => 'yii\i18n\I18N',
),
- 'securityManager' => array(
- 'class' => 'yii\base\SecurityManager',
+ 'urlManager' => array(
+ 'class' => 'yii\web\UrlManager',
),
));
}
diff --git a/framework/db/ActiveRelation.php b/framework/db/ActiveRelation.php
index e1793bd..f1b198b 100644
--- a/framework/db/ActiveRelation.php
+++ b/framework/db/ActiveRelation.php
@@ -55,16 +55,16 @@ class ActiveRelation extends ActiveQuery
/**
* Specifies the relation associated with the pivot table.
* @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]].
- * @param callback $callback a PHP callback for customizing the relation associated with the pivot table.
+ * @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
* Its signature should be `function($query)`, where `$query` is the query to be customized.
* @return ActiveRelation the relation object itself.
*/
- public function via($relationName, $callback = null)
+ public function via($relationName, $callable = null)
{
$relation = $this->primaryModel->getRelation($relationName);
$this->via = array($relationName, $relation);
- if ($callback !== null) {
- call_user_func($callback, $relation);
+ if ($callable !== null) {
+ call_user_func($callable, $relation);
}
return $this;
}
@@ -75,11 +75,11 @@ class ActiveRelation extends ActiveQuery
* @param array $link the link between the pivot table and the table associated with [[primaryModel]].
* The keys of the array represent the columns in the pivot table, and the values represent the columns
* in the [[primaryModel]] table.
- * @param callback $callback a PHP callback for customizing the relation associated with the pivot table.
+ * @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
* Its signature should be `function($query)`, where `$query` is the query to be customized.
* @return ActiveRelation
*/
- public function viaTable($tableName, $link, $callback = null)
+ public function viaTable($tableName, $link, $callable = null)
{
$relation = new ActiveRelation(array(
'modelClass' => get_class($this->primaryModel),
@@ -89,8 +89,8 @@ class ActiveRelation extends ActiveQuery
'asArray' => true,
));
$this->via = $relation;
- if ($callback !== null) {
- call_user_func($callback, $relation);
+ if ($callable !== null) {
+ call_user_func($callable, $relation);
}
return $this;
}
diff --git a/framework/util/ArrayHelper.php b/framework/util/ArrayHelper.php
index fdcd691..ebf4b23 100644
--- a/framework/util/ArrayHelper.php
+++ b/framework/util/ArrayHelper.php
@@ -281,33 +281,59 @@ class ArrayHelper
call_user_func_array('array_multisort', $args);
}
-
/**
* Encodes special characters in an array of strings into HTML entities.
- * Both the array keys and values will be encoded if needed.
+ * Both the array keys and values will be encoded.
* If a value is an array, this method will also encode it recursively.
* @param array $data data to be encoded
+ * @param boolean $valuesOnly whether to encode array values only. If false,
+ * both the array keys and array values will be encoded.
* @param string $charset the charset that the data is using. If not set,
* [[\yii\base\Application::charset]] will be used.
* @return array the encoded data
* @see http://www.php.net/manual/en/function.htmlspecialchars.php
*/
- public static function htmlEncode($data, $charset = null)
+ public static function htmlEncode($data, $valuesOnly = false, $charset = null)
{
if ($charset === null) {
$charset = Yii::$app->charset;
}
$d = array();
foreach ($data as $key => $value) {
- if (is_string($key)) {
+ if (!$valuesOnly && is_string($key)) {
$key = htmlspecialchars($key, ENT_QUOTES, $charset);
}
if (is_string($value)) {
- $value = htmlspecialchars($value, ENT_QUOTES, $charset);
+ $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset);
+ } elseif (is_array($value)) {
+ $d[$key] = static::htmlEncode($value, $charset);
+ }
+ }
+ return $d;
+ }
+
+ /**
+ * Decodes HTML entities into the corresponding characters in an array of strings.
+ * Both the array keys and values will be decoded.
+ * If a value is an array, this method will also decode it recursively.
+ * @param array $data data to be decoded
+ * @param boolean $valuesOnly whether to decode array values only. If false,
+ * both the array keys and array values will be decoded.
+ * @return array the decoded data
+ * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php
+ */
+ public static function htmlDecode($data, $valuesOnly = false)
+ {
+ $d = array();
+ foreach ($data as $key => $value) {
+ if (!$valuesOnly && is_string($key)) {
+ $key = htmlspecialchars_decode($key, ENT_QUOTES);
+ }
+ if (is_string($value)) {
+ $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES);
} elseif (is_array($value)) {
- $value = static::htmlEncode($value);
+ $d[$key] = static::htmlDecode($value);
}
- $d[$key] = $value;
}
return $d;
}
diff --git a/framework/util/Html.php b/framework/util/Html.php
index 3a3ee9c..774a733 100644
--- a/framework/util/Html.php
+++ b/framework/util/Html.php
@@ -8,8 +8,11 @@
namespace yii\util;
use Yii;
+use yii\base\InvalidParamException;
/**
+ * Html provides a set of static methods for generating commonly used HTML tags.
+ *
* @author Qiang Xue
* @since 2.0
*/
@@ -22,6 +25,7 @@ class Html
public static $closeVoidElements = true;
/**
* @var array list of void elements (element name => 1)
+ * @see closeVoidElements
* @see http://www.w3.org/TR/html-markup/syntax.html#void-element
*/
public static $voidElements = array(
@@ -43,9 +47,78 @@ class Html
'wbr' => 1,
);
/**
- * @var boolean whether to render special attributes value. Defaults to true. Can be set to false for HTML5.
- */
- public static $renderSpecialAttributesValue = true;
+ * @var boolean whether to show the values of boolean attributes in element tags.
+ * If false, only the attribute names will be generated.
+ * @see booleanAttributes
+ */
+ public static $showBooleanAttributeValues = true;
+ /**
+ * @var array list of boolean attributes. The presence of a boolean attribute on
+ * an element represents the true value, and the absence of the attribute represents the false value.
+ * @see showBooleanAttributeValues
+ * @see http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes
+ */
+ public static $booleanAttributes = array(
+ 'async' => 1,
+ 'autofocus' => 1,
+ 'autoplay' => 1,
+ 'checked' => 1,
+ 'controls' => 1,
+ 'declare' => 1,
+ 'default' => 1,
+ 'defer' => 1,
+ 'disabled' => 1,
+ 'formnovalidate' => 1,
+ 'hidden' => 1,
+ 'ismap' => 1,
+ 'loop' => 1,
+ 'multiple' => 1,
+ 'muted' => 1,
+ 'nohref' => 1,
+ 'noresize' => 1,
+ 'novalidate' => 1,
+ 'open' => 1,
+ 'readonly' => 1,
+ 'required' => 1,
+ 'reversed' => 1,
+ 'scoped' => 1,
+ 'seamless' => 1,
+ 'selected' => 1,
+ 'typemustmatch' => 1,
+ );
+ /**
+ * @var array the preferred order of attributes in a tag. This mainly affects the order of the attributes
+ * that are rendered by [[renderAttributes()]].
+ */
+ public static $attributeOrder = array(
+ 'type',
+ 'id',
+ 'class',
+ 'name',
+ 'value',
+
+ 'href',
+ 'src',
+ 'action',
+ 'method',
+
+ 'selected',
+ 'checked',
+ 'readonly',
+ 'disabled',
+
+ 'size',
+ 'maxlength',
+ 'width',
+ 'height',
+ 'rows',
+ 'cols',
+
+ 'alt',
+ 'title',
+ 'rel',
+ 'media',
+ );
/**
@@ -78,6 +151,7 @@ class Html
* Generates a complete HTML tag.
* @param string $name the tag name
* @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded.
+ * If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks.
* @param array $attributes the element attributes. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated HTML tag
@@ -117,7 +191,7 @@ class Html
*/
public static function endTag($name)
{
- return !static::$closeVoidElements && isset(static::$voidElements[strtolower($name)]) ? '' : "$name>";
+ return "$name>";
}
/**
@@ -143,9 +217,7 @@ class Html
if (!isset($attributes['type'])) {
$attributes['type'] = 'text/css';
}
- return static::beginTag('style', $attributes)
- . "\n/**/\n"
- . static::endTag('style');
+ return static::tag('style', "\n/**/\n", $attributes);
}
/**
@@ -161,110 +233,77 @@ class Html
if (!isset($attributes['type'])) {
$attributes['type'] = 'text/javascript';
}
- return static::beginTag('script', $attributes)
- . "\n/**/\n"
- . static::endTag('script');
+ return static::tag('script', "\n/**/\n", $attributes);
}
/**
- * Registers a 'refresh' meta tag.
- * This method can be invoked anywhere in a view. It will register a 'refresh'
- * meta tag with {@link CClientScript} so that the page can be refreshed in
- * the specified seconds.
- * @param integer $seconds the number of seconds to wait before refreshing the page
- * @param string $url the URL to which the page should be redirected to. If empty, it means the current page.
- * @since 1.1.1
- */
- public static function refresh($seconds, $url = '')
- {
- $content = "$seconds";
- if ($url !== '') {
- $content .= ';' . static::normalizeUrl($url);
- }
- Yii::app()->clientScript->registerMetaTag($content, null, 'refresh');
- }
-
- /**
- * Links to the specified CSS file.
- * @param string $url the CSS URL
- * @param string $media the media that this CSS should apply to.
- * @return string the CSS link.
+ * Generates a link tag that refers to an external CSS file.
+ * @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]].
+ * @param array $attributes the attributes of the link tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated link tag
+ * @see url
*/
- public static function cssFile($url, $media = '')
+ public static function cssFile($url, $attributes = array())
{
- return CHtml::linkTag('stylesheet', 'text/css', $url, $media !== '' ? $media : null);
+ $attributes['rel'] = 'stylesheet';
+ $attributes['type'] = 'text/css';
+ $attributes['href'] = static::url($url);
+ return static::tag('link', '', $attributes);
}
/**
- * Encloses the given JavaScript within a script tag.
- * @param string $text the JavaScript to be enclosed
- * @return string the enclosed JavaScript
+ * Generates a script tag that refers to an external JavaScript file.
+ * @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]].
+ * @param array $attributes the attributes of the script tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated script tag
+ * @see url
*/
- public static function script($text)
+ public static function jsFile($url, $attributes = array())
{
- return "";
+ $attributes['type'] = 'text/javascript';
+ $attributes['src'] = static::url($url);
+ return static::tag('script', '', $attributes);
}
/**
- * Includes a JavaScript file.
- * @param string $url URL for the JavaScript file
- * @return string the JavaScript file tag
+ * Generates a form start tag.
+ * @param array|string $action the form action URL. This parameter will be processed by [[url()]].
+ * @param string $method form method, either "post" or "get" (case-insensitive)
+ * @param array $attributes the attributes of the form tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated form start tag.
+ * @see endForm
*/
- public static function scriptFile($url)
+ public static function beginForm($action = '', $method = 'post', $attributes = array())
{
- return '';
- }
+ $attributes['action'] = $url = static::url($action);
+ $attributes['method'] = $method;
- /**
- * Generates an opening form tag.
- * This is a shortcut to {@link beginForm}.
- * @param mixed $action the form action URL (see {@link normalizeUrl} for details about this parameter.)
- * @param string $method form method (e.g. post, get)
- * @param array $htmlOptions additional HTML attributes (see {@link tag}).
- * @return string the generated form tag.
- */
- public static function form($action = '', $method = 'post', $htmlOptions = array())
- {
- return static::beginForm($action, $method, $htmlOptions);
- }
+ $form = static::beginTag('form', $attributes);
- /**
- * Generates an opening form tag.
- * Note, only the open tag is generated. A close tag should be placed manually
- * at the end of the form.
- * @param mixed $action the form action URL (see {@link normalizeUrl} for details about this parameter.)
- * @param string $method form method (e.g. post, get)
- * @param array $htmlOptions additional HTML attributes (see {@link tag}).
- * @return string the generated form tag.
- * @see endForm
- */
- public static function beginForm($action = '', $method = 'post', $htmlOptions = array())
- {
- $htmlOptions['action'] = $url = static::normalizeUrl($action);
- $htmlOptions['method'] = $method;
- $form = static::tag('form', $htmlOptions, false, false);
+ // query parameters in the action are ignored for GET method
+ // we use hidden fields to add them back
$hiddens = array();
if (!strcasecmp($method, 'get') && ($pos = strpos($url, '?')) !== false) {
foreach (explode('&', substr($url, $pos + 1)) as $pair) {
if (($pos = strpos($pair, '=')) !== false) {
- $hiddens[] = static::hiddenField(urldecode(substr($pair, 0, $pos)), urldecode(substr($pair, $pos + 1)), array('id' => false));
+ $hiddens[] = static::hiddenInput(urldecode(substr($pair, 0, $pos)), urldecode(substr($pair, $pos + 1)));
} else {
- $hiddens[] = static::hiddenField(urldecode($pair), '', array('id' => false));
+ $hiddens[] = static::hiddenInput(urldecode($pair), '');
}
}
}
- $request = Yii::app()->request;
- if ($request->enableCsrfValidation && !strcasecmp($method, 'post')) {
- $hiddens[] = static::hiddenField($request->csrfTokenName, $request->getCsrfToken(), array('id' => false));
- }
if ($hiddens !== array()) {
- $form .= "\n" . static::tag('div', array('style' => 'display:none'), implode("\n", $hiddens));
+ $form .= "\n" . implode("\n", $hiddens);
}
+
return $form;
}
/**
- * Generates a closing form tag.
+ * Generates a form end tag.
* @return string the generated tag
* @see beginForm
*/
@@ -275,854 +314,599 @@ class Html
/**
* Generates a hyperlink tag.
- * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code such as an image tag.
- * @param mixed $url a URL or an action route that can be used to create a URL.
- * See {@link normalizeUrl} for more details about how to specify this parameter.
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code
+ * such as an image tag. If this is is coming from end users, you should consider [[encode()]]
+ * it to prevent XSS attacks.
+ * @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]]
+ * and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute
+ * will not be generated.
+ * @param array $attributes the attributes of the hyperlink tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated hyperlink
- * @see normalizeUrl
- * @see clientChange
+ * @see url
*/
- public static function link($text, $url = '#', $htmlOptions = array())
+ public static function a($text, $url = null, $attributes = array())
{
- if ($url !== '') {
- $htmlOptions['href'] = static::normalizeUrl($url);
+ if ($url !== null) {
+ $attributes['href'] = static::url($url);
}
- static::clientChange('click', $htmlOptions);
- return static::tag('a', $htmlOptions, $text);
+ return static::tag('a', $text, $attributes);
}
/**
- * Generates a mailto link.
- * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code such as an image tag.
- * @param string $email email address. If this is empty, the first parameter (link body) will be treated as the email address.
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * Generates a mailto hyperlink.
+ * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code
+ * such as an image tag. If this is is coming from end users, you should consider [[encode()]]
+ * it to prevent XSS attacks.
+ * @param string $email email address. If this is null, the first parameter (link body) will be treated
+ * as the email address and used.
+ * @param array $attributes the attributes of the hyperlink tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated mailto link
- * @see clientChange
*/
- public static function mailto($text, $email = '', $htmlOptions = array())
+ public static function mailto($text, $email = null, $attributes = array())
{
- if ($email === '') {
- $email = $text;
- }
- return static::link($text, 'mailto:' . $email, $htmlOptions);
+ return static::a($text, 'mailto:' . ($email === null ? $text : $email), $attributes);
}
/**
* Generates an image tag.
- * @param string $src the image URL
- * @param string $alt the alternative text display
- * @param array $htmlOptions additional HTML attributes (see {@link tag}).
+ * @param string $src the image URL. This parameter will be processed by [[url()]].
+ * @param array $attributes the attributes of the image tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated image tag
*/
- public static function image($src, $alt = '', $htmlOptions = array())
+ public static function img($src, $attributes = array())
{
- $htmlOptions['src'] = $src;
- $htmlOptions['alt'] = $alt;
- return static::tag('img', $htmlOptions);
+ $attributes['src'] = static::url($src);
+ if (!isset($attributes['alt'])) {
+ $attributes['alt'] = '';
+ }
+ return static::tag('img', null, $attributes);
}
/**
- * Generates a button.
- * @param string $label the button label
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
- * @return string the generated button tag
- * @see clientChange
+ * Generates a label tag.
+ * @param string $content label text. It will NOT be HTML-encoded. Therefore you can pass in HTML code
+ * such as an image tag. If this is is coming from end users, you should consider [[encode()]]
+ * it to prevent XSS attacks.
+ * @param string $for the ID of the HTML element that this label is associated with.
+ * If this is null, the "for" attribute will not be generated.
+ * @param array $attributes the attributes of the label tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated label tag
*/
- public static function button($name, $label = 'button', $htmlOptions = array())
+ public static function label($content, $for = null, $attributes = array())
{
- if (!isset($htmlOptions['name'])) {
- if (!array_key_exists('name', $htmlOptions)) {
- $htmlOptions['name'] = static::ID_PREFIX . static::$count++;
- }
- }
- if (!isset($htmlOptions['type'])) {
- $htmlOptions['type'] = 'button';
- }
- if (!isset($htmlOptions['value'])) {
- $htmlOptions['value'] = $label;
- }
- static::clientChange('click', $htmlOptions);
- return static::tag('input', $htmlOptions);
+ $attributes['for'] = $for;
+ return static::tag('label', $content, $attributes);
}
/**
- * Generates a button using HTML button tag.
- * This method is similar to {@link button} except that it generates a 'button'
- * tag instead of 'input' tag.
- * @param string $label the button label. Note that this value will be directly inserted in the button element
- * without being HTML-encoded.
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * Generates a button tag.
+ * @param string $name the name attribute. If it is null, the name attribute will not be generated.
+ * @param string $value the value attribute. If it is null, the value attribute will not be generated.
+ * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded.
+ * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users,
+ * you should consider [[encode()]] it to prevent XSS attacks.
+ * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * If the attributes does not contain "type", a default one with value "button" will be used.
* @return string the generated button tag
- * @see clientChange
*/
- public static function htmlButton($label = 'button', $htmlOptions = array())
+ public static function button($name = null, $value = null, $content = 'Button', $attributes = array())
{
- if (!isset($htmlOptions['name'])) {
- $htmlOptions['name'] = static::ID_PREFIX . static::$count++;
- }
- if (!isset($htmlOptions['type'])) {
- $htmlOptions['type'] = 'button';
+ $attributes['name'] = $name;
+ $attributes['value'] = $value;
+ if (!isset($attributes['type'])) {
+ $attributes['type'] = 'button';
}
- static::clientChange('click', $htmlOptions);
- return static::tag('button', $htmlOptions, $label);
+ return static::tag('button', $content, $attributes);
}
/**
- * Generates a submit button.
- * @param string $label the button label
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
- * @return string the generated button tag
- * @see clientChange
+ * Generates a submit button tag.
+ * @param string $name the name attribute. If it is null, the name attribute will not be generated.
+ * @param string $value the value attribute. If it is null, the value attribute will not be generated.
+ * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded.
+ * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users,
+ * you should consider [[encode()]] it to prevent XSS attacks.
+ * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated submit button tag
*/
- public static function submitButton($label = 'submit', $htmlOptions = array())
+ public static function submitButton($name = null, $value = null, $content = 'Submit', $attributes = array())
{
- $htmlOptions['type'] = 'submit';
- return static::button($label, $htmlOptions);
+ $attributes['type'] = 'submit';
+ return static::button($name, $value, $content, $attributes);
}
/**
- * Generates a reset button.
- * @param string $label the button label
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
- * @return string the generated button tag
- * @see clientChange
+ * Generates a reset button tag.
+ * @param string $name the name attribute. If it is null, the name attribute will not be generated.
+ * @param string $value the value attribute. If it is null, the value attribute will not be generated.
+ * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded.
+ * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users,
+ * you should consider [[encode()]] it to prevent XSS attacks.
+ * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated reset button tag
*/
- public static function resetButton($label = 'reset', $htmlOptions = array())
+ public static function resetButton($name = null, $value = null, $content = 'Reset', $attributes = array())
{
- $htmlOptions['type'] = 'reset';
- return static::button($label, $htmlOptions);
+ $attributes['type'] = 'reset';
+ return static::button($name, $value, $content, $attributes);
}
/**
- * Generates an image submit button.
- * @param string $src the image URL
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * Generates an input type of the given type.
+ * @param string $type the type attribute.
+ * @param string $name the name attribute. If it is null, the name attribute will not be generated.
+ * @param string $value the value attribute. If it is null, the value attribute will not be generated.
+ * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated input tag
+ */
+ public static function input($type, $name = null, $value = null, $attributes = array())
+ {
+ $attributes['type'] = $type;
+ $attributes['name'] = $name;
+ $attributes['value'] = $value;
+ return static::tag('input', null, $attributes);
+ }
+
+ /**
+ * Generates an input button.
+ * @param string $name the name attribute.
+ * @param string $value the value attribute. If it is null, the value attribute will not be generated.
+ * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated button tag
- * @see clientChange
*/
- public static function imageButton($src, $htmlOptions = array())
+ public static function buttonInput($name, $value = 'Button', $attributes = array())
{
- $htmlOptions['src'] = $src;
- $htmlOptions['type'] = 'image';
- return static::button('submit', $htmlOptions);
+ return static::input('button', $name, $value, $attributes);
}
/**
- * Generates a link submit button.
- * @param string $label the button label
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
+ * Generates a submit input button.
+ * @param string $name the name attribute. If it is null, the name attribute will not be generated.
+ * @param string $value the value attribute. If it is null, the value attribute will not be generated.
+ * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated button tag
- * @see clientChange
*/
- public static function linkButton($label = 'submit', $htmlOptions = array())
+ public static function submitInput($name = null, $value = 'Submit', $attributes = array())
{
- if (!isset($htmlOptions['submit'])) {
- $htmlOptions['submit'] = isset($htmlOptions['href']) ? $htmlOptions['href'] : '';
- }
- return static::link($label, '#', $htmlOptions);
+ return static::input('submit', $name, $value, $attributes);
}
/**
- * Generates a label tag.
- * @param string $label label text. Note, you should HTML-encode the text if needed.
- * @param string $for the ID of the HTML element that this label is associated with.
- * If this is false, the 'for' attribute for the label tag will not be rendered.
- * @param array $htmlOptions additional HTML attributes.
- * The following HTML option is recognized:
- *
- *
required: if this is set and is true, the label will be styled
- * with CSS class 'required' (customizable with CHtml::$requiredCss),
- * and be decorated with {@link CHtml::beforeRequiredLabel} and
- * {@link CHtml::afterRequiredLabel}.
- *
- * @return string the generated label tag
+ * Generates a reset input button.
+ * @param string $name the name attribute. If it is null, the name attribute will not be generated.
+ * @param string $value the value attribute. If it is null, the value attribute will not be generated.
+ * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated button tag
*/
- public static function label($label, $for, $htmlOptions = array())
+ public static function resetInput($name = null, $value = 'Reset', $attributes = array())
{
- if ($for === false) {
- unset($htmlOptions['for']);
- } else {
- $htmlOptions['for'] = $for;
- }
- if (isset($htmlOptions['required'])) {
- if ($htmlOptions['required']) {
- if (isset($htmlOptions['class'])) {
- $htmlOptions['class'] .= ' ' . static::$requiredCss;
- } else {
- $htmlOptions['class'] = static::$requiredCss;
- }
- $label = static::$beforeRequiredLabel . $label . static::$afterRequiredLabel;
- }
- unset($htmlOptions['required']);
- }
- return static::tag('label', $htmlOptions, $label);
+ return static::input('reset', $name, $value, $attributes);
}
/**
- * Generates a text field input.
- * @param string $name the input name
- * @param string $value the input value
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
- * @return string the generated input field
- * @see clientChange
- * @see inputField
+ * Generates a text input field.
+ * @param string $name the name attribute.
+ * @param string $value the value attribute. If it is null, the value attribute will not be generated.
+ * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated button tag
*/
- public static function textField($name, $value = '', $htmlOptions = array())
+ public static function textInput($name, $value = null, $attributes = array())
{
- static::clientChange('change', $htmlOptions);
- return static::inputField('text', $name, $value, $htmlOptions);
+ return static::input('text', $name, $value, $attributes);
}
/**
- * Generates a hidden input.
- * @param string $name the input name
- * @param string $value the input value
- * @param array $htmlOptions additional HTML attributes (see {@link tag}).
- * @return string the generated input field
- * @see inputField
+ * Generates a hidden input field.
+ * @param string $name the name attribute.
+ * @param string $value the value attribute. If it is null, the value attribute will not be generated.
+ * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated button tag
*/
- public static function hiddenField($name, $value = '', $htmlOptions = array())
+ public static function hiddenInput($name, $value = null, $attributes = array())
{
- return static::inputField('hidden', $name, $value, $htmlOptions);
+ return static::input('hidden', $name, $value, $attributes);
}
/**
- * Generates a password field input.
- * @param string $name the input name
- * @param string $value the input value
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
- * @return string the generated input field
- * @see clientChange
- * @see inputField
+ * Generates a password input field.
+ * @param string $name the name attribute.
+ * @param string $value the value attribute. If it is null, the value attribute will not be generated.
+ * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated button tag
*/
- public static function passwordField($name, $value = '', $htmlOptions = array())
+ public static function passwordInput($name, $value = null, $attributes = array())
{
- static::clientChange('change', $htmlOptions);
- return static::inputField('password', $name, $value, $htmlOptions);
+ return static::input('password', $name, $value, $attributes);
}
/**
- * Generates a file input.
- * Note, you have to set the enclosing form's 'enctype' attribute to be 'multipart/form-data'.
- * After the form is submitted, the uploaded file information can be obtained via $_FILES[$name] (see
- * PHP documentation).
- * @param string $name the input name
- * @param string $value the input value
- * @param array $htmlOptions additional HTML attributes (see {@link tag}).
- * @return string the generated input field
- * @see inputField
+ * Generates a file input field.
+ * To use a file input field, you should set the enclosing form's "enctype" attribute to
+ * be "multipart/form-data". After the form is submitted, the uploaded file information
+ * can be obtained via $_FILES[$name] (see PHP documentation).
+ * @param string $name the name attribute.
+ * @param string $value the value attribute. If it is null, the value attribute will not be generated.
+ * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated button tag
*/
- public static function fileField($name, $value = '', $htmlOptions = array())
+ public static function fileInput($name, $value = null, $attributes = array())
{
- return static::inputField('file', $name, $value, $htmlOptions);
+ return static::input('file', $name, $value, $attributes);
}
/**
* Generates a text area input.
* @param string $name the input name
- * @param string $value the input value
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
- * @return string the generated text area
- * @see clientChange
- * @see inputField
+ * @param string $value the input value. Note that it will be encoded using [[encode()]].
+ * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @return string the generated text area tag
*/
- public static function textArea($name, $value = '', $htmlOptions = array())
+ public static function textarea($name, $value = '', $attributes = array())
{
- $htmlOptions['name'] = $name;
- if (!isset($htmlOptions['id'])) {
- $htmlOptions['id'] = static::getIdByName($name);
- } elseif ($htmlOptions['id'] === false) {
- unset($htmlOptions['id']);
- }
- static::clientChange('change', $htmlOptions);
- return static::tag('textarea', $htmlOptions, isset($htmlOptions['encode']) && !$htmlOptions['encode'] ? $value : static::encode($value));
+ $attributes['name'] = $name;
+ return static::tag('textarea', static::encode($value), $attributes);
}
/**
- * Generates a radio button.
- * @param string $name the input name
- * @param boolean $checked whether the radio button is checked
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
- * Since version 1.1.2, a special option named 'uncheckValue' is available that can be used to specify
- * the value returned when the radio button is not checked. When set, a hidden field is rendered so that
- * when the radio button is not checked, we can still obtain the posted uncheck value.
- * If 'uncheckValue' is not set or set to NULL, the hidden field will not be rendered.
- * @return string the generated radio button
- * @see clientChange
- * @see inputField
+ * Generates a radio button input.
+ * @param string $name the name attribute.
+ * @param string $value the value attribute. If it is null, the value attribute will not be generated.
+ * @param boolean $checked whether the radio button should be checked.
+ * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned. The following attribute
+ * will be specially handled and not put in the resulting tag:
+ *
+ * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute
+ * is present, a hidden input will be generated so that if the radio button is not checked and is submitted,
+ * the value of this attribute will still be submitted to the server via the hidden input.
+ *
+ * @return string the generated radio button tag
*/
- public static function radioButton($name, $checked = false, $htmlOptions = array())
+ public static function radio($name, $value = '1', $checked = false, $attributes = array())
{
- if ($checked) {
- $htmlOptions['checked'] = 'checked';
- } else {
- unset($htmlOptions['checked']);
- }
- $value = isset($htmlOptions['value']) ? $htmlOptions['value'] : 1;
- static::clientChange('click', $htmlOptions);
-
- if (array_key_exists('uncheckValue', $htmlOptions)) {
- $uncheck = $htmlOptions['uncheckValue'];
- unset($htmlOptions['uncheckValue']);
- } else {
- $uncheck = null;
- }
-
- if ($uncheck !== null) {
+ $attributes['checked'] = $checked;
+ if (isset($attributes['uncheck'])) {
// add a hidden field so that if the radio button is not selected, it still submits a value
- if (isset($htmlOptions['id']) && $htmlOptions['id'] !== false) {
- $uncheckOptions = array('id' => static::ID_PREFIX . $htmlOptions['id']);
- } else {
- $uncheckOptions = array('id' => false);
- }
- $hidden = static::hiddenField($name, $uncheck, $uncheckOptions);
+ $hidden = static::hiddenInput($name, $attributes['uncheck']);
+ unset($attributes['uncheck']);
} else {
$hidden = '';
}
-
- // add a hidden field so that if the radio button is not selected, it still submits a value
- return $hidden . static::inputField('radio', $name, $value, $htmlOptions);
+ return $hidden . static::input('radio', $name, $value, $attributes);
}
/**
- * Generates a check box.
- * @param string $name the input name
- * @param boolean $checked whether the check box is checked
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are also recognized (see {@link clientChange} and {@link tag} for more details.)
- * Since version 1.1.2, a special option named 'uncheckValue' is available that can be used to specify
- * the value returned when the checkbox is not checked. When set, a hidden field is rendered so that
- * when the checkbox is not checked, we can still obtain the posted uncheck value.
- * If 'uncheckValue' is not set or set to NULL, the hidden field will not be rendered.
- * @return string the generated check box
- * @see clientChange
- * @see inputField
+ * Generates a checkbox input.
+ * @param string $name the name attribute.
+ * @param string $value the value attribute. If it is null, the value attribute will not be generated.
+ * @param boolean $checked whether the checkbox should be checked.
+ * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned. The following attribute
+ * will be specially handled and not put in the resulting tag:
+ *
+ * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute
+ * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted,
+ * the value of this attribute will still be submitted to the server via the hidden input.
+ *
+ * @return string the generated checkbox tag
*/
- public static function checkBox($name, $checked = false, $htmlOptions = array())
+ public static function checkbox($name, $value = '1', $checked = false, $attributes = array())
{
- if ($checked) {
- $htmlOptions['checked'] = 'checked';
- } else {
- unset($htmlOptions['checked']);
- }
- $value = isset($htmlOptions['value']) ? $htmlOptions['value'] : 1;
- static::clientChange('click', $htmlOptions);
-
- if (array_key_exists('uncheckValue', $htmlOptions)) {
- $uncheck = $htmlOptions['uncheckValue'];
- unset($htmlOptions['uncheckValue']);
- } else {
- $uncheck = null;
- }
-
- if ($uncheck !== null) {
- // add a hidden field so that if the check box is not checked, it still submits a value
- if (isset($htmlOptions['id']) && $htmlOptions['id'] !== false) {
- $uncheckOptions = array('id' => static::ID_PREFIX . $htmlOptions['id']);
- } else {
- $uncheckOptions = array('id' => false);
- }
- $hidden = static::hiddenField($name, $uncheck, $uncheckOptions);
+ $attributes['checked'] = $checked;
+ if (isset($attributes['uncheck'])) {
+ // add a hidden field so that if the checkbox is not selected, it still submits a value
+ $hidden = static::hiddenInput($name, $attributes['uncheck']);
+ unset($attributes['uncheck']);
} else {
$hidden = '';
}
-
- // add a hidden field so that if the check box is not checked, it still submits a value
- return $hidden . static::inputField('checkbox', $name, $value, $htmlOptions);
+ return $hidden . static::input('checkbox', $name, $value, $attributes);
}
/**
- * Generates a drop down list.
+ * Generates a drop-down list.
* @param string $name the input name
- * @param string $select the selected value
- * @param array $data data for generating the list options (value=>display).
- * You may use {@link listData} to generate this data.
- * Please refer to {@link listOptions} on how this data is used to generate the list options.
- * Note, the values and labels will be automatically HTML-encoded by this method.
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are recognized. See {@link clientChange} and {@link tag} for more details.
- * In addition, the following options are also supported specifically for dropdown list:
- *
- *
encode: boolean, specifies whether to encode the values. Defaults to true.
- *
prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.
- *
empty: string, specifies the text corresponding to empty selection. Its value is empty.
- * The 'empty' option can also be an array of value-label pairs.
- * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.
- *
options: array, specifies additional attributes for each OPTION tag.
- * The array keys must be the option values, and the array values are the extra
- * OPTION tag attributes in the name-value pairs. For example,
- *
- * Since 1.1.13, a special option named 'unselectValue' is available. It can be used to set the value
- * that will be returned when no option is selected in multiple mode. When set, a hidden field is
- * rendered so that if no option is selected in multiple mode, we can still obtain the posted
- * unselect value. If 'unselectValue' is not set or set to NULL, the hidden field will not be rendered.
- * @return string the generated drop down list
- * @see clientChange
- * @see inputField
- * @see listData
+ * @param array $items the option data items. The array keys are option values, and the array values
+ * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
+ * For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
+ * If you have a list of data models, you may convert them into the format described above using
+ * [[\yii\util\ArrayHelper::map()]].
+ *
+ * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
+ * the labels will also be HTML-encoded.
+ * @param string $selection the selected value
+ * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned. The following attribute
+ * will be specially handled and not put in the resulting tag:
+ *
+ * - prompt: string, a prompt text to be displayed as the first option;
+ * - options: array, the attributes for the option tags. The array keys must be valid option values,
+ * and the array values are the extra attributes for the corresponding option tags. For example,
+ *
+ * ~~~
+ * array(
+ * 'value1' => array('disabled' => true),
+ * 'value2' => array('label' => 'value 2'),
+ * );
+ * ~~~
+ *
+ * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options',
+ * except that the array keys represent the optgroup labels specified in $items.
+ * @return string the generated drop-down list tag
*/
- public static function dropDownList($name, $select, $data, $htmlOptions = array())
+ public static function dropDownList($name, $items, $selection = null, $attributes = array())
{
- $htmlOptions['name'] = $name;
-
- if (!isset($htmlOptions['id'])) {
- $htmlOptions['id'] = static::getIdByName($name);
- } elseif ($htmlOptions['id'] === false) {
- unset($htmlOptions['id']);
- }
-
- static::clientChange('change', $htmlOptions);
- $options = "\n" . static::listOptions($select, $data, $htmlOptions);
- $hidden = '';
-
- if (isset($htmlOptions['multiple'])) {
- if (substr($htmlOptions['name'], -2) !== '[]') {
- $htmlOptions['name'] .= '[]';
- }
-
- if (isset($htmlOptions['unselectValue'])) {
- $hiddenOptions = isset($htmlOptions['id']) ? array('id' => static::ID_PREFIX . $htmlOptions['id']) : array('id' => false);
- $hidden = static::hiddenField(substr($htmlOptions['name'], 0, -2), $htmlOptions['unselectValue'], $hiddenOptions);
- unset($htmlOptions['unselectValue']);
- }
- }
- // add a hidden field so that if the option is not selected, it still submits a value
- return $hidden . static::tag('select', $htmlOptions, $options);
+ $attributes['name'] = $name;
+ $options = static::renderOptions($items, $selection, $attributes);
+ return static::tag('select', "\n" . $options . "\n", $attributes);
}
/**
* Generates a list box.
* @param string $name the input name
- * @param mixed $select the selected value(s). This can be either a string for single selection or an array for multiple selections.
- * @param array $data data for generating the list options (value=>display)
- * You may use {@link listData} to generate this data.
- * Please refer to {@link listOptions} on how this data is used to generate the list options.
- * Note, the values and labels will be automatically HTML-encoded by this method.
- * @param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
- * attributes are also recognized. See {@link clientChange} and {@link tag} for more details.
- * In addition, the following options are also supported specifically for list box:
- *
- *
encode: boolean, specifies whether to encode the values. Defaults to true.
- *
prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.
- *
empty: string, specifies the text corresponding to empty selection. Its value is empty.
- * The 'empty' option can also be an array of value-label pairs.
- * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.
- *
options: array, specifies additional attributes for each OPTION tag.
- * The array keys must be the option values, and the array values are the extra
- * OPTION tag attributes in the name-value pairs. For example,
- *
- * @return string the generated list box
- * @see clientChange
- * @see inputField
- * @see listData
- */
- public static function listBox($name, $select, $data, $htmlOptions = array())
- {
- if (!isset($htmlOptions['size'])) {
- $htmlOptions['size'] = 4;
+ * @param array $items the option data items. The array keys are option values, and the array values
+ * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
+ * For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
+ * If you have a list of data models, you may convert them into the format described above using
+ * [[\yii\util\ArrayHelper::map()]].
+ *
+ * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
+ * the labels will also be HTML-encoded.
+ * @param string|array $selection the selected value(s)
+ * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned. The following attribute
+ * will be specially handled and not put in the resulting tag:
+ *
+ * - prompt: string, a prompt text to be displayed as the first option;
+ * - options: array, the attributes for the option tags. The array keys must be valid option values,
+ * and the array values are the extra attributes for the corresponding option tags. For example,
+ *
+ * ~~~
+ * array(
+ * 'value1' => array('disabled' => true),
+ * 'value2' => array('label' => 'value 2'),
+ * );
+ * ~~~
+ *
+ * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options',
+ * except that the array keys represent the optgroup labels specified in $items.
+ * - unselect: string, the value that will be submitted when no option is selected.
+ * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple
+ * mode, we can still obtain the posted unselect value.
+ * @return string the generated list box tag
+ */
+ public static function listBox($name, $items, $selection = null, $attributes = array())
+ {
+ if (!isset($attributes['size'])) {
+ $attributes['size'] = 4;
+ }
+ if (isset($attributes['unselect'])) {
+ // add a hidden field so that if the list box has no option being selected, it still submits a value
+ $hidden = static::hiddenInput($name, $attributes['unselect']);
+ unset($attributes['unselect']);
+ } else {
+ $hidden = '';
}
- if (isset($htmlOptions['multiple'])) {
- if (substr($name, -2) !== '[]') {
- $name .= '[]';
- }
+ if (isset($attributes['multiple']) && $attributes['multiple'] && substr($name, -2) !== '[]') {
+ $name .= '[]';
}
- return static::dropDownList($name, $select, $data, $htmlOptions);
+ $attributes['name'] = $name;
+ $options = static::renderOptions($items, $selection, $attributes);
+ return $hidden . static::tag('select', "\n" . $options . "\n", $attributes);
}
/**
- * Generates a check box list.
- * A check box list allows multiple selection, like {@link listBox}.
- * As a result, the corresponding POST value is an array.
- * @param string $name name of the check box list. You can use this name to retrieve
- * the selected value(s) once the form is submitted.
- * @param mixed $select selection of the check boxes. This can be either a string
- * for single selection or an array for multiple selections.
- * @param array $data value-label pairs used to generate the check box list.
- * Note, the values will be automatically HTML-encoded, while the labels will not.
- * @param array $htmlOptions additional HTML options. The options will be applied to
- * each checkbox input. The following special options are recognized:
- *
- *
template: string, specifies how each checkbox is rendered. Defaults
- * to "{input} {label}", where "{input}" will be replaced by the generated
- * check box input tag while "{label}" be replaced by the corresponding check box label.
- *
separator: string, specifies the string that separates the generated check boxes.
- *
checkAll: string, specifies the label for the "check all" checkbox.
- * If this option is specified, a 'check all' checkbox will be displayed. Clicking on
- * this checkbox will cause all checkboxes checked or unchecked.
- *
checkAllLast: boolean, specifies whether the 'check all' checkbox should be
- * displayed at the end of the checkbox list. If this option is not set (default)
- * or is false, the 'check all' checkbox will be displayed at the beginning of
- * the checkbox list.
- *
labelOptions: array, specifies the additional HTML attributes to be rendered
- * for every label tag in the list.
- *
container: string, specifies the checkboxes enclosing tag. Defaults to 'span'.
- * If the value is an empty string, no enclosing tag will be generated
- *
baseID: string, specifies the base ID prefix to be used for checkboxes in the list.
- * This option is available since version 1.1.13.
- *
- * @return string the generated check box list
+ * Generates a list of checkboxes.
+ * A checkbox list allows multiple selection, like [[listBox()]].
+ * As a result, the corresponding submitted value is an array.
+ * @param string $name the name attribute of each checkbox.
+ * @param array $items the data item used to generate the checkboxes.
+ * The array keys are the labels, while the array values are the corresponding checkbox values.
+ * Note that the labels will NOT be HTML-encoded, while the values will.
+ * @param string|array $selection the selected value(s).
+ * @param callable $formatter a callback that can be used to customize the generation of the HTML code
+ * corresponding to a single checkbox. The signature of this callback must be:
+ *
+ * ~~~
+ * function ($index, $label, $name, $value, $checked)
+ * ~~~
+ *
+ * where $index is the zero-based index of the checkbox in the whole list; $label
+ * is the label for the checkbox; and $name, $value and $checked represent the name,
+ * value and the checked status of the checkbox input.
+ * @return string the generated checkbox list
*/
- public static function checkBoxList($name, $select, $data, $htmlOptions = array())
+ public static function checkboxList($name, $items, $selection = null, $formatter = null)
{
- $template = isset($htmlOptions['template']) ? $htmlOptions['template'] : '{input} {label}';
- $separator = isset($htmlOptions['separator']) ? $htmlOptions['separator'] : " \n";
- $container = isset($htmlOptions['container']) ? $htmlOptions['container'] : 'span';
- unset($htmlOptions['template'], $htmlOptions['separator'], $htmlOptions['container']);
-
if (substr($name, -2) !== '[]') {
$name .= '[]';
}
- if (isset($htmlOptions['checkAll'])) {
- $checkAllLabel = $htmlOptions['checkAll'];
- $checkAllLast = isset($htmlOptions['checkAllLast']) && $htmlOptions['checkAllLast'];
- }
- unset($htmlOptions['checkAll'], $htmlOptions['checkAllLast']);
-
- $labelOptions = isset($htmlOptions['labelOptions']) ? $htmlOptions['labelOptions'] : array();
- unset($htmlOptions['labelOptions']);
-
- $items = array();
- $baseID = isset($htmlOptions['baseID']) ? $htmlOptions['baseID'] : static::getIdByName($name);
- unset($htmlOptions['baseID']);
- $id = 0;
- $checkAll = true;
-
- foreach ($data as $value => $label) {
- $checked = !is_array($select) && !strcmp($value, $select) || is_array($select) && in_array($value, $select);
- $checkAll = $checkAll && $checked;
- $htmlOptions['value'] = $value;
- $htmlOptions['id'] = $baseID . '_' . $id++;
- $option = static::checkBox($name, $checked, $htmlOptions);
- $label = static::label($label, $htmlOptions['id'], $labelOptions);
- $items[] = strtr($template, array('{input}' => $option, '{label}' => $label));
- }
-
- if (isset($checkAllLabel)) {
- $htmlOptions['value'] = 1;
- $htmlOptions['id'] = $id = $baseID . '_all';
- $option = static::checkBox($id, $checkAll, $htmlOptions);
- $label = static::label($checkAllLabel, $id, $labelOptions);
- $item = strtr($template, array('{input}' => $option, '{label}' => $label));
- if ($checkAllLast) {
- $items[] = $item;
+ $lines = array();
+ $index = 0;
+ foreach ($items as $value => $label) {
+ $checked = $selection !== null &&
+ (!is_array($selection) && !strcmp($value, $selection)
+ || is_array($selection) && in_array($value, $selection));
+ if ($formatter !== null) {
+ $lines[] = call_user_func($formatter, $index, $label, $name, $value, $checked);
} else {
- array_unshift($items, $item);
+ $lines[] = static::label(static::checkbox($name, $value, $checked) . ' ' . $label);
}
- $name = strtr($name, array('[' => '\\[', ']' => '\\]'));
- $js = <<getClientScript();
- $cs->registerCoreScript('jquery');
- $cs->registerScript($id, $js);
+ $index++;
}
- if (empty($container)) {
- return implode($separator, $items);
- } else {
- return static::tag($container, array('id' => $baseID), implode($separator, $items));
- }
+ return implode("\n", $lines);
}
/**
- * Generates a radio button list.
- * A radio button list is like a {@link checkBoxList check box list}, except that
- * it only allows single selection.
- * @param string $name name of the radio button list. You can use this name to retrieve
- * the selected value(s) once the form is submitted.
- * @param string $select selection of the radio buttons.
- * @param array $data value-label pairs used to generate the radio button list.
- * Note, the values will be automatically HTML-encoded, while the labels will not.
- * @param array $htmlOptions additional HTML options. The options will be applied to
- * each radio button input. The following special options are recognized:
- *
- *
template: string, specifies how each radio button is rendered. Defaults
- * to "{input} {label}", where "{input}" will be replaced by the generated
- * radio button input tag while "{label}" will be replaced by the corresponding radio button label.
- *
separator: string, specifies the string that separates the generated radio buttons. Defaults to new line ( ).
- *
labelOptions: array, specifies the additional HTML attributes to be rendered
- * for every label tag in the list.
- *
container: string, specifies the radio buttons enclosing tag. Defaults to 'span'.
- * If the value is an empty string, no enclosing tag will be generated
- *
baseID: string, specifies the base ID prefix to be used for radio buttons in the list.
- * This option is available since version 1.1.13.
- *
- * @return string the generated radio button list
- */
- public static function radioButtonList($name, $select, $data, $htmlOptions = array())
- {
- $template = isset($htmlOptions['template']) ? $htmlOptions['template'] : '{input} {label}';
- $separator = isset($htmlOptions['separator']) ? $htmlOptions['separator'] : " \n";
- $container = isset($htmlOptions['container']) ? $htmlOptions['container'] : 'span';
- unset($htmlOptions['template'], $htmlOptions['separator'], $htmlOptions['container']);
-
- $labelOptions = isset($htmlOptions['labelOptions']) ? $htmlOptions['labelOptions'] : array();
- unset($htmlOptions['labelOptions']);
-
- $items = array();
- $baseID = isset($htmlOptions['baseID']) ? $htmlOptions['baseID'] : static::getIdByName($name);
- unset($htmlOptions['baseID']);
- $id = 0;
- foreach ($data as $value => $label) {
- $checked = !strcmp($value, $select);
- $htmlOptions['value'] = $value;
- $htmlOptions['id'] = $baseID . '_' . $id++;
- $option = static::radioButton($name, $checked, $htmlOptions);
- $label = static::label($label, $htmlOptions['id'], $labelOptions);
- $items[] = strtr($template, array('{input}' => $option, '{label}' => $label));
- }
- if (empty($container)) {
- return implode($separator, $items);
- } else {
- return static::tag($container, array('id' => $baseID), implode($separator, $items));
- }
- }
-
- /**
- * Normalizes the input parameter to be a valid URL.
- *
- * If the input parameter is an empty string, the currently requested URL will be returned.
- *
- * If the input parameter is a non-empty string, it is treated as a valid URL and will
- * be returned without any change.
+ * Generates a list of radio buttons.
+ * A radio button list is like a checkbox list, except that it only allows single selection.
+ * @param string $name the name attribute of each radio button.
+ * @param array $items the data item used to generate the radio buttons.
+ * The array keys are the labels, while the array values are the corresponding radio button values.
+ * Note that the labels will NOT be HTML-encoded, while the values will.
+ * @param string|array $selection the selected value(s).
+ * @param callable $formatter a callback that can be used to customize the generation of the HTML code
+ * corresponding to a single radio button. The signature of this callback must be:
*
- * If the input parameter is an array, it is treated as a controller route and a list of
- * GET parameters, and the {@link CController::createUrl} method will be invoked to
- * create a URL. In this case, the first array element refers to the controller route,
- * and the rest key-value pairs refer to the additional GET parameters for the URL.
- * For example, array('post/list', 'page'=>3) may be used to generate the URL
- * /index.php?r=post/list&page=3.
+ * ~~~
+ * function ($index, $label, $name, $value, $checked)
+ * ~~~
*
- * @param mixed $url the parameter to be used to generate a valid URL
- * @return string the normalized URL
+ * where $index is the zero-based index of the radio button in the whole list; $label
+ * is the label for the radio button; and $name, $value and $checked represent the name,
+ * value and the checked status of the radio button input.
+ * @return string the generated radio button list
*/
- public static function normalizeUrl($url)
+ public static function radioList($name, $items, $selection = null, $formatter = null)
{
- if (is_array($url)) {
- if (isset($url[0])) {
- if (($c = Yii::app()->getController()) !== null) {
- $url = $c->createUrl($url[0], array_splice($url, 1));
- } else {
- $url = Yii::app()->createUrl($url[0], array_splice($url, 1));
- }
+ $lines = array();
+ $index = 0;
+ foreach ($items as $value => $label) {
+ $checked = $selection !== null &&
+ (!is_array($selection) && !strcmp($value, $selection)
+ || is_array($selection) && in_array($value, $selection));
+ if ($formatter !== null) {
+ $lines[] = call_user_func($formatter, $index, $label, $name, $value, $checked);
} else {
- $url = '';
+ $lines[] = static::label(static::radio($name, $value, $checked) . ' ' . $label);
}
+ $index++;
}
- return $url === '' ? Yii::app()->getRequest()->getUrl() : $url;
- }
- /**
- * Generates an input HTML tag.
- * This method generates an input HTML tag based on the given input name and value.
- * @param string $type the input type (e.g. 'text', 'radio')
- * @param string $name the input name
- * @param string $value the input value
- * @param array $htmlOptions additional HTML attributes for the HTML tag (see {@link tag}).
- * @return string the generated input tag
- */
- protected static function inputField($type, $name, $value, $htmlOptions)
- {
- $htmlOptions['type'] = $type;
- $htmlOptions['value'] = $value;
- $htmlOptions['name'] = $name;
- if (!isset($htmlOptions['id'])) {
- $htmlOptions['id'] = static::getIdByName($name);
- } elseif ($htmlOptions['id'] === false) {
- unset($htmlOptions['id']);
- }
- return static::tag('input', $htmlOptions);
+ return implode("\n", $lines);
}
/**
- * Generates the list options.
- * @param mixed $selection the selected value(s). This can be either a string for single selection or an array for multiple selections.
- * @param array $listData the option data (see {@link listData})
- * @param array $htmlOptions additional HTML attributes. The following two special attributes are recognized:
- *
- *
encode: boolean, specifies whether to encode the values. Defaults to true.
- *
prompt: string, specifies the prompt text shown as the first list option. Its value is empty. Note, the prompt text will NOT be HTML-encoded.
- *
empty: string, specifies the text corresponding to empty selection. Its value is empty.
- * The 'empty' option can also be an array of value-label pairs.
- * Each pair will be used to render a list option at the beginning. Note, the text label will NOT be HTML-encoded.
- *
options: array, specifies additional attributes for each OPTION tag.
- * The array keys must be the option values, and the array values are the extra
- * OPTION tag attributes in the name-value pairs. For example,
- *
key: string, specifies the name of key attribute of the selection object(s).
- * This is used when the selection is represented in terms of objects. In this case,
- * the property named by the key option of the objects will be treated as the actual selection value.
- * This option defaults to 'primaryKey', meaning using the 'primaryKey' property value of the objects in the selection.
- * This option has been available since version 1.1.3.
- *
+ * Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]].
+ * @param array $items the option data items. The array keys are option values, and the array values
+ * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
+ * For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
+ * If you have a list of data models, you may convert them into the format described above using
+ * [[\yii\util\ArrayHelper::map()]].
+ *
+ * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
+ * the labels will also be HTML-encoded.
+ * @param string|array $selection the selected value(s). This can be either a string for single selection
+ * or an array for multiple selections.
+ * @param array $attributes the attributes parameter that is passed to the [[dropDownList()]] or [[listBox()]] call.
+ * This method will take out these elements, if any: "prompt", "options" and "groups". See more details
+ * in [[dropDownList()]] for the explanation of these elements.
+ *
* @return string the generated list options
*/
- public static function listOptions($selection, $listData, &$htmlOptions)
+ public static function renderOptions($items, $selection = null, &$attributes = array())
{
- $raw = isset($htmlOptions['encode']) && !$htmlOptions['encode'];
- $content = '';
- if (isset($htmlOptions['prompt'])) {
- $content .= '\n";
- unset($htmlOptions['prompt']);
- }
- if (isset($htmlOptions['empty'])) {
- if (!is_array($htmlOptions['empty'])) {
- $htmlOptions['empty'] = array('' => $htmlOptions['empty']);
- }
- foreach ($htmlOptions['empty'] as $value => $label) {
- $content .= '\n";
- }
- unset($htmlOptions['empty']);
+ $lines = array();
+ if (isset($attributes['prompt'])) {
+ $prompt = strtr(static::encode($attributes['prompt']), ' ', ' ');
+ $lines[] = static::tag('option', $prompt, array('value' => ''));
}
- if (isset($htmlOptions['options'])) {
- $options = $htmlOptions['options'];
- unset($htmlOptions['options']);
- } else {
- $options = array();
- }
+ $options = isset($attributes['options']) ? $attributes['options'] : array();
+ $groups = isset($attributes['groups']) ? $attributes['groups'] : array();
+ unset($attributes['prompt'], $attributes['options'], $attributes['groups']);
- $key = isset($htmlOptions['key']) ? $htmlOptions['key'] : 'primaryKey';
- if (is_array($selection)) {
- foreach ($selection as $i => $item) {
- if (is_object($item)) {
- $selection[$i] = $item->$key;
- }
- }
- } elseif (is_object($selection)) {
- $selection = $selection->$key;
- }
-
- foreach ($listData as $key => $value) {
+ foreach ($items as $key => $value) {
if (is_array($value)) {
- $content .= '' . "\n";
+ $groupAttrs = isset($groups[$key]) ? $groups[$key] : array();
+ $groupAttrs['label'] = $key;
+ $attrs = array('options' => $options, 'groups' => $groups);
+ $content = static::renderOptions($selection, $value, $attrs);
+ $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs);
} else {
- $attributes = array('value' => (string)$key, 'encode' => !$raw);
- if (!is_array($selection) && !strcmp($key, $selection) || is_array($selection) && in_array($key, $selection)) {
- $attributes['selected'] = 'selected';
- }
- if (isset($options[$key])) {
- $attributes = array_merge($attributes, $options[$key]);
- }
- $content .= static::tag('option', $attributes, $raw ? (string)$value : static::encode((string)$value)) . "\n";
+ $attrs = isset($options[$key]) ? $options[$key] : array();
+ $attrs['value'] = $key;
+ $attrs['selected'] = $selection !== null &&
+ (!is_array($selection) && !strcmp($key, $selection)
+ || is_array($selection) && in_array($key, $selection));
+ $lines[] = static::tag('option', strtr(static::encode($value), ' ', ' '), $attrs);
}
}
- unset($htmlOptions['key']);
-
- return $content;
+ return implode("\n", $lines);
}
/**
* Renders the HTML tag attributes.
- * Since version 1.1.5, attributes whose value is null will not be rendered.
- * Special attributes, such as 'checked', 'disabled', 'readonly', will be rendered
- * properly based on their corresponding boolean value.
- * @param array $attributes attributes to be rendered
- * @return string the rendering result
+ * Boolean attributes such as s 'checked', 'disabled', 'readonly', will be handled specially
+ * according to [[booleanAttributes]] and [[showBooleanAttributeValues]].
+ * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the rendering result.
+ * @return string the rendering result. If the attributes are not empty, they will be rendered
+ * into a string with a leading white space (such that it can be directly appended to the tag name
+ * in a tag. If there is no attribute, an empty string will be returned.
*/
public static function renderAttributes($attributes)
{
- static $specialAttributes = array(
- 'async' => 1,
- 'autofocus' => 1,
- 'autoplay' => 1,
- 'checked' => 1,
- 'controls' => 1,
- 'declare' => 1,
- 'default' => 1,
- 'defer' => 1,
- 'disabled' => 1,
- 'formnovalidate' => 1,
- 'hidden' => 1,
- 'ismap' => 1,
- 'loop' => 1,
- 'multiple' => 1,
- 'muted' => 1,
- 'nohref' => 1,
- 'noresize' => 1,
- 'novalidate' => 1,
- 'open' => 1,
- 'readonly' => 1,
- 'required' => 1,
- 'reversed' => 1,
- 'scoped' => 1,
- 'seamless' => 1,
- 'selected' => 1,
- 'typemustmatch' => 1,
- );
-
- if ($attributes === array()) {
- return '';
+ if (count($attributes) > 1) {
+ $sorted = array();
+ foreach (static::$attributeOrder as $name) {
+ if (isset($attributes[$name])) {
+ $sorted[$name] = $attributes[$name];
+ }
+ }
+ $attributes = array_merge($sorted, $attributes);
}
$html = '';
- if (isset($attributes['encode'])) {
- $raw = !$attributes['encode'];
- unset($attributes['encode']);
- } else {
- $raw = false;
- }
-
foreach ($attributes as $name => $value) {
- if (isset($specialAttributes[$name])) {
- if ($value) {
- $html .= ' ' . $name;
- if (static::$renderSpecialAttributesValue) {
- $html .= '="' . $name . '"';
- }
+ if (isset(static::$booleanAttributes[strtolower($name)])) {
+ if ($value || strcasecmp($name, $value) === 0) {
+ $html .= static::$showBooleanAttributeValues ? " $name=\"$name\"" : " $name";
}
} elseif ($value !== null) {
- $html .= ' ' . $name . '="' . ($raw ? $value : static::encode($value)) . '"';
+ $html .= " $name=\"" . static::encode($value) . '"';
}
}
-
return $html;
}
+
+ /**
+ * Normalizes the input parameter to be a valid URL.
+ *
+ * If the input parameter
+ *
+ * - is an empty string: the currently requested URL will be returned;
+ * - is a non-empty string: it will be processed by [[Yii::getAlias()]] which, if the string is an alias,
+ * will be resolved into a URL;
+ * - is an array: the first array element is considered a route, while the rest of the name-value
+ * pairs are considered as the parameters to be used for URL creation using [[\yii\base\Application::createUrl()]].
+ * Here are some examples: `array('post/index', 'page' => 2)`, `array('index')`.
+ *
+ * @param array|string $url the parameter to be used to generate a valid URL
+ * @return string the normalized URL
+ * @throws InvalidParamException if the parameter is invalid.
+ */
+ public static function url($url)
+ {
+ if (is_array($url)) {
+ if (isset($url[0])) {
+ return Yii::$app->createUrl($url[0], array_splice($url, 1));
+ } else {
+ throw new InvalidParamException('The array specifying a URL must contain at least one element.');
+ }
+ } elseif ($url === '') {
+ return Yii::$app->getRequest()->getUrl();
+ } else {
+ return Yii::getAlias($url);
+ }
+ }
}
diff --git a/framework/web/Application.php b/framework/web/Application.php
index d2a758a..0b9ce06 100644
--- a/framework/web/Application.php
+++ b/framework/web/Application.php
@@ -6,6 +6,7 @@
*/
namespace yii\web;
+use yii\base\InvalidParamException;
/**
* Application is the base class for all application classes.
@@ -62,11 +63,48 @@ class Application extends \yii\base\Application
}
/**
- * @return UrlManager
+ * Creates a URL using the given route and parameters.
+ *
+ * This method first normalizes the given route by converting a relative route into an absolute one.
+ * A relative route is a route without slash. If the route is an empty string, it stands for
+ * the route of the currently active [[controller]]. If the route is not empty, it stands for
+ * an action ID of the [[controller]].
+ *
+ * After normalizing the route, this method calls [[\yii\web\UrlManager::createUrl()]]
+ * to create a relative URL.
+ *
+ * @param string $route the route. This can be either an absolute or a relative route.
+ * @param array $params the parameters (name-value pairs) to be included in the generated URL
+ * @return string the created URL
+ * @throws InvalidParamException if a relative route is given and there is no active controller.
+ * @see createAbsoluteUrl
*/
- public function getUrlManager()
+ public function createUrl($route, $params = array())
{
- return $this->getComponent('urlManager');
+ if (strpos($route, '/') === false) {
+ // a relative route
+ if ($this->controller !== null) {
+ $route = $route === '' ? $this->controller->route : $this->controller->uniqueId . '/' . $route;
+ } else {
+ throw new InvalidParamException('No active controller exists for resolving a relative route.');
+ }
+ }
+ return $this->getUrlManager()->createUrl($route, $params);
+ }
+
+ /**
+ * Creates an absolute URL using the given route and parameters.
+ * This method first calls [[createUrl()]] to create a relative URL.
+ * It then prepends [[\yii\web\UrlManager::hostInfo]] to the URL to form an absolute one.
+ * @param string $route the route. This can be either an absolute or a relative route.
+ * See [[createUrl()]] for more details.
+ * @param array $params the parameters (name-value pairs)
+ * @return string the created URL
+ * @see createUrl
+ */
+ public function createAbsoluteUrl($route, $params = array())
+ {
+ return $this->getUrlManager()->getHostInfo() . $this->createUrl($route, $params);
}
/**
@@ -86,9 +124,6 @@ class Application extends \yii\base\Application
'session' => array(
'class' => 'yii\web\Session',
),
- 'urlManager' => array(
- 'class' => 'yii\web\UrlManager',
- ),
));
}
}
diff --git a/framework/web/Controller.php b/framework/web/Controller.php
index 2779c35..9420034 100644
--- a/framework/web/Controller.php
+++ b/framework/web/Controller.php
@@ -16,4 +16,8 @@ namespace yii\web;
*/
class Controller extends \yii\base\Controller
{
+ public function createUrl($route, $params = array())
+ {
+
+ }
}
\ No newline at end of file
diff --git a/framework/web/Request.php b/framework/web/Request.php
index 65a7f30..c7899cf 100644
--- a/framework/web/Request.php
+++ b/framework/web/Request.php
@@ -368,7 +368,7 @@ class Request extends \yii\base\Request
*/
protected function resolvePathInfo()
{
- $pathInfo = $this->getRequestUri();
+ $pathInfo = $this->getUrl();
if (($pos = strpos($pathInfo, '?')) !== false) {
$pathInfo = substr($pathInfo, 0, $pos);
@@ -407,42 +407,41 @@ class Request extends \yii\base\Request
}
/**
- * Returns the currently requested URL.
- * This is a shortcut to the concatenation of [[hostInfo]] and [[requestUri]].
- * @return string the currently requested URL.
+ * Returns the currently requested absolute URL.
+ * This is a shortcut to the concatenation of [[hostInfo]] and [[url]].
+ * @return string the currently requested absolute URL.
*/
- public function getUrl()
+ public function getAbsoluteUrl()
{
- return $this->getHostInfo() . $this->getRequestUri();
+ return $this->getHostInfo() . $this->getUrl();
}
- private $_requestUri;
+ private $_url;
/**
- * Returns the portion after [[hostInfo]] for the currently requested URL.
- * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any.
- * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
- * @return string the request URI portion for the currently requested URL.
- * Note that the URI returned is URL-encoded.
- * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration
+ * Returns the currently requested relative URL.
+ * This refers to the portion of the URL that is after the [[hostInfo]] part.
+ * It includes the [[queryString]] part if any.
+ * @return string the currently requested relative URL. Note that the URI returned is URL-encoded.
+ * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration
*/
- public function getRequestUri()
+ public function getUrl()
{
- if ($this->_requestUri === null) {
- $this->_requestUri = $this->resolveRequestUri();
+ if ($this->_url === null) {
+ $this->_url = $this->resolveRequestUri();
}
- return $this->_requestUri;
+ return $this->_url;
}
/**
- * Sets the currently requested URI.
+ * Sets the currently requested relative URL.
* The URI must refer to the portion that is after [[hostInfo]].
* Note that the URI should be URL-encoded.
* @param string $value the request URI to be set
*/
- public function setRequestUri($value)
+ public function setUrl($value)
{
- $this->_requestUri = $value;
+ $this->_url = $value;
}
/**
diff --git a/todo.md b/todo.md
index c80da5a..03b9f70 100644
--- a/todo.md
+++ b/todo.md
@@ -18,6 +18,7 @@
* backend-specific unit tests
* dependency unit tests
- validators
+ * Refactor validators to add validateValue() for every validator, if possible. Check if value is an array.
* FileValidator: depends on CUploadedFile
* CaptchaValidator: depends on CaptchaAction
* DateValidator: should we use CDateTimeParser, or simply use strtotime()?
From 03e212bcf671e28ecc36445017f5d214bcf5405d Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Mon, 11 Mar 2013 11:41:46 -0400
Subject: [PATCH 62/84] Added HtmlTest.
---
framework/util/Html.php | 78 ++++++---
framework/web/Application.php | 10 +-
tests/unit/framework/util/ArrayHelperTest.php | 2 +-
tests/unit/framework/util/HtmlTest.php | 234 ++++++++++++++++++++++++++
tests/unit/runtime/.gitignore | 1 +
5 files changed, 297 insertions(+), 28 deletions(-)
create mode 100644 tests/unit/framework/util/HtmlTest.php
create mode 100644 tests/unit/runtime/.gitignore
diff --git a/framework/util/Html.php b/framework/util/Html.php
index 774a733..8c29047 100644
--- a/framework/util/Html.php
+++ b/framework/util/Html.php
@@ -217,7 +217,7 @@ class Html
if (!isset($attributes['type'])) {
$attributes['type'] = 'text/css';
}
- return static::tag('style', "\n/**/\n", $attributes);
+ return static::tag('style', "/**/", $attributes);
}
/**
@@ -233,7 +233,7 @@ class Html
if (!isset($attributes['type'])) {
$attributes['type'] = 'text/javascript';
}
- return static::tag('script', "\n/**/\n", $attributes);
+ return static::tag('script', "/**/", $attributes);
}
/**
@@ -278,23 +278,25 @@ class Html
*/
public static function beginForm($action = '', $method = 'post', $attributes = array())
{
- $attributes['action'] = $url = static::url($action);
- $attributes['method'] = $method;
-
- $form = static::beginTag('form', $attributes);
+ $action = static::url($action);
// query parameters in the action are ignored for GET method
// we use hidden fields to add them back
$hiddens = array();
- if (!strcasecmp($method, 'get') && ($pos = strpos($url, '?')) !== false) {
- foreach (explode('&', substr($url, $pos + 1)) as $pair) {
- if (($pos = strpos($pair, '=')) !== false) {
- $hiddens[] = static::hiddenInput(urldecode(substr($pair, 0, $pos)), urldecode(substr($pair, $pos + 1)));
+ if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) {
+ foreach (explode('&', substr($action, $pos + 1)) as $pair) {
+ if (($pos1 = strpos($pair, '=')) !== false) {
+ $hiddens[] = static::hiddenInput(urldecode(substr($pair, 0, $pos1)), urldecode(substr($pair, $pos1 + 1)));
} else {
$hiddens[] = static::hiddenInput(urldecode($pair), '');
}
}
+ $action = substr($action, 0, $pos);
}
+
+ $attributes['action'] = $action;
+ $attributes['method'] = $method;
+ $form = static::beginTag('form', $attributes);
if ($hiddens !== array()) {
$form .= "\n" . implode("\n", $hiddens);
}
@@ -696,17 +698,20 @@ class Html
if (!isset($attributes['size'])) {
$attributes['size'] = 4;
}
+ if (isset($attributes['multiple']) && $attributes['multiple'] && substr($name, -2) !== '[]') {
+ $name .= '[]';
+ }
+ $attributes['name'] = $name;
if (isset($attributes['unselect'])) {
// add a hidden field so that if the list box has no option being selected, it still submits a value
+ if (substr($name, -2) === '[]') {
+ $name = substr($name, 0, -2);
+ }
$hidden = static::hiddenInput($name, $attributes['unselect']);
unset($attributes['unselect']);
} else {
$hidden = '';
}
- if (isset($attributes['multiple']) && $attributes['multiple'] && substr($name, -2) !== '[]') {
- $name .= '[]';
- }
- $attributes['name'] = $name;
$options = static::renderOptions($items, $selection, $attributes);
return $hidden . static::tag('select', "\n" . $options . "\n", $attributes);
}
@@ -720,8 +725,13 @@ class Html
* The array keys are the labels, while the array values are the corresponding checkbox values.
* Note that the labels will NOT be HTML-encoded, while the values will.
* @param string|array $selection the selected value(s).
- * @param callable $formatter a callback that can be used to customize the generation of the HTML code
- * corresponding to a single checkbox. The signature of this callback must be:
+ * @param array $options options (name => config) for the checkbox list. The following options are supported:
+ *
+ * - unselect: string, the value that should be submitted when none of the checkboxes is selected.
+ * By setting this option, a hidden input will be generated.
+ * - separator: string, the HTML code that separates items.
+ * - item: callable, a callback that can be used to customize the generation of the HTML code
+ * corresponding to a single item in $items. The signature of this callback must be:
*
* ~~~
* function ($index, $label, $name, $value, $checked)
@@ -732,12 +742,13 @@ class Html
* value and the checked status of the checkbox input.
* @return string the generated checkbox list
*/
- public static function checkboxList($name, $items, $selection = null, $formatter = null)
+ public static function checkboxList($name, $items, $selection = null, $options = array())
{
if (substr($name, -2) !== '[]') {
$name .= '[]';
}
+ $formatter = isset($options['item']) ? $options['item'] : null;
$lines = array();
$index = 0;
foreach ($items as $value => $label) {
@@ -752,7 +763,16 @@ class Html
$index++;
}
- return implode("\n", $lines);
+ if (isset($options['unselect'])) {
+ // add a hidden field so that if the list box has no option being selected, it still submits a value
+ $name2 = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name;
+ $hidden = static::hiddenInput($name2, $options['unselect']);
+ } else {
+ $hidden = '';
+ }
+ $separator = isset($options['separator']) ? $options['separator'] : "\n";
+
+ return $hidden . implode($separator, $lines);
}
/**
@@ -763,8 +783,13 @@ class Html
* The array keys are the labels, while the array values are the corresponding radio button values.
* Note that the labels will NOT be HTML-encoded, while the values will.
* @param string|array $selection the selected value(s).
- * @param callable $formatter a callback that can be used to customize the generation of the HTML code
- * corresponding to a single radio button. The signature of this callback must be:
+ * @param array $options options (name => config) for the radio button list. The following options are supported:
+ *
+ * - unselect: string, the value that should be submitted when none of the radio buttons is selected.
+ * By setting this option, a hidden input will be generated.
+ * - separator: string, the HTML code that separates items.
+ * - item: callable, a callback that can be used to customize the generation of the HTML code
+ * corresponding to a single item in $items. The signature of this callback must be:
*
* ~~~
* function ($index, $label, $name, $value, $checked)
@@ -775,8 +800,9 @@ class Html
* value and the checked status of the radio button input.
* @return string the generated radio button list
*/
- public static function radioList($name, $items, $selection = null, $formatter = null)
+ public static function radioList($name, $items, $selection = null, $options = array())
{
+ $formatter = isset($options['item']) ? $options['item'] : null;
$lines = array();
$index = 0;
foreach ($items as $value => $label) {
@@ -791,7 +817,15 @@ class Html
$index++;
}
- return implode("\n", $lines);
+ $separator = isset($options['separator']) ? $options['separator'] : "\n";
+ if (isset($options['unselect'])) {
+ // add a hidden field so that if the list box has no option being selected, it still submits a value
+ $hidden = static::hiddenInput($name, $options['unselect']);
+ } else {
+ $hidden = '';
+ }
+
+ return $hidden . implode($separator, $lines);
}
/**
diff --git a/framework/web/Application.php b/framework/web/Application.php
index 0b9ce06..61e84a3 100644
--- a/framework/web/Application.php
+++ b/framework/web/Application.php
@@ -66,9 +66,9 @@ class Application extends \yii\base\Application
* Creates a URL using the given route and parameters.
*
* This method first normalizes the given route by converting a relative route into an absolute one.
- * A relative route is a route without slash. If the route is an empty string, it stands for
- * the route of the currently active [[controller]]. If the route is not empty, it stands for
- * an action ID of the [[controller]].
+ * A relative route is a route without a leading slash. It is considered to be relative to the currently
+ * requested route. If the route is an empty string, it stands for the route of the currently active
+ * [[controller]]. Otherwise, the [[Controller::uniqueId]] will be prepended to the route.
*
* After normalizing the route, this method calls [[\yii\web\UrlManager::createUrl()]]
* to create a relative URL.
@@ -81,12 +81,12 @@ class Application extends \yii\base\Application
*/
public function createUrl($route, $params = array())
{
- if (strpos($route, '/') === false) {
+ if (strncmp($route, '/', 1) !== 0) {
// a relative route
if ($this->controller !== null) {
$route = $route === '' ? $this->controller->route : $this->controller->uniqueId . '/' . $route;
} else {
- throw new InvalidParamException('No active controller exists for resolving a relative route.');
+ throw new InvalidParamException('Relative route cannot be handled because there is no active controller.');
}
}
return $this->getUrlManager()->createUrl($route, $params);
diff --git a/tests/unit/framework/util/ArrayHelperTest.php b/tests/unit/framework/util/ArrayHelperTest.php
index a713381..a36ce68 100644
--- a/tests/unit/framework/util/ArrayHelperTest.php
+++ b/tests/unit/framework/util/ArrayHelperTest.php
@@ -1,6 +1,6 @@
array(
+ 'request' => array(
+ 'class' => 'yii\web\Request',
+ 'url' => '/test',
+ ),
+ ),
+ ));
+ }
+
+ public function tearDown()
+ {
+ Yii::$app = null;
+ }
+
+ public function testEncode()
+ {
+ $this->assertEquals("a<>&"'", Html::encode("a<>&\"'"));
+ }
+
+ public function testDecode()
+ {
+ $this->assertEquals("a<>&\"'", Html::decode("a<>&"'"));
+ }
+
+ public function testTag()
+ {
+ $this->assertEquals(' ', Html::tag('br'));
+ $this->assertEquals('', Html::tag('span'));
+ $this->assertEquals('
', Html::tag('div', 'content'));
+ $this->assertEquals('', Html::tag('input', '', array('type' => 'text', 'name' => 'test', 'value' => '<>')));
+
+ Html::$closeVoidElements = true;
+ }
+
+ public function testBeginTag()
+ {
+ $this->assertEquals(' ', Html::beginTag('br'));
+ $this->assertEquals('', Html::beginTag('span', array('id' => 'test', 'class' => 'title')));
+ }
+
+ public function testEndTag()
+ {
+ $this->assertEquals('', Html::endTag('br'));
+ $this->assertEquals('', Html::endTag('span'));
+ }
+
+ public function testCdata()
+ {
+ $data = 'test<>';
+ $this->assertEquals('', Html::cdata($data));
+ }
+
+ public function testStyle()
+ {
+ $content = 'a <>';
+ $this->assertEquals("", Html::style($content));
+ $this->assertEquals("", Html::style($content, array('type' => 'text/less')));
+ }
+
+ public function testScript()
+ {
+ $content = 'a <>';
+ $this->assertEquals("", Html::script($content));
+ $this->assertEquals("", Html::script($content, array('type' => 'text/js')));
+ }
+
+ public function testCssFile()
+ {
+ $this->assertEquals('', Html::cssFile('http://example.com'));
+ $this->assertEquals('', Html::cssFile(''));
+ }
+
+ public function testJsFile()
+ {
+ $this->assertEquals('', Html::jsFile('http://example.com'));
+ $this->assertEquals('', Html::jsFile(''));
+ }
+
+ public function testBeginForm()
+ {
+ $this->assertEquals('', Html::endForm());
+ }
+
+ public function testA()
+ {
+ $this->assertEquals('something<>', Html::a('something<>'));
+ $this->assertEquals('something', Html::a('something', '/example'));
+ $this->assertEquals('something', Html::a('something', ''));
+ }
+
+ public function testMailto()
+ {
+ $this->assertEquals('test<>', Html::mailto('test<>'));
+ $this->assertEquals('test<>', Html::mailto('test<>', 'test>'));
+ }
+
+ public function testImg()
+ {
+ $this->assertEquals('', Html::img('/example'));
+ $this->assertEquals('', Html::img(''));
+ $this->assertEquals('', Html::img('/example', array('alt' => 'something', 'width' => 10)));
+ }
+
+ public function testLabel()
+ {
+ $this->assertEquals('', Html::label('something<>'));
+ $this->assertEquals('', Html::label('something<>', 'a'));
+ $this->assertEquals('', Html::label('something<>', 'a', array('class' => 'test')));
+ }
+
+ public function testButton()
+ {
+ $this->assertEquals('', Html::button());
+ $this->assertEquals('', Html::button('test', 'value', 'content<>'));
+ $this->assertEquals('', Html::button('test', 'value', 'content<>', array('type' => 'submit', 'class' => "t")));
+ }
+
+ public function testSubmitButton()
+ {
+ $this->assertEquals('', Html::submitButton());
+ $this->assertEquals('', Html::submitButton('test', 'value', 'content<>', array('class' => 't')));
+ }
+
+ public function testResetButton()
+ {
+ $this->assertEquals('', Html::resetButton());
+ $this->assertEquals('', Html::resetButton('test', 'value', 'content<>', array('class' => 't')));
+ }
+
+ public function testInput()
+ {
+ $this->assertEquals('', Html::input('text'));
+ $this->assertEquals('', Html::input('text', 'test', 'value', array('class' => 't')));
+ }
+
+ public function testButtonInput()
+ {
+ }
+
+ public function testSubmitInput()
+ {
+ }
+
+ public function testResetInput()
+ {
+ }
+
+ public function testTextInput()
+ {
+ }
+
+ public function testHiddenInput()
+ {
+ }
+
+ public function testPasswordInput()
+ {
+ }
+
+ public function testFileInput()
+ {
+ }
+
+ public function testTextarea()
+ {
+ }
+
+ public function testRadio()
+ {
+ }
+
+ public function testCheckbox()
+ {
+ }
+
+ public function testDropDownList()
+ {
+ }
+
+ public function testListBox()
+ {
+ }
+
+ public function testCheckboxList()
+ {
+ }
+
+ public function testRadioList()
+ {
+ }
+
+ public function testRenderOptions()
+ {
+ }
+
+ public function testRenderAttributes()
+ {
+ }
+
+ public function testUrl()
+ {
+ }
+}
diff --git a/tests/unit/runtime/.gitignore b/tests/unit/runtime/.gitignore
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/tests/unit/runtime/.gitignore
@@ -0,0 +1 @@
+*
From a5ababe4c63d8f22ca2b0b776cf5aacb9045e2dd Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Mon, 11 Mar 2013 15:03:00 -0400
Subject: [PATCH 63/84] Finished HtmlTest.
---
framework/util/ArrayHelper.php | 4 +-
framework/util/Html.php | 15 +--
framework/web/Controller.php | 4 -
framework/web/Pagination.php | 4 +-
framework/widgets/ActiveForm.php | 6 +
tests/unit/framework/util/HtmlTest.php | 201 ++++++++++++++++++++++++++++++++-
todo.md | 5 -
7 files changed, 218 insertions(+), 21 deletions(-)
create mode 100644 framework/widgets/ActiveForm.php
diff --git a/framework/util/ArrayHelper.php b/framework/util/ArrayHelper.php
index ebf4b23..447d034 100644
--- a/framework/util/ArrayHelper.php
+++ b/framework/util/ArrayHelper.php
@@ -293,7 +293,7 @@ class ArrayHelper
* @return array the encoded data
* @see http://www.php.net/manual/en/function.htmlspecialchars.php
*/
- public static function htmlEncode($data, $valuesOnly = false, $charset = null)
+ public static function htmlEncode($data, $valuesOnly = true, $charset = null)
{
if ($charset === null) {
$charset = Yii::$app->charset;
@@ -322,7 +322,7 @@ class ArrayHelper
* @return array the decoded data
* @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php
*/
- public static function htmlDecode($data, $valuesOnly = false)
+ public static function htmlDecode($data, $valuesOnly = true)
{
$d = array();
foreach ($data as $key => $value) {
diff --git a/framework/util/Html.php b/framework/util/Html.php
index 8c29047..92c8f02 100644
--- a/framework/util/Html.php
+++ b/framework/util/Html.php
@@ -106,6 +106,7 @@ class Html
'checked',
'readonly',
'disabled',
+ 'multiple',
'size',
'maxlength',
@@ -652,7 +653,7 @@ class Html
* except that the array keys represent the optgroup labels specified in $items.
* @return string the generated drop-down list tag
*/
- public static function dropDownList($name, $items, $selection = null, $attributes = array())
+ public static function dropDownList($name, $items = array(), $selection = null, $attributes = array())
{
$attributes['name'] = $name;
$options = static::renderOptions($items, $selection, $attributes);
@@ -693,7 +694,7 @@ class Html
* mode, we can still obtain the posted unselect value.
* @return string the generated list box tag
*/
- public static function listBox($name, $items, $selection = null, $attributes = array())
+ public static function listBox($name, $items = array(), $selection = null, $attributes = array())
{
if (!isset($attributes['size'])) {
$attributes['size'] = 4;
@@ -742,7 +743,7 @@ class Html
* value and the checked status of the checkbox input.
* @return string the generated checkbox list
*/
- public static function checkboxList($name, $items, $selection = null, $options = array())
+ public static function checkboxList($name, $items = array(), $selection = null, $options = array())
{
if (substr($name, -2) !== '[]') {
$name .= '[]';
@@ -800,7 +801,7 @@ class Html
* value and the checked status of the radio button input.
* @return string the generated radio button list
*/
- public static function radioList($name, $items, $selection = null, $options = array())
+ public static function radioList($name, $items = array(), $selection = null, $options = array())
{
$formatter = isset($options['item']) ? $options['item'] : null;
$lines = array();
@@ -850,7 +851,7 @@ class Html
{
$lines = array();
if (isset($attributes['prompt'])) {
- $prompt = strtr(static::encode($attributes['prompt']), ' ', ' ');
+ $prompt = str_replace(' ', ' ', static::encode($attributes['prompt']));
$lines[] = static::tag('option', $prompt, array('value' => ''));
}
@@ -863,7 +864,7 @@ class Html
$groupAttrs = isset($groups[$key]) ? $groups[$key] : array();
$groupAttrs['label'] = $key;
$attrs = array('options' => $options, 'groups' => $groups);
- $content = static::renderOptions($selection, $value, $attrs);
+ $content = static::renderOptions($value, $selection, $attrs);
$lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs);
} else {
$attrs = isset($options[$key]) ? $options[$key] : array();
@@ -871,7 +872,7 @@ class Html
$attrs['selected'] = $selection !== null &&
(!is_array($selection) && !strcmp($key, $selection)
|| is_array($selection) && in_array($key, $selection));
- $lines[] = static::tag('option', strtr(static::encode($value), ' ', ' '), $attrs);
+ $lines[] = static::tag('option', str_replace(' ', ' ', static::encode($value)), $attrs);
}
}
diff --git a/framework/web/Controller.php b/framework/web/Controller.php
index 9420034..2779c35 100644
--- a/framework/web/Controller.php
+++ b/framework/web/Controller.php
@@ -16,8 +16,4 @@ namespace yii\web;
*/
class Controller extends \yii\base\Controller
{
- public function createUrl($route, $params = array())
- {
-
- }
}
\ No newline at end of file
diff --git a/framework/web/Pagination.php b/framework/web/Pagination.php
index 84585d6..1d41c0c 100644
--- a/framework/web/Pagination.php
+++ b/framework/web/Pagination.php
@@ -14,7 +14,7 @@ use Yii;
*
* When data needs to be rendered in multiple pages, Pagination can be used to
* represent information such as [[itemCount|total item count]], [[pageSize|page size]],
- * [[page|current page]], etc. These information can be passed to [[yii\web\widgets\Pager|pagers]]
+ * [[page|current page]], etc. These information can be passed to [[yii\widgets\Pager|pagers]]
* to render pagination buttons or links.
*
* The following example shows how to create a pagination object and feed it
@@ -47,7 +47,7 @@ use Yii;
* }
*
* // display pagination
- * $this->widget('yii\web\widgets\LinkPager', array(
+ * $this->widget('yii\widgets\LinkPager', array(
* 'pages' => $pages,
* ));
* ~~~
diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php
new file mode 100644
index 0000000..77fd9fd
--- /dev/null
+++ b/framework/widgets/ActiveForm.php
@@ -0,0 +1,6 @@
+assertEquals('', Html::tag('input', '', array('type' => 'text', 'name' => 'test', 'value' => '<>')));
Html::$closeVoidElements = true;
+
+ $this->assertEquals('', Html::tag('span', '', array('disabled' => true)));
+ Html::$showBooleanAttributeValues = false;
+ $this->assertEquals('', Html::tag('span', '', array('disabled' => true)));
+ Html::$showBooleanAttributeValues = true;
}
public function testBeginTag()
@@ -166,69 +171,263 @@ class HtmlTest extends \yii\test\TestCase
public function testButtonInput()
{
+ $this->assertEquals('', Html::buttonInput('test'));
+ $this->assertEquals('', Html::buttonInput('test', 'text', array('class' => 'a')));
}
public function testSubmitInput()
{
+ $this->assertEquals('', Html::submitInput());
+ $this->assertEquals('', Html::submitInput('test', 'text', array('class' => 'a')));
}
public function testResetInput()
{
+ $this->assertEquals('', Html::resetInput());
+ $this->assertEquals('', Html::resetInput('test', 'text', array('class' => 'a')));
}
public function testTextInput()
{
+ $this->assertEquals('', Html::textInput('test'));
+ $this->assertEquals('', Html::textInput('test', 'value', array('class' => 't')));
}
public function testHiddenInput()
{
+ $this->assertEquals('', Html::hiddenInput('test'));
+ $this->assertEquals('', Html::hiddenInput('test', 'value', array('class' => 't')));
}
public function testPasswordInput()
{
+ $this->assertEquals('', Html::passwordInput('test'));
+ $this->assertEquals('', Html::passwordInput('test', 'value', array('class' => 't')));
}
public function testFileInput()
{
+ $this->assertEquals('', Html::fileInput('test'));
+ $this->assertEquals('', Html::fileInput('test', 'value', array('class' => 't')));
}
public function testTextarea()
{
+ $this->assertEquals('', Html::textarea('test'));
+ $this->assertEquals('', Html::textarea('test', 'value<>', array('class' => 't')));
}
public function testRadio()
{
+ $this->assertEquals('', Html::radio('test'));
+ $this->assertEquals('', Html::radio('test', null, true, array('class' => 'a')));
+ $this->assertEquals('', Html::radio('test', null, true, array('class' => 'a' ,'uncheck' => '0')));
}
public function testCheckbox()
{
+ $this->assertEquals('', Html::checkbox('test'));
+ $this->assertEquals('', Html::checkbox('test', null, true, array('class' => 'a')));
+ $this->assertEquals('', Html::checkbox('test', null, true, array('class' => 'a' ,'uncheck' => '0')));
}
public function testDropDownList()
{
+ $this->assertEquals("", Html::dropDownList('test'));
+ $this->assertEquals("", Html::dropDownList('test', $this->getDataItems()));
+ $this->assertEquals("", Html::dropDownList('test', $this->getDataItems(), 'value2'));
}
public function testListBox()
{
+ $expected = <<
+
+
+EOD;
+ $this->assertEquals($expected, Html::listBox('test'));
+ $expected = <<
+
+
+
+EOD;
+ $this->assertEquals($expected, Html::listBox('test', $this->getDataItems(), null, array('size' => 5)));
+ $expected = <<
+
+
+
+EOD;
+ $this->assertEquals($expected, Html::listBox('test', $this->getDataItems2(), null));
+ $expected = <<
+
+
+
+EOD;
+ $this->assertEquals($expected, Html::listBox('test', $this->getDataItems(), 'value2'));
+ $expected = <<
+
+
+
+EOD;
+ $this->assertEquals($expected, Html::listBox('test', $this->getDataItems(), array('value1', 'value2')));
+
+ $expected = <<
+
+
+EOD;
+ $this->assertEquals($expected, Html::listBox('test', array(), null, array('multiple' => true)));
+ $expected = <<
+EOD;
+ $this->assertEquals($expected, Html::listBox('test', array(), '', array('unselect' => '0')));
}
public function testCheckboxList()
{
+ $this->assertEquals('', Html::checkboxList('test'));
+
+ $expected = << text1
+
+EOD;
+ $this->assertEquals($expected, Html::checkboxList('test', $this->getDataItems(), array('value2')));
+
+ $expected = << text1<>
+
+EOD;
+ $this->assertEquals($expected, Html::checkboxList('test', $this->getDataItems2(), array('value2')));
+
+ $expected = <<
+
+EOD;
+ $this->assertEquals($expected, Html::checkboxList('test', $this->getDataItems(), array('value2'), array(
+ 'separator' => " \n",
+ 'unselect' => '0',
+ )));
+
+ $expected = <<text1
+
+EOD;
+ $this->assertEquals($expected, Html::checkboxList('test', $this->getDataItems(), array('value2'), array(
+ 'item' => function ($index, $label, $name, $value, $checked) {
+ return Html::label($label . ' ' . Html::checkbox($name, $value, $checked));
+ }
+ )));
}
public function testRadioList()
{
+ $this->assertEquals('', Html::radioList('test'));
+
+ $expected = << text1
+
+EOD;
+ $this->assertEquals($expected, Html::radioList('test', $this->getDataItems(), array('value2')));
+
+ $expected = << text1<>
+
+EOD;
+ $this->assertEquals($expected, Html::radioList('test', $this->getDataItems2(), array('value2')));
+
+ $expected = <<
+
+EOD;
+ $this->assertEquals($expected, Html::radioList('test', $this->getDataItems(), array('value2'), array(
+ 'separator' => " \n",
+ 'unselect' => '0',
+ )));
+
+ $expected = <<text1
+
+EOD;
+ $this->assertEquals($expected, Html::radioList('test', $this->getDataItems(), array('value2'), array(
+ 'item' => function ($index, $label, $name, $value, $checked) {
+ return Html::label($label . ' ' . Html::radio($name, $value, $checked));
+ }
+ )));
}
public function testRenderOptions()
{
+ $this->assertEquals('', Html::renderOptions(array()));
+
+ $data = array(
+ 'value1' => 'label1',
+ 'group1' => array(
+ 'value11' => 'label11',
+ 'group11' => array(
+ 'value111' => 'label111',
+ ),
+ 'group12' => array(),
+ ),
+ 'value2' => 'label2',
+ 'group2' => array(),
+ );
+ $expected = <<please select<>
+
+
+
+
+
+
+EOD;
+ $attributes = array(
+ 'prompt' => 'please select<>',
+ 'options' => array(
+ 'value111' => array('class' => 'option'),
+ ),
+ 'groups' => array(
+ 'group12' => array('class' => 'group'),
+ ),
+ );
+ $this->assertEquals($expected, Html::renderOptions($data, array('value111', 'value1'), $attributes));
}
public function testRenderAttributes()
{
+ $this->assertEquals('', Html::renderAttributes(array()));
+ $this->assertEquals(' name="test" value="1<>"', Html::renderAttributes(array('name' => 'test', 'empty' => null, 'value' => '1<>')));
+ Html::$showBooleanAttributeValues = false;
+ $this->assertEquals(' checked disabled', Html::renderAttributes(array('checked' => 'checked', 'disabled' => true, 'hidden' => false)));
+ Html::$showBooleanAttributeValues = true;
+ }
+
+ protected function getDataItems()
+ {
+ return array(
+ 'value1' => 'text1',
+ 'value2' => 'text2',
+ );
}
- public function testUrl()
+ protected function getDataItems2()
{
+ return array(
+ 'value1<>' => 'text1<>',
+ 'value 2' => 'text 2',
+ );
}
}
diff --git a/todo.md b/todo.md
index 03b9f70..4d5343a 100644
--- a/todo.md
+++ b/todo.md
@@ -32,8 +32,6 @@ memo
* module
- Module should be able to define its own configuration including routes. Application should be able to overwrite it.
* application
- * security
- - backport 1.1 changes
- built-in console commands
+ api doc builder
* support for markdown syntax
@@ -46,7 +44,6 @@ memo
* parsing??
* make dates/date patterns uniform application-wide including JUI, formats etc.
- helpers
- * array
* image
* string
* file
@@ -59,8 +56,6 @@ memo
* move generation API out of gii, provide yiic commands to use it. Use same templates for gii/yiic.
* i18n variant of templates
* allow to generate module-specific CRUD
-- markup and HTML helpers
- * use HTML5 instead of XHTML
- assets
* ability to manage scripts order (store these in a vector?)
* http://ryanbigg.com/guides/asset_pipeline.html, http://guides.rubyonrails.org/asset_pipeline.html, use content hash instead of mtime + directory hash.
From 2422a134f007c2a17db6d62099cf43c262b1fda7 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Wed, 13 Mar 2013 16:21:13 -0400
Subject: [PATCH 64/84] Refactored Html.
---
framework/util/Html.php | 322 +++++++++++++++++----------------
tests/unit/framework/util/HtmlTest.php | 66 ++++---
2 files changed, 194 insertions(+), 194 deletions(-)
diff --git a/framework/util/Html.php b/framework/util/Html.php
index 92c8f02..bdbdcd2 100644
--- a/framework/util/Html.php
+++ b/framework/util/Html.php
@@ -153,15 +153,15 @@ class Html
* @param string $name the tag name
* @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded.
* If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks.
- * @param array $attributes the element attributes. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the element attributes. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated HTML tag
* @see beginTag
* @see endTag
*/
- public static function tag($name, $content = '', $attributes = array())
+ public static function tag($name, $content = '', $tagAttributes = array())
{
- $html = '<' . $name . static::renderAttributes($attributes);
+ $html = '<' . $name . static::renderTagAttributes($tagAttributes);
if (isset(static::$voidElements[strtolower($name)])) {
return $html . (static::$closeVoidElements ? ' />' : '>');
} else {
@@ -172,15 +172,15 @@ class Html
/**
* Generates a start tag.
* @param string $name the tag name
- * @param array $attributes the element attributes. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the element attributes. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated start tag
* @see endTag
* @see tag
*/
- public static function beginTag($name, $attributes = array())
+ public static function beginTag($name, $tagAttributes = array())
{
- return '<' . $name . static::renderAttributes($attributes) . '>';
+ return '<' . $name . static::renderTagAttributes($tagAttributes) . '>';
}
/**
@@ -208,76 +208,76 @@ class Html
/**
* Generates a style tag.
* @param string $content the style content
- * @param array $attributes the attributes of the style tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the style tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* If the attributes does not contain "type", a default one with value "text/css" will be used.
* @return string the generated style tag
*/
- public static function style($content, $attributes = array())
+ public static function style($content, $tagAttributes = array())
{
- if (!isset($attributes['type'])) {
- $attributes['type'] = 'text/css';
+ if (!isset($tagAttributes['type'])) {
+ $tagAttributes['type'] = 'text/css';
}
- return static::tag('style', "/**/", $attributes);
+ return static::tag('style', "/**/", $tagAttributes);
}
/**
* Generates a script tag.
* @param string $content the script content
- * @param array $attributes the attributes of the script tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the script tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* If the attributes does not contain "type", a default one with value "text/javascript" will be used.
* @return string the generated script tag
*/
- public static function script($content, $attributes = array())
+ public static function script($content, $tagAttributes = array())
{
- if (!isset($attributes['type'])) {
- $attributes['type'] = 'text/javascript';
+ if (!isset($tagAttributes['type'])) {
+ $tagAttributes['type'] = 'text/javascript';
}
- return static::tag('script', "/**/", $attributes);
+ return static::tag('script', "/**/", $tagAttributes);
}
/**
* Generates a link tag that refers to an external CSS file.
* @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]].
- * @param array $attributes the attributes of the link tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the link tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated link tag
* @see url
*/
- public static function cssFile($url, $attributes = array())
+ public static function cssFile($url, $tagAttributes = array())
{
- $attributes['rel'] = 'stylesheet';
- $attributes['type'] = 'text/css';
- $attributes['href'] = static::url($url);
- return static::tag('link', '', $attributes);
+ $tagAttributes['rel'] = 'stylesheet';
+ $tagAttributes['type'] = 'text/css';
+ $tagAttributes['href'] = static::url($url);
+ return static::tag('link', '', $tagAttributes);
}
/**
* Generates a script tag that refers to an external JavaScript file.
* @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]].
- * @param array $attributes the attributes of the script tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the script tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated script tag
* @see url
*/
- public static function jsFile($url, $attributes = array())
+ public static function jsFile($url, $tagAttributes = array())
{
- $attributes['type'] = 'text/javascript';
- $attributes['src'] = static::url($url);
- return static::tag('script', '', $attributes);
+ $tagAttributes['type'] = 'text/javascript';
+ $tagAttributes['src'] = static::url($url);
+ return static::tag('script', '', $tagAttributes);
}
/**
* Generates a form start tag.
* @param array|string $action the form action URL. This parameter will be processed by [[url()]].
* @param string $method form method, either "post" or "get" (case-insensitive)
- * @param array $attributes the attributes of the form tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the form tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated form start tag.
* @see endForm
*/
- public static function beginForm($action = '', $method = 'post', $attributes = array())
+ public static function beginForm($action = '', $method = 'post', $tagAttributes = array())
{
$action = static::url($action);
@@ -295,9 +295,9 @@ class Html
$action = substr($action, 0, $pos);
}
- $attributes['action'] = $action;
- $attributes['method'] = $method;
- $form = static::beginTag('form', $attributes);
+ $tagAttributes['action'] = $action;
+ $tagAttributes['method'] = $method;
+ $form = static::beginTag('form', $tagAttributes);
if ($hiddens !== array()) {
$form .= "\n" . implode("\n", $hiddens);
}
@@ -323,17 +323,17 @@ class Html
* @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]]
* and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute
* will not be generated.
- * @param array $attributes the attributes of the hyperlink tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the hyperlink tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated hyperlink
* @see url
*/
- public static function a($text, $url = null, $attributes = array())
+ public static function a($text, $url = null, $tagAttributes = array())
{
if ($url !== null) {
- $attributes['href'] = static::url($url);
+ $tagAttributes['href'] = static::url($url);
}
- return static::tag('a', $text, $attributes);
+ return static::tag('a', $text, $tagAttributes);
}
/**
@@ -343,29 +343,29 @@ class Html
* it to prevent XSS attacks.
* @param string $email email address. If this is null, the first parameter (link body) will be treated
* as the email address and used.
- * @param array $attributes the attributes of the hyperlink tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the hyperlink tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated mailto link
*/
- public static function mailto($text, $email = null, $attributes = array())
+ public static function mailto($text, $email = null, $tagAttributes = array())
{
- return static::a($text, 'mailto:' . ($email === null ? $text : $email), $attributes);
+ return static::a($text, 'mailto:' . ($email === null ? $text : $email), $tagAttributes);
}
/**
* Generates an image tag.
* @param string $src the image URL. This parameter will be processed by [[url()]].
- * @param array $attributes the attributes of the image tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the image tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated image tag
*/
- public static function img($src, $attributes = array())
+ public static function img($src, $tagAttributes = array())
{
- $attributes['src'] = static::url($src);
- if (!isset($attributes['alt'])) {
- $attributes['alt'] = '';
+ $tagAttributes['src'] = static::url($src);
+ if (!isset($tagAttributes['alt'])) {
+ $tagAttributes['alt'] = '';
}
- return static::tag('img', null, $attributes);
+ return static::tag('img', null, $tagAttributes);
}
/**
@@ -375,14 +375,14 @@ class Html
* it to prevent XSS attacks.
* @param string $for the ID of the HTML element that this label is associated with.
* If this is null, the "for" attribute will not be generated.
- * @param array $attributes the attributes of the label tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the label tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated label tag
*/
- public static function label($content, $for = null, $attributes = array())
+ public static function label($content, $for = null, $tagAttributes = array())
{
- $attributes['for'] = $for;
- return static::tag('label', $content, $attributes);
+ $tagAttributes['for'] = $for;
+ return static::tag('label', $content, $tagAttributes);
}
/**
@@ -392,19 +392,19 @@ class Html
* @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded.
* Therefore you can pass in HTML code such as an image tag. If this is is coming from end users,
* you should consider [[encode()]] it to prevent XSS attacks.
- * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* If the attributes does not contain "type", a default one with value "button" will be used.
* @return string the generated button tag
*/
- public static function button($name = null, $value = null, $content = 'Button', $attributes = array())
+ public static function button($name = null, $value = null, $content = 'Button', $tagAttributes = array())
{
- $attributes['name'] = $name;
- $attributes['value'] = $value;
- if (!isset($attributes['type'])) {
- $attributes['type'] = 'button';
+ $tagAttributes['name'] = $name;
+ $tagAttributes['value'] = $value;
+ if (!isset($tagAttributes['type'])) {
+ $tagAttributes['type'] = 'button';
}
- return static::tag('button', $content, $attributes);
+ return static::tag('button', $content, $tagAttributes);
}
/**
@@ -414,14 +414,14 @@ class Html
* @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded.
* Therefore you can pass in HTML code such as an image tag. If this is is coming from end users,
* you should consider [[encode()]] it to prevent XSS attacks.
- * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated submit button tag
*/
- public static function submitButton($name = null, $value = null, $content = 'Submit', $attributes = array())
+ public static function submitButton($name = null, $value = null, $content = 'Submit', $tagAttributes = array())
{
- $attributes['type'] = 'submit';
- return static::button($name, $value, $content, $attributes);
+ $tagAttributes['type'] = 'submit';
+ return static::button($name, $value, $content, $tagAttributes);
}
/**
@@ -431,14 +431,14 @@ class Html
* @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded.
* Therefore you can pass in HTML code such as an image tag. If this is is coming from end users,
* you should consider [[encode()]] it to prevent XSS attacks.
- * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated reset button tag
*/
- public static function resetButton($name = null, $value = null, $content = 'Reset', $attributes = array())
+ public static function resetButton($name = null, $value = null, $content = 'Reset', $tagAttributes = array())
{
- $attributes['type'] = 'reset';
- return static::button($name, $value, $content, $attributes);
+ $tagAttributes['type'] = 'reset';
+ return static::button($name, $value, $content, $tagAttributes);
}
/**
@@ -446,94 +446,94 @@ class Html
* @param string $type the type attribute.
* @param string $name the name attribute. If it is null, the name attribute will not be generated.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated input tag
*/
- public static function input($type, $name = null, $value = null, $attributes = array())
+ public static function input($type, $name = null, $value = null, $tagAttributes = array())
{
- $attributes['type'] = $type;
- $attributes['name'] = $name;
- $attributes['value'] = $value;
- return static::tag('input', null, $attributes);
+ $tagAttributes['type'] = $type;
+ $tagAttributes['name'] = $name;
+ $tagAttributes['value'] = $value;
+ return static::tag('input', null, $tagAttributes);
}
/**
* Generates an input button.
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated button tag
*/
- public static function buttonInput($name, $value = 'Button', $attributes = array())
+ public static function buttonInput($name, $value = 'Button', $tagAttributes = array())
{
- return static::input('button', $name, $value, $attributes);
+ return static::input('button', $name, $value, $tagAttributes);
}
/**
* Generates a submit input button.
* @param string $name the name attribute. If it is null, the name attribute will not be generated.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated button tag
*/
- public static function submitInput($name = null, $value = 'Submit', $attributes = array())
+ public static function submitInput($name = null, $value = 'Submit', $tagAttributes = array())
{
- return static::input('submit', $name, $value, $attributes);
+ return static::input('submit', $name, $value, $tagAttributes);
}
/**
* Generates a reset input button.
* @param string $name the name attribute. If it is null, the name attribute will not be generated.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $attributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated button tag
*/
- public static function resetInput($name = null, $value = 'Reset', $attributes = array())
+ public static function resetInput($name = null, $value = 'Reset', $tagAttributes = array())
{
- return static::input('reset', $name, $value, $attributes);
+ return static::input('reset', $name, $value, $tagAttributes);
}
/**
* Generates a text input field.
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated button tag
*/
- public static function textInput($name, $value = null, $attributes = array())
+ public static function textInput($name, $value = null, $tagAttributes = array())
{
- return static::input('text', $name, $value, $attributes);
+ return static::input('text', $name, $value, $tagAttributes);
}
/**
* Generates a hidden input field.
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated button tag
*/
- public static function hiddenInput($name, $value = null, $attributes = array())
+ public static function hiddenInput($name, $value = null, $tagAttributes = array())
{
- return static::input('hidden', $name, $value, $attributes);
+ return static::input('hidden', $name, $value, $tagAttributes);
}
/**
* Generates a password input field.
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated button tag
*/
- public static function passwordInput($name, $value = null, $attributes = array())
+ public static function passwordInput($name, $value = null, $tagAttributes = array())
{
- return static::input('password', $name, $value, $attributes);
+ return static::input('password', $name, $value, $tagAttributes);
}
/**
@@ -543,36 +543,36 @@ class Html
* can be obtained via $_FILES[$name] (see PHP documentation).
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated button tag
*/
- public static function fileInput($name, $value = null, $attributes = array())
+ public static function fileInput($name, $value = null, $tagAttributes = array())
{
- return static::input('file', $name, $value, $attributes);
+ return static::input('file', $name, $value, $tagAttributes);
}
/**
* Generates a text area input.
* @param string $name the input name
* @param string $value the input value. Note that it will be encoded using [[encode()]].
- * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated text area tag
*/
- public static function textarea($name, $value = '', $attributes = array())
+ public static function textarea($name, $value = '', $tagAttributes = array())
{
- $attributes['name'] = $name;
- return static::tag('textarea', static::encode($value), $attributes);
+ $tagAttributes['name'] = $name;
+ return static::tag('textarea', static::encode($value), $tagAttributes);
}
/**
* Generates a radio button input.
* @param string $name the name attribute.
- * @param string $value the value attribute. If it is null, the value attribute will not be generated.
* @param boolean $checked whether the radio button should be checked.
- * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned. The following attribute
+ * @param string $value the value attribute. If it is null, the value attribute will not be rendered.
+ * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned. The following attributes
* will be specially handled and not put in the resulting tag:
*
* - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute
@@ -581,26 +581,27 @@ class Html
*
* @return string the generated radio button tag
*/
- public static function radio($name, $value = '1', $checked = false, $attributes = array())
+ public static function radio($name, $checked = false, $value = '1', $tagAttributes = array())
{
- $attributes['checked'] = $checked;
- if (isset($attributes['uncheck'])) {
+ $tagAttributes['checked'] = $checked;
+ $tagAttributes['value'] = $value;
+ if (isset($tagAttributes['uncheck'])) {
// add a hidden field so that if the radio button is not selected, it still submits a value
- $hidden = static::hiddenInput($name, $attributes['uncheck']);
- unset($attributes['uncheck']);
+ $hidden = static::hiddenInput($name, $tagAttributes['uncheck']);
+ unset($tagAttributes['uncheck']);
} else {
$hidden = '';
}
- return $hidden . static::input('radio', $name, $value, $attributes);
+ return $hidden . static::input('radio', $name, $value, $tagAttributes);
}
/**
* Generates a checkbox input.
* @param string $name the name attribute.
- * @param string $value the value attribute. If it is null, the value attribute will not be generated.
* @param boolean $checked whether the checkbox should be checked.
- * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned. The following attribute
+ * @param string $value the value attribute. If it is null, the value attribute will not be rendered.
+ * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned. The following attributes
* will be specially handled and not put in the resulting tag:
*
* - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute
@@ -609,22 +610,24 @@ class Html
*
* @return string the generated checkbox tag
*/
- public static function checkbox($name, $value = '1', $checked = false, $attributes = array())
+ public static function checkbox($name, $checked = false, $value = '1', $tagAttributes = array())
{
- $attributes['checked'] = $checked;
- if (isset($attributes['uncheck'])) {
+ $tagAttributes['checked'] = $checked;
+ $tagAttributes['value'] = $value;
+ if (isset($tagAttributes['uncheck'])) {
// add a hidden field so that if the checkbox is not selected, it still submits a value
- $hidden = static::hiddenInput($name, $attributes['uncheck']);
- unset($attributes['uncheck']);
+ $hidden = static::hiddenInput($name, $tagAttributes['uncheck']);
+ unset($tagAttributes['uncheck']);
} else {
$hidden = '';
}
- return $hidden . static::input('checkbox', $name, $value, $attributes);
+ return $hidden . static::input('checkbox', $name, $value, $tagAttributes);
}
/**
* Generates a drop-down list.
* @param string $name the input name
+ * @param string $selection the selected value
* @param array $items the option data items. The array keys are option values, and the array values
* are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
* For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
@@ -633,9 +636,8 @@ class Html
*
* Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
* the labels will also be HTML-encoded.
- * @param string $selection the selected value
- * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned. The following attribute
+ * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned. The following attributes
* will be specially handled and not put in the resulting tag:
*
* - prompt: string, a prompt text to be displayed as the first option;
@@ -653,16 +655,17 @@ class Html
* except that the array keys represent the optgroup labels specified in $items.
* @return string the generated drop-down list tag
*/
- public static function dropDownList($name, $items = array(), $selection = null, $attributes = array())
+ public static function dropDownList($name, $selection = null, $items = array(), $tagAttributes = array())
{
- $attributes['name'] = $name;
- $options = static::renderOptions($items, $selection, $attributes);
- return static::tag('select', "\n" . $options . "\n", $attributes);
+ $tagAttributes['name'] = $name;
+ $options = static::renderSelectOptions($selection, $items, $tagAttributes);
+ return static::tag('select', "\n" . $options . "\n", $tagAttributes);
}
/**
* Generates a list box.
* @param string $name the input name
+ * @param string|array $selection the selected value(s)
* @param array $items the option data items. The array keys are option values, and the array values
* are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
* For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
@@ -671,9 +674,8 @@ class Html
*
* Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
* the labels will also be HTML-encoded.
- * @param string|array $selection the selected value(s)
- * @param array $attributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned. The following attribute
+ * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
+ * Attributes whose value is null will be ignored and not put in the tag returned. The following attributes
* will be specially handled and not put in the resulting tag:
*
* - prompt: string, a prompt text to be displayed as the first option;
@@ -694,27 +696,27 @@ class Html
* mode, we can still obtain the posted unselect value.
* @return string the generated list box tag
*/
- public static function listBox($name, $items = array(), $selection = null, $attributes = array())
+ public static function listBox($name, $selection = null, $items = array(), $tagAttributes = array())
{
- if (!isset($attributes['size'])) {
- $attributes['size'] = 4;
+ if (!isset($tagAttributes['size'])) {
+ $tagAttributes['size'] = 4;
}
- if (isset($attributes['multiple']) && $attributes['multiple'] && substr($name, -2) !== '[]') {
+ if (isset($tagAttributes['multiple']) && $tagAttributes['multiple'] && substr($name, -2) !== '[]') {
$name .= '[]';
}
- $attributes['name'] = $name;
- if (isset($attributes['unselect'])) {
+ $tagAttributes['name'] = $name;
+ if (isset($tagAttributes['unselect'])) {
// add a hidden field so that if the list box has no option being selected, it still submits a value
if (substr($name, -2) === '[]') {
$name = substr($name, 0, -2);
}
- $hidden = static::hiddenInput($name, $attributes['unselect']);
- unset($attributes['unselect']);
+ $hidden = static::hiddenInput($name, $tagAttributes['unselect']);
+ unset($tagAttributes['unselect']);
} else {
$hidden = '';
}
- $options = static::renderOptions($items, $selection, $attributes);
- return $hidden . static::tag('select', "\n" . $options . "\n", $attributes);
+ $options = static::renderSelectOptions($selection, $items, $tagAttributes);
+ return $hidden . static::tag('select', "\n" . $options . "\n", $tagAttributes);
}
/**
@@ -722,10 +724,10 @@ class Html
* A checkbox list allows multiple selection, like [[listBox()]].
* As a result, the corresponding submitted value is an array.
* @param string $name the name attribute of each checkbox.
+ * @param string|array $selection the selected value(s).
* @param array $items the data item used to generate the checkboxes.
* The array keys are the labels, while the array values are the corresponding checkbox values.
* Note that the labels will NOT be HTML-encoded, while the values will.
- * @param string|array $selection the selected value(s).
* @param array $options options (name => config) for the checkbox list. The following options are supported:
*
* - unselect: string, the value that should be submitted when none of the checkboxes is selected.
@@ -735,7 +737,7 @@ class Html
* corresponding to a single item in $items. The signature of this callback must be:
*
* ~~~
- * function ($index, $label, $name, $value, $checked)
+ * function ($index, $label, $name, $checked, $value)
* ~~~
*
* where $index is the zero-based index of the checkbox in the whole list; $label
@@ -743,7 +745,7 @@ class Html
* value and the checked status of the checkbox input.
* @return string the generated checkbox list
*/
- public static function checkboxList($name, $items = array(), $selection = null, $options = array())
+ public static function checkboxList($name, $selection = null, $items = array(), $options = array())
{
if (substr($name, -2) !== '[]') {
$name .= '[]';
@@ -757,9 +759,9 @@ class Html
(!is_array($selection) && !strcmp($value, $selection)
|| is_array($selection) && in_array($value, $selection));
if ($formatter !== null) {
- $lines[] = call_user_func($formatter, $index, $label, $name, $value, $checked);
+ $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value);
} else {
- $lines[] = static::label(static::checkbox($name, $value, $checked) . ' ' . $label);
+ $lines[] = static::label(static::checkbox($name, $checked, $value) . ' ' . $label);
}
$index++;
}
@@ -780,10 +782,10 @@ class Html
* Generates a list of radio buttons.
* A radio button list is like a checkbox list, except that it only allows single selection.
* @param string $name the name attribute of each radio button.
+ * @param string|array $selection the selected value(s).
* @param array $items the data item used to generate the radio buttons.
* The array keys are the labels, while the array values are the corresponding radio button values.
* Note that the labels will NOT be HTML-encoded, while the values will.
- * @param string|array $selection the selected value(s).
* @param array $options options (name => config) for the radio button list. The following options are supported:
*
* - unselect: string, the value that should be submitted when none of the radio buttons is selected.
@@ -793,7 +795,7 @@ class Html
* corresponding to a single item in $items. The signature of this callback must be:
*
* ~~~
- * function ($index, $label, $name, $value, $checked)
+ * function ($index, $label, $name, $checked, $value)
* ~~~
*
* where $index is the zero-based index of the radio button in the whole list; $label
@@ -801,7 +803,7 @@ class Html
* value and the checked status of the radio button input.
* @return string the generated radio button list
*/
- public static function radioList($name, $items = array(), $selection = null, $options = array())
+ public static function radioList($name, $selection = null, $items = array(), $options = array())
{
$formatter = isset($options['item']) ? $options['item'] : null;
$lines = array();
@@ -811,9 +813,9 @@ class Html
(!is_array($selection) && !strcmp($value, $selection)
|| is_array($selection) && in_array($value, $selection));
if ($formatter !== null) {
- $lines[] = call_user_func($formatter, $index, $label, $name, $value, $checked);
+ $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value);
} else {
- $lines[] = static::label(static::radio($name, $value, $checked) . ' ' . $label);
+ $lines[] = static::label(static::radio($name, $checked, $value) . ' ' . $label);
}
$index++;
}
@@ -831,6 +833,8 @@ class Html
/**
* Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]].
+ * @param string|array $selection the selected value(s). This can be either a string for single selection
+ * or an array for multiple selections.
* @param array $items the option data items. The array keys are option values, and the array values
* are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too).
* For each sub-array, an option group will be generated whose label is the key associated with the sub-array.
@@ -839,32 +843,30 @@ class Html
*
* Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
* the labels will also be HTML-encoded.
- * @param string|array $selection the selected value(s). This can be either a string for single selection
- * or an array for multiple selections.
- * @param array $attributes the attributes parameter that is passed to the [[dropDownList()]] or [[listBox()]] call.
+ * @param array $tagAttributes the attributes parameter that is passed to the [[dropDownList()]] or [[listBox()]] call.
* This method will take out these elements, if any: "prompt", "options" and "groups". See more details
* in [[dropDownList()]] for the explanation of these elements.
*
* @return string the generated list options
*/
- public static function renderOptions($items, $selection = null, &$attributes = array())
+ public static function renderSelectOptions($selection, $items, &$tagAttributes = array())
{
$lines = array();
- if (isset($attributes['prompt'])) {
- $prompt = str_replace(' ', ' ', static::encode($attributes['prompt']));
+ if (isset($tagAttributes['prompt'])) {
+ $prompt = str_replace(' ', ' ', static::encode($tagAttributes['prompt']));
$lines[] = static::tag('option', $prompt, array('value' => ''));
}
- $options = isset($attributes['options']) ? $attributes['options'] : array();
- $groups = isset($attributes['groups']) ? $attributes['groups'] : array();
- unset($attributes['prompt'], $attributes['options'], $attributes['groups']);
+ $options = isset($tagAttributes['options']) ? $tagAttributes['options'] : array();
+ $groups = isset($tagAttributes['groups']) ? $tagAttributes['groups'] : array();
+ unset($tagAttributes['prompt'], $tagAttributes['options'], $tagAttributes['groups']);
foreach ($items as $key => $value) {
if (is_array($value)) {
$groupAttrs = isset($groups[$key]) ? $groups[$key] : array();
$groupAttrs['label'] = $key;
$attrs = array('options' => $options, 'groups' => $groups);
- $content = static::renderOptions($value, $selection, $attrs);
+ $content = static::renderSelectOptions($selection, $value, $attrs);
$lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs);
} else {
$attrs = isset($options[$key]) ? $options[$key] : array();
@@ -883,26 +885,26 @@ class Html
* Renders the HTML tag attributes.
* Boolean attributes such as s 'checked', 'disabled', 'readonly', will be handled specially
* according to [[booleanAttributes]] and [[showBooleanAttributeValues]].
- * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]].
+ * @param array $tagAttributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the rendering result.
* @return string the rendering result. If the attributes are not empty, they will be rendered
* into a string with a leading white space (such that it can be directly appended to the tag name
* in a tag. If there is no attribute, an empty string will be returned.
*/
- public static function renderAttributes($attributes)
+ public static function renderTagAttributes($tagAttributes)
{
- if (count($attributes) > 1) {
+ if (count($tagAttributes) > 1) {
$sorted = array();
foreach (static::$attributeOrder as $name) {
- if (isset($attributes[$name])) {
- $sorted[$name] = $attributes[$name];
+ if (isset($tagAttributes[$name])) {
+ $sorted[$name] = $tagAttributes[$name];
}
}
- $attributes = array_merge($sorted, $attributes);
+ $tagAttributes = array_merge($sorted, $tagAttributes);
}
$html = '';
- foreach ($attributes as $name => $value) {
+ foreach ($tagAttributes as $name => $value) {
if (isset(static::$booleanAttributes[strtolower($name)])) {
if ($value || strcasecmp($name, $value) === 0) {
$html .= static::$showBooleanAttributeValues ? " $name=\"$name\"" : " $name";
diff --git a/tests/unit/framework/util/HtmlTest.php b/tests/unit/framework/util/HtmlTest.php
index 43991c4..35ae24c 100644
--- a/tests/unit/framework/util/HtmlTest.php
+++ b/tests/unit/framework/util/HtmlTest.php
@@ -220,22 +220,22 @@ class HtmlTest extends \yii\test\TestCase
public function testRadio()
{
$this->assertEquals('', Html::radio('test'));
- $this->assertEquals('', Html::radio('test', null, true, array('class' => 'a')));
- $this->assertEquals('', Html::radio('test', null, true, array('class' => 'a' ,'uncheck' => '0')));
+ $this->assertEquals('', Html::radio('test', true, null, array('class' => 'a')));
+ $this->assertEquals('', Html::radio('test', true, 2, array('class' => 'a' , 'uncheck' => '0')));
}
public function testCheckbox()
{
$this->assertEquals('', Html::checkbox('test'));
- $this->assertEquals('', Html::checkbox('test', null, true, array('class' => 'a')));
- $this->assertEquals('', Html::checkbox('test', null, true, array('class' => 'a' ,'uncheck' => '0')));
+ $this->assertEquals('', Html::checkbox('test', true, null, array('class' => 'a')));
+ $this->assertEquals('', Html::checkbox('test', true, 2, array('class' => 'a', 'uncheck' => '0')));
}
public function testDropDownList()
{
$this->assertEquals("", Html::dropDownList('test'));
- $this->assertEquals("", Html::dropDownList('test', $this->getDataItems()));
- $this->assertEquals("", Html::dropDownList('test', $this->getDataItems(), 'value2'));
+ $this->assertEquals("", Html::dropDownList('test', null, $this->getDataItems()));
+ $this->assertEquals("", Html::dropDownList('test', 'value2', $this->getDataItems()));
}
public function testListBox()
@@ -252,41 +252,41 @@ EOD;
EOD;
- $this->assertEquals($expected, Html::listBox('test', $this->getDataItems(), null, array('size' => 5)));
+ $this->assertEquals($expected, Html::listBox('test', null, $this->getDataItems(), array('size' => 5)));
$expected = <<
EOD;
- $this->assertEquals($expected, Html::listBox('test', $this->getDataItems2(), null));
+ $this->assertEquals($expected, Html::listBox('test', null, $this->getDataItems2()));
$expected = <<
EOD;
- $this->assertEquals($expected, Html::listBox('test', $this->getDataItems(), 'value2'));
+ $this->assertEquals($expected, Html::listBox('test', 'value2', $this->getDataItems()));
$expected = <<
EOD;
- $this->assertEquals($expected, Html::listBox('test', $this->getDataItems(), array('value1', 'value2')));
+ $this->assertEquals($expected, Html::listBox('test', array('value1', 'value2'), $this->getDataItems()));
$expected = <<
EOD;
- $this->assertEquals($expected, Html::listBox('test', array(), null, array('multiple' => true)));
+ $this->assertEquals($expected, Html::listBox('test', null, array(), array('multiple' => true)));
$expected = <<
EOD;
- $this->assertEquals($expected, Html::listBox('test', array(), '', array('unselect' => '0')));
+ $this->assertEquals($expected, Html::listBox('test', '', array(), array('unselect' => '0')));
}
public function testCheckboxList()
@@ -297,30 +297,30 @@ EOD;
EOD;
- $this->assertEquals($expected, Html::checkboxList('test', $this->getDataItems(), array('value2')));
+ $this->assertEquals($expected, Html::checkboxList('test', array('value2'), $this->getDataItems()));
$expected = << text1<>
EOD;
- $this->assertEquals($expected, Html::checkboxList('test', $this->getDataItems2(), array('value2')));
+ $this->assertEquals($expected, Html::checkboxList('test', array('value2'), $this->getDataItems2()));
$expected = <<
EOD;
- $this->assertEquals($expected, Html::checkboxList('test', $this->getDataItems(), array('value2'), array(
+ $this->assertEquals($expected, Html::checkboxList('test', array('value2'), $this->getDataItems(), array(
'separator' => " \n",
'unselect' => '0',
)));
$expected = <<text1
-
+0
+1
EOD;
- $this->assertEquals($expected, Html::checkboxList('test', $this->getDataItems(), array('value2'), array(
- 'item' => function ($index, $label, $name, $value, $checked) {
- return Html::label($label . ' ' . Html::checkbox($name, $value, $checked));
+ $this->assertEquals($expected, Html::checkboxList('test', array('value2'), $this->getDataItems(), array(
+ 'item' => function ($index, $label, $name, $checked, $value) {
+ return $index . Html::label($label . ' ' . Html::checkbox($name, $checked, $value));
}
)));
}
@@ -333,38 +333,36 @@ EOD;
EOD;
- $this->assertEquals($expected, Html::radioList('test', $this->getDataItems(), array('value2')));
+ $this->assertEquals($expected, Html::radioList('test', array('value2'), $this->getDataItems()));
$expected = << text1<>
EOD;
- $this->assertEquals($expected, Html::radioList('test', $this->getDataItems2(), array('value2')));
+ $this->assertEquals($expected, Html::radioList('test', array('value2'), $this->getDataItems2()));
$expected = <<
EOD;
- $this->assertEquals($expected, Html::radioList('test', $this->getDataItems(), array('value2'), array(
+ $this->assertEquals($expected, Html::radioList('test', array('value2'), $this->getDataItems(), array(
'separator' => " \n",
'unselect' => '0',
)));
$expected = <<text1
-
+0
+1
EOD;
- $this->assertEquals($expected, Html::radioList('test', $this->getDataItems(), array('value2'), array(
- 'item' => function ($index, $label, $name, $value, $checked) {
- return Html::label($label . ' ' . Html::radio($name, $value, $checked));
+ $this->assertEquals($expected, Html::radioList('test', array('value2'), $this->getDataItems(), array(
+ 'item' => function ($index, $label, $name, $checked, $value) {
+ return $index . Html::label($label . ' ' . Html::radio($name, $checked, $value));
}
)));
}
public function testRenderOptions()
{
- $this->assertEquals('', Html::renderOptions(array()));
-
$data = array(
'value1' => 'label1',
'group1' => array(
@@ -403,15 +401,15 @@ EOD;
'group12' => array('class' => 'group'),
),
);
- $this->assertEquals($expected, Html::renderOptions($data, array('value111', 'value1'), $attributes));
+ $this->assertEquals($expected, Html::renderSelectOptions(array('value111', 'value1'), $data, $attributes));
}
public function testRenderAttributes()
{
- $this->assertEquals('', Html::renderAttributes(array()));
- $this->assertEquals(' name="test" value="1<>"', Html::renderAttributes(array('name' => 'test', 'empty' => null, 'value' => '1<>')));
+ $this->assertEquals('', Html::renderTagAttributes(array()));
+ $this->assertEquals(' name="test" value="1<>"', Html::renderTagAttributes(array('name' => 'test', 'empty' => null, 'value' => '1<>')));
Html::$showBooleanAttributeValues = false;
- $this->assertEquals(' checked disabled', Html::renderAttributes(array('checked' => 'checked', 'disabled' => true, 'hidden' => false)));
+ $this->assertEquals(' checked disabled', Html::renderTagAttributes(array('checked' => 'checked', 'disabled' => true, 'hidden' => false)));
Html::$showBooleanAttributeValues = true;
}
From 5227d2db7dcf94f022247cbeb41385d2e0896516 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Wed, 13 Mar 2013 16:46:52 -0400
Subject: [PATCH 65/84] refactoring.
---
framework/util/Html.php | 389 ++++++++++++++++++---------------
tests/unit/framework/util/HtmlTest.php | 23 +-
2 files changed, 228 insertions(+), 184 deletions(-)
diff --git a/framework/util/Html.php b/framework/util/Html.php
index bdbdcd2..ebba40a 100644
--- a/framework/util/Html.php
+++ b/framework/util/Html.php
@@ -12,7 +12,7 @@ use yii\base\InvalidParamException;
/**
* Html provides a set of static methods for generating commonly used HTML tags.
- *
+ *
* @author Qiang Xue
* @since 2.0
*/
@@ -121,7 +121,6 @@ class Html
'media',
);
-
/**
* Encodes special characters into HTML entities.
* The [[yii\base\Application::charset|application charset]] will be used for encoding.
@@ -153,15 +152,16 @@ class Html
* @param string $name the tag name
* @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded.
* If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks.
- * @param array $tagAttributes the element attributes. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated HTML tag
* @see beginTag
* @see endTag
*/
- public static function tag($name, $content = '', $tagAttributes = array())
+ public static function tag($name, $content = '', $options = array())
{
- $html = '<' . $name . static::renderTagAttributes($tagAttributes);
+ $html = '<' . $name . static::renderTagAttributes($options);
if (isset(static::$voidElements[strtolower($name)])) {
return $html . (static::$closeVoidElements ? ' />' : '>');
} else {
@@ -172,15 +172,16 @@ class Html
/**
* Generates a start tag.
* @param string $name the tag name
- * @param array $tagAttributes the element attributes. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated start tag
* @see endTag
* @see tag
*/
- public static function beginTag($name, $tagAttributes = array())
+ public static function beginTag($name, $options = array())
{
- return '<' . $name . static::renderTagAttributes($tagAttributes) . '>';
+ return '<' . $name . static::renderTagAttributes($options) . '>';
}
/**
@@ -208,76 +209,81 @@ class Html
/**
* Generates a style tag.
* @param string $content the style content
- * @param array $tagAttributes the attributes of the style tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
- * If the attributes does not contain "type", a default one with value "text/css" will be used.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
+ * If the options does not contain "type", a "type" attribute with value "text/css" will be used.
* @return string the generated style tag
*/
- public static function style($content, $tagAttributes = array())
+ public static function style($content, $options = array())
{
- if (!isset($tagAttributes['type'])) {
- $tagAttributes['type'] = 'text/css';
+ if (!isset($options['type'])) {
+ $options['type'] = 'text/css';
}
- return static::tag('style', "/**/", $tagAttributes);
+ return static::tag('style', "/**/", $options);
}
/**
* Generates a script tag.
* @param string $content the script content
- * @param array $tagAttributes the attributes of the script tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
- * If the attributes does not contain "type", a default one with value "text/javascript" will be used.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
+ * If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered.
* @return string the generated script tag
*/
- public static function script($content, $tagAttributes = array())
+ public static function script($content, $options = array())
{
- if (!isset($tagAttributes['type'])) {
- $tagAttributes['type'] = 'text/javascript';
+ if (!isset($options['type'])) {
+ $options['type'] = 'text/javascript';
}
- return static::tag('script', "/**/", $tagAttributes);
+ return static::tag('script', "/**/", $options);
}
/**
* Generates a link tag that refers to an external CSS file.
* @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]].
- * @param array $tagAttributes the attributes of the link tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated link tag
* @see url
*/
- public static function cssFile($url, $tagAttributes = array())
+ public static function cssFile($url, $options = array())
{
- $tagAttributes['rel'] = 'stylesheet';
- $tagAttributes['type'] = 'text/css';
- $tagAttributes['href'] = static::url($url);
- return static::tag('link', '', $tagAttributes);
+ $options['rel'] = 'stylesheet';
+ $options['type'] = 'text/css';
+ $options['href'] = static::url($url);
+ return static::tag('link', '', $options);
}
/**
* Generates a script tag that refers to an external JavaScript file.
* @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]].
- * @param array $tagAttributes the attributes of the script tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated script tag
* @see url
*/
- public static function jsFile($url, $tagAttributes = array())
+ public static function jsFile($url, $options = array())
{
- $tagAttributes['type'] = 'text/javascript';
- $tagAttributes['src'] = static::url($url);
- return static::tag('script', '', $tagAttributes);
+ $options['type'] = 'text/javascript';
+ $options['src'] = static::url($url);
+ return static::tag('script', '', $options);
}
/**
* Generates a form start tag.
* @param array|string $action the form action URL. This parameter will be processed by [[url()]].
* @param string $method form method, either "post" or "get" (case-insensitive)
- * @param array $tagAttributes the attributes of the form tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated form start tag.
* @see endForm
*/
- public static function beginForm($action = '', $method = 'post', $tagAttributes = array())
+ public static function beginForm($action = '', $method = 'post', $options = array())
{
$action = static::url($action);
@@ -295,9 +301,9 @@ class Html
$action = substr($action, 0, $pos);
}
- $tagAttributes['action'] = $action;
- $tagAttributes['method'] = $method;
- $form = static::beginTag('form', $tagAttributes);
+ $options['action'] = $action;
+ $options['method'] = $method;
+ $form = static::beginTag('form', $options);
if ($hiddens !== array()) {
$form .= "\n" . implode("\n", $hiddens);
}
@@ -323,17 +329,18 @@ class Html
* @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]]
* and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute
* will not be generated.
- * @param array $tagAttributes the attributes of the hyperlink tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated hyperlink
* @see url
*/
- public static function a($text, $url = null, $tagAttributes = array())
+ public static function a($text, $url = null, $options = array())
{
if ($url !== null) {
- $tagAttributes['href'] = static::url($url);
+ $options['href'] = static::url($url);
}
- return static::tag('a', $text, $tagAttributes);
+ return static::tag('a', $text, $options);
}
/**
@@ -343,29 +350,31 @@ class Html
* it to prevent XSS attacks.
* @param string $email email address. If this is null, the first parameter (link body) will be treated
* as the email address and used.
- * @param array $tagAttributes the attributes of the hyperlink tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated mailto link
*/
- public static function mailto($text, $email = null, $tagAttributes = array())
+ public static function mailto($text, $email = null, $options = array())
{
- return static::a($text, 'mailto:' . ($email === null ? $text : $email), $tagAttributes);
+ return static::a($text, 'mailto:' . ($email === null ? $text : $email), $options);
}
/**
* Generates an image tag.
* @param string $src the image URL. This parameter will be processed by [[url()]].
- * @param array $tagAttributes the attributes of the image tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated image tag
*/
- public static function img($src, $tagAttributes = array())
+ public static function img($src, $options = array())
{
- $tagAttributes['src'] = static::url($src);
- if (!isset($tagAttributes['alt'])) {
- $tagAttributes['alt'] = '';
+ $options['src'] = static::url($src);
+ if (!isset($options['alt'])) {
+ $options['alt'] = '';
}
- return static::tag('img', null, $tagAttributes);
+ return static::tag('img', null, $options);
}
/**
@@ -375,14 +384,15 @@ class Html
* it to prevent XSS attacks.
* @param string $for the ID of the HTML element that this label is associated with.
* If this is null, the "for" attribute will not be generated.
- * @param array $tagAttributes the attributes of the label tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated label tag
*/
- public static function label($content, $for = null, $tagAttributes = array())
+ public static function label($content, $for = null, $options = array())
{
- $tagAttributes['for'] = $for;
- return static::tag('label', $content, $tagAttributes);
+ $options['for'] = $for;
+ return static::tag('label', $content, $options);
}
/**
@@ -392,19 +402,20 @@ class Html
* @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded.
* Therefore you can pass in HTML code such as an image tag. If this is is coming from end users,
* you should consider [[encode()]] it to prevent XSS attacks.
- * @param array $tagAttributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
- * If the attributes does not contain "type", a default one with value "button" will be used.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
+ * If the options does not contain "type", a "type" attribute with value "button" will be rendered.
* @return string the generated button tag
*/
- public static function button($name = null, $value = null, $content = 'Button', $tagAttributes = array())
+ public static function button($name = null, $value = null, $content = 'Button', $options = array())
{
- $tagAttributes['name'] = $name;
- $tagAttributes['value'] = $value;
- if (!isset($tagAttributes['type'])) {
- $tagAttributes['type'] = 'button';
+ $options['name'] = $name;
+ $options['value'] = $value;
+ if (!isset($options['type'])) {
+ $options['type'] = 'button';
}
- return static::tag('button', $content, $tagAttributes);
+ return static::tag('button', $content, $options);
}
/**
@@ -414,14 +425,15 @@ class Html
* @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded.
* Therefore you can pass in HTML code such as an image tag. If this is is coming from end users,
* you should consider [[encode()]] it to prevent XSS attacks.
- * @param array $tagAttributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated submit button tag
*/
- public static function submitButton($name = null, $value = null, $content = 'Submit', $tagAttributes = array())
+ public static function submitButton($name = null, $value = null, $content = 'Submit', $options = array())
{
- $tagAttributes['type'] = 'submit';
- return static::button($name, $value, $content, $tagAttributes);
+ $options['type'] = 'submit';
+ return static::button($name, $value, $content, $options);
}
/**
@@ -431,14 +443,15 @@ class Html
* @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded.
* Therefore you can pass in HTML code such as an image tag. If this is is coming from end users,
* you should consider [[encode()]] it to prevent XSS attacks.
- * @param array $tagAttributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated reset button tag
*/
- public static function resetButton($name = null, $value = null, $content = 'Reset', $tagAttributes = array())
+ public static function resetButton($name = null, $value = null, $content = 'Reset', $options = array())
{
- $tagAttributes['type'] = 'reset';
- return static::button($name, $value, $content, $tagAttributes);
+ $options['type'] = 'reset';
+ return static::button($name, $value, $content, $options);
}
/**
@@ -446,94 +459,100 @@ class Html
* @param string $type the type attribute.
* @param string $name the name attribute. If it is null, the name attribute will not be generated.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated input tag
*/
- public static function input($type, $name = null, $value = null, $tagAttributes = array())
+ public static function input($type, $name = null, $value = null, $options = array())
{
- $tagAttributes['type'] = $type;
- $tagAttributes['name'] = $name;
- $tagAttributes['value'] = $value;
- return static::tag('input', null, $tagAttributes);
+ $options['type'] = $type;
+ $options['name'] = $name;
+ $options['value'] = $value;
+ return static::tag('input', null, $options);
}
/**
* Generates an input button.
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $tagAttributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated button tag
*/
- public static function buttonInput($name, $value = 'Button', $tagAttributes = array())
+ public static function buttonInput($name, $value = 'Button', $options = array())
{
- return static::input('button', $name, $value, $tagAttributes);
+ return static::input('button', $name, $value, $options);
}
/**
* Generates a submit input button.
* @param string $name the name attribute. If it is null, the name attribute will not be generated.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $tagAttributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated button tag
*/
- public static function submitInput($name = null, $value = 'Submit', $tagAttributes = array())
+ public static function submitInput($name = null, $value = 'Submit', $options = array())
{
- return static::input('submit', $name, $value, $tagAttributes);
+ return static::input('submit', $name, $value, $options);
}
/**
* Generates a reset input button.
* @param string $name the name attribute. If it is null, the name attribute will not be generated.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $tagAttributes the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
+ * @param array $options the attributes of the button tag. The values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the tag returned.
* @return string the generated button tag
*/
- public static function resetInput($name = null, $value = 'Reset', $tagAttributes = array())
+ public static function resetInput($name = null, $value = 'Reset', $options = array())
{
- return static::input('reset', $name, $value, $tagAttributes);
+ return static::input('reset', $name, $value, $options);
}
/**
* Generates a text input field.
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated button tag
*/
- public static function textInput($name, $value = null, $tagAttributes = array())
+ public static function textInput($name, $value = null, $options = array())
{
- return static::input('text', $name, $value, $tagAttributes);
+ return static::input('text', $name, $value, $options);
}
/**
* Generates a hidden input field.
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated button tag
*/
- public static function hiddenInput($name, $value = null, $tagAttributes = array())
+ public static function hiddenInput($name, $value = null, $options = array())
{
- return static::input('hidden', $name, $value, $tagAttributes);
+ return static::input('hidden', $name, $value, $options);
}
/**
* Generates a password input field.
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated button tag
*/
- public static function passwordInput($name, $value = null, $tagAttributes = array())
+ public static function passwordInput($name, $value = null, $options = array())
{
- return static::input('password', $name, $value, $tagAttributes);
+ return static::input('password', $name, $value, $options);
}
/**
@@ -543,27 +562,29 @@ class Html
* can be obtained via $_FILES[$name] (see PHP documentation).
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated button tag
*/
- public static function fileInput($name, $value = null, $tagAttributes = array())
+ public static function fileInput($name, $value = null, $options = array())
{
- return static::input('file', $name, $value, $tagAttributes);
+ return static::input('file', $name, $value, $options);
}
/**
* Generates a text area input.
* @param string $name the input name
* @param string $value the input value. Note that it will be encoded using [[encode()]].
- * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned.
+ * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
+ * If a value is null, the corresponding attribute will not be rendered.
* @return string the generated text area tag
*/
- public static function textarea($name, $value = '', $tagAttributes = array())
+ public static function textarea($name, $value = '', $options = array())
{
- $tagAttributes['name'] = $name;
- return static::tag('textarea', static::encode($value), $tagAttributes);
+ $options['name'] = $name;
+ return static::tag('textarea', static::encode($value), $options);
}
/**
@@ -571,28 +592,29 @@ class Html
* @param string $name the name attribute.
* @param boolean $checked whether the radio button should be checked.
* @param string $value the value attribute. If it is null, the value attribute will not be rendered.
- * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned. The following attributes
- * will be specially handled and not put in the resulting tag:
+ * @param array $options the tag options in terms of name-value pairs. The following options are supported:
*
* - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute
* is present, a hidden input will be generated so that if the radio button is not checked and is submitted,
* the value of this attribute will still be submitted to the server via the hidden input.
*
+ * The rest of the options will be rendered as the attributes of the resulting tag. The values will
+ * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered.
+ *
* @return string the generated radio button tag
*/
- public static function radio($name, $checked = false, $value = '1', $tagAttributes = array())
+ public static function radio($name, $checked = false, $value = '1', $options = array())
{
- $tagAttributes['checked'] = $checked;
- $tagAttributes['value'] = $value;
- if (isset($tagAttributes['uncheck'])) {
+ $options['checked'] = $checked;
+ $options['value'] = $value;
+ if (isset($options['uncheck'])) {
// add a hidden field so that if the radio button is not selected, it still submits a value
- $hidden = static::hiddenInput($name, $tagAttributes['uncheck']);
- unset($tagAttributes['uncheck']);
+ $hidden = static::hiddenInput($name, $options['uncheck']);
+ unset($options['uncheck']);
} else {
$hidden = '';
}
- return $hidden . static::input('radio', $name, $value, $tagAttributes);
+ return $hidden . static::input('radio', $name, $value, $options);
}
/**
@@ -600,28 +622,29 @@ class Html
* @param string $name the name attribute.
* @param boolean $checked whether the checkbox should be checked.
* @param string $value the value attribute. If it is null, the value attribute will not be rendered.
- * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned. The following attributes
- * will be specially handled and not put in the resulting tag:
+ * @param array $options the tag options in terms of name-value pairs. The following options are supported:
*
* - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute
* is present, a hidden input will be generated so that if the checkbox is not checked and is submitted,
* the value of this attribute will still be submitted to the server via the hidden input.
*
+ * The rest of the options will be rendered as the attributes of the resulting tag. The values will
+ * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered.
+ *
* @return string the generated checkbox tag
*/
- public static function checkbox($name, $checked = false, $value = '1', $tagAttributes = array())
+ public static function checkbox($name, $checked = false, $value = '1', $options = array())
{
- $tagAttributes['checked'] = $checked;
- $tagAttributes['value'] = $value;
- if (isset($tagAttributes['uncheck'])) {
+ $options['checked'] = $checked;
+ $options['value'] = $value;
+ if (isset($options['uncheck'])) {
// add a hidden field so that if the checkbox is not selected, it still submits a value
- $hidden = static::hiddenInput($name, $tagAttributes['uncheck']);
- unset($tagAttributes['uncheck']);
+ $hidden = static::hiddenInput($name, $options['uncheck']);
+ unset($options['uncheck']);
} else {
$hidden = '';
}
- return $hidden . static::input('checkbox', $name, $value, $tagAttributes);
+ return $hidden . static::input('checkbox', $name, $value, $options);
}
/**
@@ -636,12 +659,10 @@ class Html
*
* Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
* the labels will also be HTML-encoded.
- * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned. The following attributes
- * will be specially handled and not put in the resulting tag:
+ * @param array $options the tag options in terms of name-value pairs. The following options are supported:
*
* - prompt: string, a prompt text to be displayed as the first option;
- * - options: array, the attributes for the option tags. The array keys must be valid option values,
+ * - options: array, the attributes for the select option tags. The array keys must be valid option values,
* and the array values are the extra attributes for the corresponding option tags. For example,
*
* ~~~
@@ -653,13 +674,17 @@ class Html
*
* - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options',
* except that the array keys represent the optgroup labels specified in $items.
+ *
+ * The rest of the options will be rendered as the attributes of the resulting tag. The values will
+ * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered.
+ *
* @return string the generated drop-down list tag
*/
- public static function dropDownList($name, $selection = null, $items = array(), $tagAttributes = array())
+ public static function dropDownList($name, $selection = null, $items = array(), $options = array())
{
- $tagAttributes['name'] = $name;
- $options = static::renderSelectOptions($selection, $items, $tagAttributes);
- return static::tag('select', "\n" . $options . "\n", $tagAttributes);
+ $options['name'] = $name;
+ $selectOptions = static::renderSelectOptions($selection, $items, $options);
+ return static::tag('select', "\n" . $selectOptions . "\n", $options);
}
/**
@@ -674,12 +699,10 @@ class Html
*
* Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
* the labels will also be HTML-encoded.
- * @param array $tagAttributes the attributes of the input tag. The values will be HTML-encoded using [[encode()]].
- * Attributes whose value is null will be ignored and not put in the tag returned. The following attributes
- * will be specially handled and not put in the resulting tag:
+ * @param array $options the tag options in terms of name-value pairs. The following options are supported:
*
* - prompt: string, a prompt text to be displayed as the first option;
- * - options: array, the attributes for the option tags. The array keys must be valid option values,
+ * - options: array, the attributes for the select option tags. The array keys must be valid option values,
* and the array values are the extra attributes for the corresponding option tags. For example,
*
* ~~~
@@ -694,29 +717,33 @@ class Html
* - unselect: string, the value that will be submitted when no option is selected.
* When this attribute is set, a hidden field will be generated so that if no option is selected in multiple
* mode, we can still obtain the posted unselect value.
+ *
+ * The rest of the options will be rendered as the attributes of the resulting tag. The values will
+ * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered.
+ *
* @return string the generated list box tag
*/
- public static function listBox($name, $selection = null, $items = array(), $tagAttributes = array())
+ public static function listBox($name, $selection = null, $items = array(), $options = array())
{
- if (!isset($tagAttributes['size'])) {
- $tagAttributes['size'] = 4;
+ if (!isset($options['size'])) {
+ $options['size'] = 4;
}
- if (isset($tagAttributes['multiple']) && $tagAttributes['multiple'] && substr($name, -2) !== '[]') {
+ if (isset($options['multiple']) && $options['multiple'] && substr($name, -2) !== '[]') {
$name .= '[]';
}
- $tagAttributes['name'] = $name;
- if (isset($tagAttributes['unselect'])) {
+ $options['name'] = $name;
+ if (isset($options['unselect'])) {
// add a hidden field so that if the list box has no option being selected, it still submits a value
if (substr($name, -2) === '[]') {
$name = substr($name, 0, -2);
}
- $hidden = static::hiddenInput($name, $tagAttributes['unselect']);
- unset($tagAttributes['unselect']);
+ $hidden = static::hiddenInput($name, $options['unselect']);
+ unset($options['unselect']);
} else {
$hidden = '';
}
- $options = static::renderSelectOptions($selection, $items, $tagAttributes);
- return $hidden . static::tag('select', "\n" . $options . "\n", $tagAttributes);
+ $selectOptions = static::renderSelectOptions($selection, $items, $options);
+ return $hidden . static::tag('select', "\n" . $selectOptions . "\n", $options);
}
/**
@@ -757,7 +784,7 @@ class Html
foreach ($items as $value => $label) {
$checked = $selection !== null &&
(!is_array($selection) && !strcmp($value, $selection)
- || is_array($selection) && in_array($value, $selection));
+ || is_array($selection) && in_array($value, $selection));
if ($formatter !== null) {
$lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value);
} else {
@@ -811,7 +838,7 @@ class Html
foreach ($items as $value => $label) {
$checked = $selection !== null &&
(!is_array($selection) && !strcmp($value, $selection)
- || is_array($selection) && in_array($value, $selection));
+ || is_array($selection) && in_array($value, $selection));
if ($formatter !== null) {
$lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value);
} else {
@@ -843,23 +870,23 @@ class Html
*
* Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in
* the labels will also be HTML-encoded.
- * @param array $tagAttributes the attributes parameter that is passed to the [[dropDownList()]] or [[listBox()]] call.
+ * @param array $tagOptions the $options parameter that is passed to the [[dropDownList()]] or [[listBox()]] call.
* This method will take out these elements, if any: "prompt", "options" and "groups". See more details
* in [[dropDownList()]] for the explanation of these elements.
*
* @return string the generated list options
*/
- public static function renderSelectOptions($selection, $items, &$tagAttributes = array())
+ public static function renderSelectOptions($selection, $items, &$tagOptions = array())
{
$lines = array();
- if (isset($tagAttributes['prompt'])) {
- $prompt = str_replace(' ', ' ', static::encode($tagAttributes['prompt']));
+ if (isset($tagOptions['prompt'])) {
+ $prompt = str_replace(' ', ' ', static::encode($tagOptions['prompt']));
$lines[] = static::tag('option', $prompt, array('value' => ''));
}
- $options = isset($tagAttributes['options']) ? $tagAttributes['options'] : array();
- $groups = isset($tagAttributes['groups']) ? $tagAttributes['groups'] : array();
- unset($tagAttributes['prompt'], $tagAttributes['options'], $tagAttributes['groups']);
+ $options = isset($tagOptions['options']) ? $tagOptions['options'] : array();
+ $groups = isset($tagOptions['groups']) ? $tagOptions['groups'] : array();
+ unset($tagOptions['prompt'], $tagOptions['options'], $tagOptions['groups']);
foreach ($items as $key => $value) {
if (is_array($value)) {
@@ -873,7 +900,7 @@ class Html
$attrs['value'] = $key;
$attrs['selected'] = $selection !== null &&
(!is_array($selection) && !strcmp($key, $selection)
- || is_array($selection) && in_array($key, $selection));
+ || is_array($selection) && in_array($key, $selection));
$lines[] = static::tag('option', str_replace(' ', ' ', static::encode($value)), $attrs);
}
}
@@ -885,26 +912,26 @@ class Html
* Renders the HTML tag attributes.
* Boolean attributes such as s 'checked', 'disabled', 'readonly', will be handled specially
* according to [[booleanAttributes]] and [[showBooleanAttributeValues]].
- * @param array $tagAttributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]].
+ * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]].
* Attributes whose value is null will be ignored and not put in the rendering result.
* @return string the rendering result. If the attributes are not empty, they will be rendered
* into a string with a leading white space (such that it can be directly appended to the tag name
* in a tag. If there is no attribute, an empty string will be returned.
*/
- public static function renderTagAttributes($tagAttributes)
+ public static function renderTagAttributes($attributes)
{
- if (count($tagAttributes) > 1) {
+ if (count($attributes) > 1) {
$sorted = array();
foreach (static::$attributeOrder as $name) {
- if (isset($tagAttributes[$name])) {
- $sorted[$name] = $tagAttributes[$name];
+ if (isset($attributes[$name])) {
+ $sorted[$name] = $attributes[$name];
}
}
- $tagAttributes = array_merge($sorted, $tagAttributes);
+ $attributes = array_merge($sorted, $attributes);
}
$html = '';
- foreach ($tagAttributes as $name => $value) {
+ foreach ($attributes as $name => $value) {
if (isset(static::$booleanAttributes[strtolower($name)])) {
if ($value || strcasecmp($name, $value) === 0) {
$html .= static::$showBooleanAttributeValues ? " $name=\"$name\"" : " $name";
diff --git a/tests/unit/framework/util/HtmlTest.php b/tests/unit/framework/util/HtmlTest.php
index 35ae24c..a887f29 100644
--- a/tests/unit/framework/util/HtmlTest.php
+++ b/tests/unit/framework/util/HtmlTest.php
@@ -233,9 +233,26 @@ class HtmlTest extends \yii\test\TestCase
public function testDropDownList()
{
- $this->assertEquals("", Html::dropDownList('test'));
- $this->assertEquals("", Html::dropDownList('test', null, $this->getDataItems()));
- $this->assertEquals("", Html::dropDownList('test', 'value2', $this->getDataItems()));
+ $expected = <<
+
+
+EOD;
+ $this->assertEquals($expected, Html::dropDownList('test'));
+ $expected = <<
+
+
+
+EOD;
+ $this->assertEquals($expected, Html::dropDownList('test', null, $this->getDataItems()));
+ $expected = <<
+
+
+
+EOD;
+ $this->assertEquals($expected, Html::dropDownList('test', 'value2', $this->getDataItems()));
}
public function testListBox()
From c69e61801149d97992d0ddd6580a483cb668f1d7 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Wed, 13 Mar 2013 17:25:27 -0400
Subject: [PATCH 66/84] ActiveForm WIP
---
framework/widgets/ActiveForm.php | 128 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 127 insertions(+), 1 deletion(-)
diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php
index 77fd9fd..2bac53a 100644
--- a/framework/widgets/ActiveForm.php
+++ b/framework/widgets/ActiveForm.php
@@ -1,6 +1,132 @@
+ * @since 2.0
+ */
+class ActiveForm extends Widget
{
+ /**
+ * @var mixed the form action URL (see {@link CHtml::normalizeUrl} for details about this parameter).
+ * If not set, the current page URL is used.
+ */
+ public $action = '';
+ /**
+ * @var string the form submission method. This should be either 'post' or 'get'.
+ * Defaults to 'post'.
+ */
+ public $method = 'post';
+ /**
+ * @var string the CSS class name for error messages. Defaults to 'errorMessage'.
+ * Individual {@link error} call may override this value by specifying the 'class' HTML option.
+ */
+ public $errorMessageCssClass = 'errorMessage';
+ /**
+ * @var array additional HTML attributes that should be rendered for the form tag.
+ */
+ public $htmlOptions = array();
+ /**
+ * @var boolean whether to enable data validation via AJAX. Defaults to false.
+ * When this property is set true, you should respond to the AJAX validation request on the server side as shown below:
+ *
+ */
+ public $enableAjaxValidation = false;
+ /**
+ * @var boolean whether to enable client-side data validation. Defaults to false.
+ *
+ * When this property is set true, client-side validation will be performed by validators
+ * that support it (see {@link CValidator::enableClientValidation} and {@link CValidator::clientValidateAttribute}).
+ *
+ * @see error
+ * @since 1.1.7
+ */
+ public $enableClientValidation = false;
+
+
+ public function errorSummary($model, $options = array())
+ {
+ }
+
+ public function error($model, $attribute, $options = array())
+ {
+ }
+
+ public function label($model, $attribute, $options = array())
+ {
+ }
+
+ public function input($type, $model, $attribute, $options = array())
+ {
+ return '';
+ }
+
+ public function textInput($model, $attribute, $options = array())
+ {
+ return $this->input('text', $model, $attribute, $options);
+ }
+
+ public function hiddenInput($model, $attribute, $options = array())
+ {
+ return $this->input('hidden', $model, $attribute, $options);
+ }
+
+ public function passwordInput($model, $attribute, $options = array())
+ {
+ return $this->input('password', $model, $attribute, $options);
+ }
+
+ public function fileInput($model, $attribute, $options = array())
+ {
+ return $this->input('file', $model, $attribute, $options);
+ }
+
+ public function textarea($model, $attribute, $options = array())
+ {
+ }
+
+ public function radio($model, $attribute, $value = '1', $options = array())
+ {
+ }
+
+ public function checkbox($model, $attribute, $value = '1', $options = array())
+ {
+ }
+
+ public function dropDownList($model, $attribute, $items, $options = array())
+ {
+ }
+
+ public function listBox($model, $attribute, $items, $options = array())
+ {
+ }
+
+ public function checkboxList($model, $attribute, $items, $options = array())
+ {
+ }
+ public function radioList($model, $attribute, $items, $options = array())
+ {
+ }
}
From b71e83016fedf1c874f3514346f720db6724feea Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Thu, 14 Mar 2013 20:44:58 -0400
Subject: [PATCH 67/84] ActiveForm WIP
---
framework/base/Model.php | 40 ++++++++--------
framework/util/Html.php | 46 +++++++++---------
framework/widgets/ActiveForm.php | 100 +++++++++++++++++++++++++++------------
3 files changed, 114 insertions(+), 72 deletions(-)
diff --git a/framework/base/Model.php b/framework/base/Model.php
index 50b1058..b761ada 100644
--- a/framework/base/Model.php
+++ b/framework/base/Model.php
@@ -420,12 +420,31 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
}
/**
+ * Returns the first error of every attribute in the model.
+ * @return array the first errors. An empty array will be returned if there is no error.
+ */
+ public function getFirstErrors()
+ {
+ if (empty($this->_errors)) {
+ return array();
+ } else {
+ $errors = array();
+ foreach ($this->_errors as $errors) {
+ if (isset($errors[0])) {
+ $errors[] = $errors[0];
+ }
+ }
+ }
+ return $errors;
+ }
+
+ /**
* Returns the first error of the specified attribute.
* @param string $attribute attribute name.
* @return string the error message. Null is returned if no error.
* @see getErrors
*/
- public function getError($attribute)
+ public function getFirstError($attribute)
{
return isset($this->_errors[$attribute]) ? reset($this->_errors[$attribute]) : null;
}
@@ -441,25 +460,6 @@ class Model extends Component implements \IteratorAggregate, \ArrayAccess
}
/**
- * Adds a list of errors.
- * @param array $errors a list of errors. The array keys must be attribute names.
- * The array values should be error messages. If an attribute has multiple errors,
- * these errors must be given in terms of an array.
- */
- public function addErrors($errors)
- {
- foreach ($errors as $attribute => $error) {
- if (is_array($error)) {
- foreach ($error as $e) {
- $this->_errors[$attribute][] = $e;
- }
- } else {
- $this->_errors[$attribute][] = $error;
- }
- }
- }
-
- /**
* Removes errors for all attributes or a single attribute.
* @param string $attribute attribute name. Use null to remove errors for all attribute.
*/
diff --git a/framework/util/Html.php b/framework/util/Html.php
index ebba40a..a7b744b 100644
--- a/framework/util/Html.php
+++ b/framework/util/Html.php
@@ -152,7 +152,7 @@ class Html
* @param string $name the tag name
* @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded.
* If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks.
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated HTML tag
@@ -172,7 +172,7 @@ class Html
/**
* Generates a start tag.
* @param string $name the tag name
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated start tag
@@ -209,7 +209,7 @@ class Html
/**
* Generates a style tag.
* @param string $content the style content
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* If the options does not contain "type", a "type" attribute with value "text/css" will be used.
@@ -226,7 +226,7 @@ class Html
/**
* Generates a script tag.
* @param string $content the script content
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered.
@@ -243,7 +243,7 @@ class Html
/**
* Generates a link tag that refers to an external CSS file.
* @param array|string $url the URL of the external CSS file. This parameter will be processed by [[url()]].
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated link tag
@@ -260,7 +260,7 @@ class Html
/**
* Generates a script tag that refers to an external JavaScript file.
* @param string $url the URL of the external JavaScript file. This parameter will be processed by [[url()]].
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated script tag
@@ -276,8 +276,8 @@ class Html
/**
* Generates a form start tag.
* @param array|string $action the form action URL. This parameter will be processed by [[url()]].
- * @param string $method form method, either "post" or "get" (case-insensitive)
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param string $method the form submission method, either "post" or "get" (case-insensitive)
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated form start tag.
@@ -329,7 +329,7 @@ class Html
* @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[url()]]
* and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute
* will not be generated.
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated hyperlink
@@ -350,7 +350,7 @@ class Html
* it to prevent XSS attacks.
* @param string $email email address. If this is null, the first parameter (link body) will be treated
* as the email address and used.
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated mailto link
@@ -363,7 +363,7 @@ class Html
/**
* Generates an image tag.
* @param string $src the image URL. This parameter will be processed by [[url()]].
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated image tag
@@ -384,7 +384,7 @@ class Html
* it to prevent XSS attacks.
* @param string $for the ID of the HTML element that this label is associated with.
* If this is null, the "for" attribute will not be generated.
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated label tag
@@ -402,7 +402,7 @@ class Html
* @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded.
* Therefore you can pass in HTML code such as an image tag. If this is is coming from end users,
* you should consider [[encode()]] it to prevent XSS attacks.
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* If the options does not contain "type", a "type" attribute with value "button" will be rendered.
@@ -425,7 +425,7 @@ class Html
* @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded.
* Therefore you can pass in HTML code such as an image tag. If this is is coming from end users,
* you should consider [[encode()]] it to prevent XSS attacks.
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated submit button tag
@@ -443,7 +443,7 @@ class Html
* @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded.
* Therefore you can pass in HTML code such as an image tag. If this is is coming from end users,
* you should consider [[encode()]] it to prevent XSS attacks.
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated reset button tag
@@ -459,7 +459,7 @@ class Html
* @param string $type the type attribute.
* @param string $name the name attribute. If it is null, the name attribute will not be generated.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated input tag
@@ -476,7 +476,7 @@ class Html
* Generates an input button.
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated button tag
@@ -490,7 +490,7 @@ class Html
* Generates a submit input button.
* @param string $name the name attribute. If it is null, the name attribute will not be generated.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated button tag
@@ -517,7 +517,7 @@ class Html
* Generates a text input field.
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated button tag
@@ -531,7 +531,7 @@ class Html
* Generates a hidden input field.
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated button tag
@@ -545,7 +545,7 @@ class Html
* Generates a password input field.
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated button tag
@@ -562,7 +562,7 @@ class Html
* can be obtained via $_FILES[$name] (see PHP documentation).
* @param string $name the name attribute.
* @param string $value the value attribute. If it is null, the value attribute will not be generated.
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated button tag
@@ -576,7 +576,7 @@ class Html
* Generates a text area input.
* @param string $name the input name
* @param string $value the input value. Note that it will be encoded using [[encode()]].
- * @param array $options the tag options in terms of name-value pairs. These be rendered as
+ * @param array $options the tag options in terms of name-value pairs. These will be rendered as
* the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]].
* If a value is null, the corresponding attribute will not be rendered.
* @return string the generated text area tag
diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php
index 2bac53a..4a0f89c 100644
--- a/framework/widgets/ActiveForm.php
+++ b/framework/widgets/ActiveForm.php
@@ -7,7 +7,11 @@
namespace yii\widgets;
+use Yii;
use yii\base\Widget;
+use yii\base\Model;
+use yii\util\Html;
+use yii\util\ArrayHelper;
/**
* ActiveForm ...
@@ -18,8 +22,7 @@ use yii\base\Widget;
class ActiveForm extends Widget
{
/**
- * @var mixed the form action URL (see {@link CHtml::normalizeUrl} for details about this parameter).
- * If not set, the current page URL is used.
+ * @param array|string $action the form action URL. This parameter will be processed by [[\yii\util\Html::url()]].
*/
public $action = '';
/**
@@ -28,49 +31,88 @@ class ActiveForm extends Widget
*/
public $method = 'post';
/**
- * @var string the CSS class name for error messages. Defaults to 'errorMessage'.
- * Individual {@link error} call may override this value by specifying the 'class' HTML option.
+ * @var string the default CSS class for the error summary container.
+ * @see errorSummary()
*/
- public $errorMessageCssClass = 'errorMessage';
+ public $errorSummaryClass = 'yii-error-summary';
/**
- * @var array additional HTML attributes that should be rendered for the form tag.
+ * @var string the default CSS class that indicates an input has error.
+ * This is
*/
- public $htmlOptions = array();
- /**
- * @var boolean whether to enable data validation via AJAX. Defaults to false.
- * When this property is set true, you should respond to the AJAX validation request on the server side as shown below:
- *
";
$options['style'] = isset($options['style']) ? rtrim($options['style'], ';') . '; display:none' : 'display:none';
- return Html::tag($container, $header . $content . $footer, $options);
+ return Html::tag($tag, $header . $content . $footer, $options);
}
}
@@ -103,25 +110,32 @@ class ActiveForm extends Widget
*/
public function error($model, $attribute, $options = array())
{
- self::resolveName($model, $attribute); // turn [a][b]attr into attr
- $container = isset($options['container']) ? $options['container'] : 'div';
- unset($options['container']);
+ $attribute = $this->normalizeAttributeName($attribute);
+ $this->getInputName($model, $attribute);
+ $tag = isset($options['tag']) ? $options['tag'] : 'div';
+ unset($options['tag']);
$error = $model->getFirstError($attribute);
- return Html::tag($container, Html::encode($error), $options);
- }
-
- public function resolveAttributeName($name)
- {
-
+ return Html::tag($tag, Html::encode($error), $options);
}
+ /**
+ * @param Model $model
+ * @param string $attribute
+ * @param array $options
+ * @return string
+ */
public function label($model, $attribute, $options = array())
{
+ $attribute = $this->normalizeAttributeName($attribute);
+ $label = $model->getAttributeLabel($attribute);
+ return Html::label(Html::encode($label), isset($options['for']) ? $options['for'] : null, $options);
}
public function input($type, $model, $attribute, $options = array())
{
- return '';
+ $value = $this->getAttributeValue($model, $attribute);
+ $name = $this->getInputName($model, $attribute);
+ return Html::input($type, $name, $value, $options);
}
public function textInput($model, $attribute, $options = array())
@@ -146,29 +160,119 @@ class ActiveForm extends Widget
public function textarea($model, $attribute, $options = array())
{
+ $value = $this->getAttributeValue($model, $attribute);
+ $name = $this->getInputName($model, $attribute);
+ return Html::textarea($name, $value, $options);
}
public function radio($model, $attribute, $value = '1', $options = array())
{
+ $checked = $this->getAttributeValue($model, $attribute);
+ $name = $this->getInputName($model, $attribute);
+ if (!array_key_exists('uncheck', $options)) {
+ $options['unchecked'] = '0';
+ }
+ return Html::radio($name, $checked, $value, $options);
}
public function checkbox($model, $attribute, $value = '1', $options = array())
{
+ $checked = $this->getAttributeValue($model, $attribute);
+ $name = $this->getInputName($model, $attribute);
+ if (!array_key_exists('uncheck', $options)) {
+ $options['unchecked'] = '0';
+ }
+ return Html::checkbox($name, $checked, $value, $options);
}
public function dropDownList($model, $attribute, $items, $options = array())
{
+ $checked = $this->getAttributeValue($model, $attribute);
+ $name = $this->getInputName($model, $attribute);
+ return Html::dropDownList($name, $checked, $items, $options);
}
public function listBox($model, $attribute, $items, $options = array())
{
+ $checked = $this->getAttributeValue($model, $attribute);
+ $name = $this->getInputName($model, $attribute);
+ if (!array_key_exists('unselect', $options)) {
+ $options['unselect'] = '0';
+ }
+ return Html::listBox($name, $checked, $items, $options);
}
public function checkboxList($model, $attribute, $items, $options = array())
{
+ $checked = $this->getAttributeValue($model, $attribute);
+ $name = $this->getInputName($model, $attribute);
+ if (!array_key_exists('unselect', $options)) {
+ $options['unselect'] = '0';
+ }
+ return Html::checkboxList($name, $checked, $items, $options);
}
public function radioList($model, $attribute, $items, $options = array())
{
+ $checked = $this->getAttributeValue($model, $attribute);
+ $name = $this->getInputName($model, $attribute);
+ if (!array_key_exists('unselect', $options)) {
+ $options['unselect'] = '0';
+ }
+ return Html::radioList($name, $checked, $items, $options);
+ }
+
+ public function getInputName($model, $attribute)
+ {
+ $class = get_class($model);
+ if (isset($this->modelMap[$class])) {
+ $class = $this->modelMap[$class];
+ } elseif (($pos = strrpos($class, '\\')) !== false) {
+ $class = substr($class, $pos);
+ }
+ if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) {
+ throw new InvalidParamException('Attribute name must contain word characters only.');
+ }
+ $prefix = $matches[1];
+ $attribute = $matches[2];
+ $suffix = $matches[3];
+ if ($class === '' && $prefix === '') {
+ return $attribute . $suffix;
+ } elseif ($class !== '') {
+ return $class . $prefix . "[$attribute]" . $suffix;
+ } else {
+ throw new InvalidParamException('Model name cannot be mapped to empty for tabular inputs.');
+ }
+ }
+
+ public function getAttributeValue($model, $attribute)
+ {
+ if (!preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) {
+ throw new InvalidParamException('Attribute name must contain word characters only.');
+ }
+ $attribute = $matches[2];
+ $index = $matches[3];
+ if ($index === '') {
+ return $model->$attribute;
+ } else {
+ $value = $model->$attribute;
+ foreach (explode('][', trim($index, '[]')) as $id) {
+ if ((is_array($value) || $value instanceof \ArrayAccess) && isset($value[$id])) {
+ $value = $value[$id];
+ } else {
+ return null;
+ }
+ }
+ return $value;
+ }
+ }
+
+ public function normalizeAttributeName($attribute)
+ {
+ if (preg_match('/(^|.*\])(\w+)(\[.*|$)/', $attribute, $matches)) {
+ return $matches[2];
+ } else {
+ throw new InvalidParamException('Attribute name must contain word characters only.');
+ }
}
}
From b2b9b4f319551f31d1c41d43fa93635d4f8ac298 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Sat, 16 Mar 2013 10:18:44 -0400
Subject: [PATCH 69/84] test wip
---
framework/test/TestCase.php | 4 ++--
framework/test/WebTestCase.php | 25 +++++++++++++++++++++++++
framework/widgets/ActiveForm.php | 2 +-
3 files changed, 28 insertions(+), 3 deletions(-)
create mode 100644 framework/test/WebTestCase.php
diff --git a/framework/test/TestCase.php b/framework/test/TestCase.php
index 3fd92c0..f190e5a 100644
--- a/framework/test/TestCase.php
+++ b/framework/test/TestCase.php
@@ -10,9 +10,9 @@
namespace yii\test;
require_once('PHPUnit/Runner/Version.php');
-spl_autoload_unregister(array('YiiBase','autoload'));
+spl_autoload_unregister(array('Yii','autoload'));
require_once('PHPUnit/Autoload.php');
-spl_autoload_register(array('YiiBase','autoload')); // put yii's autoloader at the end
+spl_autoload_register(array('Yii','autoload')); // put yii's autoloader at the end
/**
* TestCase is the base class for all test case classes.
diff --git a/framework/test/WebTestCase.php b/framework/test/WebTestCase.php
new file mode 100644
index 0000000..39162c9
--- /dev/null
+++ b/framework/test/WebTestCase.php
@@ -0,0 +1,25 @@
+
+ * @since 2.0
+ */
+abstract class WebTestCase extends \PHPUnit_Extensions_SeleniumTestCase
+{
+}
diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php
index 4a0f89c..b8c8f2c 100644
--- a/framework/widgets/ActiveForm.php
+++ b/framework/widgets/ActiveForm.php
@@ -63,7 +63,7 @@ class ActiveForm extends Widget
$models = array($models);
}
- $showAll = isset($options['showAll']) && $options['showAll']);
+ $showAll = isset($options['showAll']) && $options['showAll'];
$lines = array();
/** @var $model Model */
foreach ($models as $model) {
From 3b610926f4eb569ea3992f5ed2a8b3e65b615a01 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Sat, 16 Mar 2013 10:21:15 -0400
Subject: [PATCH 70/84] Added web test folders.
---
tests/web/app/assets/.gitignore | 1 +
tests/web/app/protected/runtime/.gitignore | 1 +
2 files changed, 2 insertions(+)
create mode 100644 tests/web/app/assets/.gitignore
create mode 100644 tests/web/app/protected/runtime/.gitignore
diff --git a/tests/web/app/assets/.gitignore b/tests/web/app/assets/.gitignore
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/tests/web/app/assets/.gitignore
@@ -0,0 +1 @@
+*
diff --git a/tests/web/app/protected/runtime/.gitignore b/tests/web/app/protected/runtime/.gitignore
new file mode 100644
index 0000000..72e8ffc
--- /dev/null
+++ b/tests/web/app/protected/runtime/.gitignore
@@ -0,0 +1 @@
+*
From 9a4f4f85d26ad796bdb6440fd895240f860a2e8d Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Mon, 18 Mar 2013 08:42:12 -0400
Subject: [PATCH 71/84] wip
---
tests/web/app/index.php | 6 +++++
tests/web/app/protected/config/main.php | 3 +++
.../app/protected/controllers/SiteController.php | 30 ++++++++++++++++++++++
tests/web/app/protected/views/site/index.php | 8 ++++++
4 files changed, 47 insertions(+)
create mode 100644 tests/web/app/index.php
create mode 100644 tests/web/app/protected/config/main.php
create mode 100644 tests/web/app/protected/controllers/SiteController.php
create mode 100644 tests/web/app/protected/views/site/index.php
diff --git a/tests/web/app/index.php b/tests/web/app/index.php
new file mode 100644
index 0000000..4cfa1ab
--- /dev/null
+++ b/tests/web/app/index.php
@@ -0,0 +1,6 @@
+run();
diff --git a/tests/web/app/protected/config/main.php b/tests/web/app/protected/config/main.php
new file mode 100644
index 0000000..eed6d54
--- /dev/null
+++ b/tests/web/app/protected/config/main.php
@@ -0,0 +1,3 @@
+ 'item 1',
+ 'value 2' => 'item 2',
+ 'value 3' => 'item 3',
+ ), isset($_POST['test']) ? $_POST['test'] : null,
+ function ($index, $label, $name, $value, $checked) {
+ return Html::label(
+ $label . ' ' . Html::checkbox($name, $value, $checked),
+ null, array('class' => 'inline checkbox')
+ );
+ });
+ echo Html::submitButton();
+ echo Html::endForm();
+ print_r($_POST);
+ }
+}
\ No newline at end of file
diff --git a/tests/web/app/protected/views/site/index.php b/tests/web/app/protected/views/site/index.php
new file mode 100644
index 0000000..5decb56
--- /dev/null
+++ b/tests/web/app/protected/views/site/index.php
@@ -0,0 +1,8 @@
+
Date: Wed, 20 Mar 2013 22:03:13 -0400
Subject: [PATCH 72/84] refactored View.
---
framework/YiiBase.php | 46 +-
framework/base/Application.php | 44 +-
framework/base/Controller.php | 144 +++++-
framework/base/ErrorHandler.php | 9 +-
framework/base/Theme.php | 9 +-
framework/base/View.php | 509 +++++++--------------
framework/base/Widget.php | 77 +++-
framework/caching/FileCache.php | 3 -
.../console/controllers/MigrateController.php | 2 +-
framework/util/FileHelper.php | 2 +-
framework/views/error.php | 10 +-
framework/views/exception.php | 16 +-
framework/web/Session.php | 2 +-
13 files changed, 419 insertions(+), 454 deletions(-)
diff --git a/framework/YiiBase.php b/framework/YiiBase.php
index 392dd1c..678856c 100644
--- a/framework/YiiBase.php
+++ b/framework/YiiBase.php
@@ -4,9 +4,9 @@
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
-
use yii\base\Exception;
use yii\base\InvalidConfigException;
+use yii\base\InvalidParamException;
use yii\logging\Logger;
/**
@@ -94,7 +94,7 @@ class YiiBase
*/
public static $objectConfig = array();
- private static $_imported = array(); // alias => class name or directory
+ private static $_imported = array(); // alias => class name or directory
private static $_logger;
/**
@@ -159,9 +159,7 @@ class YiiBase
return self::$_imported[$alias] = $className;
}
- if (($path = static::getAlias(dirname($alias))) === false) {
- throw new Exception('Invalid path alias: ' . $alias);
- }
+ $path = static::getAlias(dirname($alias));
if ($isClass) {
if ($forceInclude) {
@@ -191,24 +189,30 @@ class YiiBase
*
* Note, this method does not ensure the existence of the resulting path.
* @param string $alias alias
+ * @param boolean $throwException whether to throw an exception if the given alias is invalid.
+ * If this is false and an invalid alias is given, false will be returned by this method.
* @return string|boolean path corresponding to the alias, false if the root alias is not previously registered.
* @see setAlias
*/
- public static function getAlias($alias)
+ public static function getAlias($alias, $throwException = true)
{
- if (!is_string($alias)) {
- return false;
- } elseif (isset(self::$aliases[$alias])) {
- return self::$aliases[$alias];
- } elseif ($alias === '' || $alias[0] !== '@') { // not an alias
- return $alias;
- } elseif (($pos = strpos($alias, '/')) !== false) {
- $rootAlias = substr($alias, 0, $pos);
- if (isset(self::$aliases[$rootAlias])) {
- return self::$aliases[$alias] = self::$aliases[$rootAlias] . substr($alias, $pos);
+ if (is_string($alias)) {
+ if (isset(self::$aliases[$alias])) {
+ return self::$aliases[$alias];
+ } elseif ($alias === '' || $alias[0] !== '@') { // not an alias
+ return $alias;
+ } elseif (($pos = strpos($alias, '/')) !== false || ($pos = strpos($alias, '\\')) !== false) {
+ $rootAlias = substr($alias, 0, $pos);
+ if (isset(self::$aliases[$rootAlias])) {
+ return self::$aliases[$alias] = self::$aliases[$rootAlias] . substr($alias, $pos);
+ }
}
}
- return false;
+ if ($throwException) {
+ throw new InvalidParamException("Invalid path alias: $alias");
+ } else {
+ return false;
+ }
}
/**
@@ -236,10 +240,8 @@ class YiiBase
unset(self::$aliases[$alias]);
} elseif ($path[0] !== '@') {
self::$aliases[$alias] = rtrim($path, '\\/');
- } elseif (($p = static::getAlias($path)) !== false) {
- self::$aliases[$alias] = $p;
} else {
- throw new Exception('Invalid path: ' . $path);
+ self::$aliases[$alias] = static::getAlias($path);
}
}
@@ -273,14 +275,14 @@ class YiiBase
// namespaced class, e.g. yii\base\Component
// convert namespace to path alias, e.g. yii\base\Component to @yii/base/Component
$alias = '@' . str_replace('\\', '/', ltrim($className, '\\'));
- if (($path = static::getAlias($alias)) !== false) {
+ if (($path = static::getAlias($alias, false)) !== false) {
$classFile = $path . '.php';
}
} elseif (($pos = strpos($className, '_')) !== false) {
// PEAR-styled class, e.g. PHPUnit_Framework_TestCase
// convert class name to path alias, e.g. PHPUnit_Framework_TestCase to @PHPUnit/Framework/TestCase
$alias = '@' . str_replace('_', '/', $className);
- if (($path = static::getAlias($alias)) !== false) {
+ if (($path = static::getAlias($alias, false)) !== false) {
$classFile = $path . '.php';
}
}
diff --git a/framework/base/Application.php b/framework/base/Application.php
index a60bf90..bbc4601 100644
--- a/framework/base/Application.php
+++ b/framework/base/Application.php
@@ -160,28 +160,28 @@ class Application extends Module
*/
public function handleFatalError()
{
- if(YII_ENABLE_ERROR_HANDLER) {
+ if (YII_ENABLE_ERROR_HANDLER) {
$error = error_get_last();
- if(ErrorException::isFatalErorr($error)) {
+ if (ErrorException::isFatalErorr($error)) {
unset($this->_memoryReserve);
$exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']);
- if(function_exists('xdebug_get_function_stack')) {
+ if (function_exists('xdebug_get_function_stack')) {
$trace = array_slice(array_reverse(xdebug_get_function_stack()), 4, -1);
- foreach($trace as &$frame) {
- if(!isset($frame['function'])) {
+ foreach ($trace as &$frame) {
+ if (!isset($frame['function'])) {
$frame['function'] = 'unknown';
}
// XDebug < 2.1.1: http://bugs.xdebug.org/view.php?id=695
- if(!isset($frame['type'])) {
+ if (!isset($frame['type'])) {
$frame['type'] = '::';
}
// XDebug has a different key name
$frame['args'] = array();
- if(isset($frame['params']) && !isset($frame['args'])) {
+ if (isset($frame['params']) && !isset($frame['args'])) {
$frame['args'] = $frame['params'];
}
}
@@ -214,8 +214,8 @@ class Application extends Module
$this->beforeRequest();
// Allocating twice more than required to display memory exhausted error
// in case of trying to allocate last 1 byte while all memory is taken.
- $this->_memoryReserve = str_repeat('x', 1024*256);
- register_shutdown_function(array($this,'end'),0,false);
+ $this->_memoryReserve = str_repeat('x', 1024 * 256);
+ register_shutdown_function(array($this, 'end'), 0, false);
$status = $this->processRequest();
$this->afterRequest();
return $status;
@@ -346,15 +346,6 @@ class Application extends Module
}
/**
- * Returns the application theme.
- * @return Theme the theme that this application is currently using.
- */
- public function getTheme()
- {
- return $this->getComponent('theme');
- }
-
- /**
* Returns the cache component.
* @return \yii\caching\Cache the cache application component. Null if the component is not enabled.
*/
@@ -373,12 +364,12 @@ class Application extends Module
}
/**
- * Returns the view renderer.
- * @return ViewRenderer the view renderer used by this application.
+ * Returns the view object.
+ * @return View the view object that is used to render various view files.
*/
- public function getViewRenderer()
+ public function getView()
{
- return $this->getComponent('viewRenderer');
+ return $this->getComponent('view');
}
/**
@@ -423,6 +414,9 @@ class Application extends Module
'urlManager' => array(
'class' => 'yii\web\UrlManager',
),
+ 'view' => array(
+ 'class' => 'yii\base\View',
+ ),
));
}
@@ -446,8 +440,8 @@ class Application extends Module
// in case error appeared in __toString method we can't throw any exception
$trace = debug_backtrace(false);
array_shift($trace);
- foreach($trace as $frame) {
- if($frame['function'] == '__toString') {
+ foreach ($trace as $frame) {
+ if ($frame['function'] == '__toString') {
$this->handleException($exception);
}
}
@@ -481,7 +475,7 @@ class Application extends Module
$this->end(1);
- } catch(\Exception $e) {
+ } catch (\Exception $e) {
// exception could be thrown in end() or ErrorHandler::handle()
$msg = (string)$e;
$msg .= "\nPrevious exception:\n";
diff --git a/framework/base/Controller.php b/framework/base/Controller.php
index 65200bb..8840cca 100644
--- a/framework/base/Controller.php
+++ b/framework/base/Controller.php
@@ -8,6 +8,7 @@
namespace yii\base;
use Yii;
+use yii\util\FileHelper;
use yii\util\StringHelper;
/**
@@ -295,34 +296,42 @@ class Controller extends Component
/**
* Renders a view and applies layout if available.
- *
- * @param $view
- * @param array $params
- * @return string
+ * @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
+ * @param array $params the parameters (name-value pairs) that should be made available in the view.
+ * These parameters will not be available in the layout.
+ * @return string the rendering result.
+ * @throws InvalidParamException if the view file or the layout file does not exist.
*/
public function render($view, $params = array())
{
- return $this->createView()->render($view, $params);
- }
-
- public function renderContent($content)
- {
- return $this->createView()->renderContent($content);
+ $viewFile = $this->findViewFile($view);
+ $layoutFile = $this->findLayoutFile();
+ return Yii::$app->getView()->render($this, $viewFile, $params, $layoutFile);
}
+ /**
+ * Renders a view.
+ * This method differs from [[render()]] in that it does not apply any layout.
+ * @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
+ * @param array $params the parameters (name-value pairs) that should be made available in the view.
+ * @return string the rendering result.
+ * @throws InvalidParamException if the view file does not exist.
+ */
public function renderPartial($view, $params = array())
{
- return $this->createView()->renderPartial($view, $params);
+ return $this->renderFile($this->findViewFile($view), $params);
}
+ /**
+ * Renders a view file.
+ * @param string $file the view file to be rendered. This can be either a file path or a path alias.
+ * @param array $params the parameters (name-value pairs) that should be made available in the view.
+ * @return string the rendering result.
+ * @throws InvalidParamException if the view file does not exist.
+ */
public function renderFile($file, $params = array())
{
- return $this->createView()->renderFile($file, $params);
- }
-
- public function createView()
- {
- return new View($this);
+ return Yii::$app->getView()->render($this, $file, $params);
}
/**
@@ -335,4 +344,105 @@ class Controller extends Component
{
return $this->module->getViewPath() . DIRECTORY_SEPARATOR . $this->id;
}
+
+ /**
+ * Finds the view file based on the given view name.
+ *
+ * A view name can be specified in one of the following formats:
+ *
+ * - path alias (e.g. "@app/views/site/index");
+ * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
+ * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
+ * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
+ * The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently
+ * active module.
+ * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
+ *
+ * If the view name does not contain a file extension, it will use the default one `.php`.
+ *
+ * @param string $view the view name or the path alias of the view file.
+ * @return string the view file path. Note that the file may not exist.
+ * @throws InvalidParamException if the view file is an invalid path alias
+ */
+ protected function findViewFile($view)
+ {
+ if (strncmp($view, '@', 1) === 0) {
+ // e.g. "@app/views/common"
+ $file = Yii::getAlias($view);
+ } elseif (strncmp($view, '/', 1) !== 0) {
+ // e.g. "index"
+ $file = $this->getViewPath() . DIRECTORY_SEPARATOR . $view;
+ } elseif (strncmp($view, '//', 2) !== 0) {
+ // e.g. "/site/index"
+ $file = $this->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
+ } else {
+ // e.g. "//layouts/main"
+ $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
+ }
+ if (FileHelper::getExtension($file) === '') {
+ $file .= '.php';
+ }
+ return $file;
+ }
+
+ /**
+ * Finds the applicable layout file.
+ *
+ * This method locates an applicable layout file via two steps.
+ *
+ * In the first step, it determines the layout name and the context module:
+ *
+ * - If [[layout]] is specified as a string, use it as the layout name and [[module]] as the context module;
+ * - If [[layout]] is null, search through all ancestor modules of this controller and find the first
+ * module whose [[Module::layout|layout]] is not null. The layout and the corresponding module
+ * are used as the layout name and the context module, respectively. If such a module is not found
+ * or the corresponding layout is not a string, it will return false, meaning no applicable layout.
+ *
+ * In the second step, it determines the actual layout file according to the previously found layout name
+ * and context module. The layout name can be
+ *
+ * - a path alias (e.g. "@app/views/layouts/main");
+ * - an absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
+ * looked for under the [[Application::layoutPath|layout path]] of the application;
+ * - a relative path (e.g. "main"): the actual layout layout file will be looked for under the
+ * [[Module::viewPath|view path]] of the context module.
+ *
+ * If the layout name does not contain a file extension, it will use the default one `.php`.
+ *
+ * @return string|boolean the layout file path, or false if layout is not needed.
+ * @throws InvalidParamException if an invalid path alias is used to specify the layout
+ */
+ protected function findLayoutFile()
+ {
+ /** @var $module Module */
+ if (is_string($this->layout)) {
+ $module = $this->module;
+ $view = $this->layout;
+ } elseif ($this->layout === null) {
+ $module = $this->module;
+ while ($module !== null && $module->layout === null) {
+ $module = $module->module;
+ }
+ if ($module !== null && is_string($module->layout)) {
+ $view = $module->layout;
+ }
+ }
+
+ if (!isset($view)) {
+ return false;
+ }
+
+ if (strncmp($view, '@', 1) === 0) {
+ $file = Yii::getAlias($view);
+ } elseif (strncmp($view, '/', 1) === 0) {
+ $file = Yii::$app->getLayoutPath() . DIRECTORY_SEPARATOR . $view;
+ } else {
+ $file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $view;
+ }
+
+ if (FileHelper::getExtension($file) === '') {
+ $file .= '.php';
+ }
+ return $file;
+ }
}
diff --git a/framework/base/ErrorHandler.php b/framework/base/ErrorHandler.php
index 40ae6d8..996fa18 100644
--- a/framework/base/ErrorHandler.php
+++ b/framework/base/ErrorHandler.php
@@ -253,14 +253,9 @@ class ErrorHandler extends Component
*/
public function renderAsHtml($exception)
{
- $view = new View($this);
- if (!YII_DEBUG || $exception instanceof UserException) {
- $viewName = $this->errorView;
- } else {
- $viewName = $this->exceptionView;
- }
+ $view = new View;
$name = !YII_DEBUG || $exception instanceof HttpException ? $this->errorView : $this->exceptionView;
- echo $view->render($name, array(
+ echo $view->render($this, $name, array(
'exception' => $exception,
));
}
diff --git a/framework/base/Theme.php b/framework/base/Theme.php
index 61487d8..c5fd925 100644
--- a/framework/base/Theme.php
+++ b/framework/base/Theme.php
@@ -40,7 +40,8 @@ class Theme extends Component
/**
* @var array the mapping between view directories and their corresponding themed versions.
* If not set, it will be initialized as a mapping from [[Application::basePath]] to [[basePath]].
- * This property is used by [[apply()]] when a view is trying to apply the theme.
+ * This property is used by [[applyTo()]] when a view is trying to apply the theme.
+ * Path aliases can be used when specifying directories.
*/
public $pathMap;
@@ -63,7 +64,9 @@ class Theme extends Component
}
$paths = array();
foreach ($this->pathMap as $from => $to) {
- $paths[FileHelper::normalizePath($from) . DIRECTORY_SEPARATOR] = FileHelper::normalizePath($to) . DIRECTORY_SEPARATOR;
+ $from = FileHelper::normalizePath(Yii::getAlias($from));
+ $to = FileHelper::normalizePath(Yii::getAlias($to));
+ $paths[$from . DIRECTORY_SEPARATOR] = $to . DIRECTORY_SEPARATOR;
}
$this->pathMap = $paths;
}
@@ -93,7 +96,7 @@ class Theme extends Component
* @param string $path the file to be themed
* @return string the themed file, or the original file if the themed version is not available.
*/
- public function apply($path)
+ public function applyTo($path)
{
$path = FileHelper::normalizePath($path);
foreach ($this->pathMap as $from => $to) {
diff --git a/framework/base/View.php b/framework/base/View.php
index 2b2e7d1..75a911d 100644
--- a/framework/base/View.php
+++ b/framework/base/View.php
@@ -8,14 +8,14 @@
namespace yii\base;
use Yii;
-use yii\util\FileHelper;
use yii\base\Application;
+use yii\util\FileHelper;
/**
* View represents a view object in the MVC pattern.
- *
+ *
* View provides a set of methods (e.g. [[render()]]) for rendering purpose.
- *
+ *
* @author Qiang Xue
* @since 2.0
*/
@@ -24,133 +24,112 @@ class View extends Component
/**
* @var object the object that owns this view. This can be a controller, a widget, or any other object.
*/
- public $owner;
- /**
- * @var string the layout to be applied when [[render()]] or [[renderContent()]] is called.
- * If not set, it will use the [[Module::layout]] of the currently active module.
- */
- public $layout;
- /**
- * @var string the language that the view should be rendered in. If not set, it will use
- * the value of [[Application::language]].
- */
- public $language;
+ public $context;
/**
- * @var string the language that the original view is in. If not set, it will use
- * the value of [[Application::sourceLanguage]].
+ * @var mixed custom parameters that are shared among view templates.
*/
- public $sourceLanguage;
- /**
- * @var boolean whether to localize the view when possible. Defaults to true.
- * Note that when this is true, if a localized view cannot be found, the original view will be rendered.
- * No error will be reported.
- */
- public $enableI18N = true;
+ public $params;
/**
- * @var boolean whether to theme the view when possible. Defaults to true.
- * Note that theming will be disabled if [[Application::theme]] is not set.
+ * @var ViewRenderer|array the view renderer object or the configuration array for
+ * creating the view renderer. If not set, view files will be treated as normal PHP files.
*/
- public $enableTheme = true;
+ public $renderer;
/**
- * @var mixed custom parameters that are available in the view template
+ * @var Theme|array the theme object or the configuration array for creating the theme.
+ * If not set, it means theming is not enabled.
*/
- public $params;
+ public $theme;
/**
* @var Widget[] the widgets that are currently not ended
*/
- private $_widgetStack = array();
-
- /**
- * Constructor.
- * @param object $owner the owner of this view. This usually is a controller or a widget.
- * @param array $config name-value pairs that will be used to initialize the object properties
- */
- public function __construct($owner, $config = array())
- {
- $this->owner = $owner;
- parent::__construct($config);
- }
+ private $_widgetStack = array();
- /**
- * Renders a view within a layout.
- * This method is similar to [[renderPartial()]] except that if a layout is available,
- * this method will embed the view result into the layout and then return it.
- * @param string $view the view to be rendered. Please refer to [[findViewFile()]] on possible formats of the view name.
- * @param array $params the parameters that should be made available in the view. The PHP function `extract()`
- * will be called on this variable to extract the variables from this parameter.
- * @return string the rendering result
- * @throws InvalidConfigException if the view file or layout file cannot be found
- * @see findViewFile()
- * @see findLayoutFile()
- */
- public function render($view, $params = array())
- {
- $content = $this->renderPartial($view, $params);
- return $this->renderContent($content);
- }
/**
- * Renders a text content within a layout.
- * The layout being used is resolved by [[findLayout()]].
- * If no layout is available, the content will be returned back.
- * @param string $content the content to be rendered
- * @return string the rendering result
- * @throws InvalidConfigException if the layout file cannot be found
- * @see findLayoutFile()
+ * Initializes the view component.
*/
- public function renderContent($content)
+ public function init()
{
- $layoutFile = $this->findLayoutFile();
- if ($layoutFile !== false) {
- return $this->renderFile($layoutFile, array('content' => $content));
- } else {
- return $content;
+ parent::init();
+ if (is_array($this->renderer)) {
+ $this->renderer = Yii::createObject($this->renderer);
+ }
+ if (is_array($this->theme)) {
+ $this->theme = Yii::createObject($this->theme);
}
}
/**
- * Renders a view.
+ * Renders a view file under a context with an optional layout.
+ *
+ * This method is similar to [[renderFile()]] except that it will update [[context]]
+ * with the provided $context parameter. It will also apply layout to the rendering result
+ * of the view file if $layoutFile is given.
*
- * The method first finds the actual view file corresponding to the specified view.
- * It then calls [[renderFile()]] to render the view file. The rendering result is returned
- * as a string. If the view file does not exist, an exception will be thrown.
+ * Theming and localization will be performed for the view file and the layout file, if possible.
*
- * @param string $view the view to be rendered. Please refer to [[findViewFile()]] on possible formats of the view name.
- * @param array $params the parameters that should be made available in the view. The PHP function `extract()`
- * will be called on this variable to extract the variables from this parameter.
+ * @param object $context the context object for rendering the file. This could be a controller, a widget,
+ * or any other object that serves as the rendering context of the view file. In the view file,
+ * it can be accessed through the [[context]] property.
+ * @param string $viewFile the view file. This can be a file path or a path alias.
+ * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
+ * @param string|boolean $layoutFile the layout file. This can be a file path or a path alias.
+ * If it is false, it means no layout should be applied.
* @return string the rendering result
- * @throws InvalidParamException if the view file cannot be found
- * @see findViewFile()
+ * @throws InvalidParamException if the view file or the layout file does not exist.
*/
- public function renderPartial($view, $params = array())
+ public function render($context, $viewFile, $params = array(), $layoutFile = false)
{
- $file = $this->findViewFile($view);
- if ($file !== false) {
- return $this->renderFile($file, $params);
- } else {
- throw new InvalidParamException("Unable to find the view file for view '$view'.");
+ $oldContext = $this->context;
+ $this->context = $context;
+
+ $content = $this->renderFile($viewFile, $params);
+
+ if ($layoutFile !== false) {
+ $content = $this->renderFile($layoutFile, array('content' => $content));
}
+
+ $this->context = $oldContext;
+
+ return $content;
}
/**
* Renders a view file.
*
- * If a [[ViewRenderer|view renderer]] is installed, this method will try to use the view renderer
- * to render the view file. Otherwise, it will simply include the view file, capture its output
- * and return it as a string.
+ * This method renders the specified view file under the existing [[context]].
+ *
+ * If [[theme]] is enabled (not null), it will try to render the themed version of the view file as long
+ * as it is available.
*
- * @param string $file the view file.
+ * The method will call [[FileHelper::localize()]] to localize the view file.
+ *
+ * If [[renderer]] is enabled (not null), the method will use it to render the view file.
+ * Otherwise, it will simply include the view file as a normal PHP file, capture its output and
+ * return it as a string.
+ *
+ * @param string $viewFile the view file. This can be a file path or a path alias.
* @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
* @return string the rendering result
+ * @throws InvalidParamException if the view file does not exist
*/
- public function renderFile($file, $params = array())
+ public function renderFile($viewFile, $params = array())
{
- $renderer = Yii::$app->getViewRenderer();
- if ($renderer !== null) {
- return $renderer->render($this, $file, $params);
+ $viewFile = Yii::getAlias($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");
+ }
+
+ if ($this->renderer !== null) {
+ return $this->renderer->render($this, $viewFile, $params);
} else {
- return $this->renderPhpFile($file, $params);
+ return $this->renderPhpFile($viewFile, $params);
}
}
@@ -161,6 +140,8 @@ class View extends Component
* It extracts the given parameters and makes them available in the view file.
* The method captures the output of the included view file and returns it as a string.
*
+ * This method should mainly be called by view renderer or [[renderFile()]].
+ *
* @param string $_file_ the view file.
* @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file.
* @return string the rendering result
@@ -184,7 +165,7 @@ class View extends Component
public function createWidget($class, $properties = array())
{
$properties['class'] = $class;
- return Yii::createObject($properties, $this->owner);
+ return Yii::createObject($properties, $this->context);
}
/**
@@ -233,7 +214,7 @@ class View extends Component
* If you want to capture the rendering result of a widget, you may use
* [[createWidget()]] and [[Widget::run()]].
* @return Widget the widget instance
- * @throws Exception if [[beginWidget()]] and [[endWidget()]] calls are not properly nested
+ * @throws InvalidCallException if [[beginWidget()]] and [[endWidget()]] calls are not properly nested
*/
public function endWidget()
{
@@ -242,251 +223,99 @@ class View extends Component
$widget->run();
return $widget;
} else {
- throw new Exception("Unmatched beginWidget() and endWidget() calls.");
- }
- }
-//
-// /**
-// * Begins recording a clip.
-// * This method is a shortcut to beginning [[yii\widgets\Clip]]
-// * @param string $id the clip ID.
-// * @param array $properties initial property values for [[yii\widgets\Clip]]
-// */
-// public function beginClip($id, $properties = array())
-// {
-// $properties['id'] = $id;
-// $this->beginWidget('yii\widgets\Clip', $properties);
-// }
-//
-// /**
-// * Ends recording a clip.
-// */
-// public function endClip()
-// {
-// $this->endWidget();
-// }
-//
-// /**
-// * Begins fragment caching.
-// * This method will display cached content if it is available.
-// * If not, it will start caching and would expect an [[endCache()]]
-// * call to end the cache and save the content into cache.
-// * A typical usage of fragment caching is as follows,
-// *
-// * ~~~
-// * if($this->beginCache($id)) {
-// * // ...generate content here
-// * $this->endCache();
-// * }
-// * ~~~
-// *
-// * @param string $id a unique ID identifying the fragment to be cached.
-// * @param array $properties initial property values for [[yii\widgets\OutputCache]]
-// * @return boolean whether we need to generate content for caching. False if cached version is available.
-// * @see endCache
-// */
-// public function beginCache($id, $properties = array())
-// {
-// $properties['id'] = $id;
-// $cache = $this->beginWidget('yii\widgets\OutputCache', $properties);
-// if ($cache->getIsContentCached()) {
-// $this->endCache();
-// return false;
-// } else {
-// return true;
-// }
-// }
-//
-// /**
-// * Ends fragment caching.
-// * This is an alias to [[endWidget()]]
-// * @see beginCache
-// */
-// public function endCache()
-// {
-// $this->endWidget();
-// }
-//
-// /**
-// * Begins the rendering of content that is to be decorated by the specified view.
-// * @param mixed $view the name of the view that will be used to decorate the content. The actual view script
-// * is resolved via {@link getViewFile}. If this parameter is null (default),
-// * the default layout will be used as the decorative view.
-// * Note that if the current controller does not belong to
-// * any module, the default layout refers to the application's {@link CWebApplication::layout default layout};
-// * If the controller belongs to a module, the default layout refers to the module's
-// * {@link CWebModule::layout default layout}.
-// * @param array $params the variables (name=>value) to be extracted and made available in the decorative view.
-// * @see endContent
-// * @see yii\widgets\ContentDecorator
-// */
-// public function beginContent($view, $params = array())
-// {
-// $this->beginWidget('yii\widgets\ContentDecorator', array(
-// 'view' => $view,
-// 'params' => $params,
-// ));
-// }
-//
-// /**
-// * Ends the rendering of content.
-// * @see beginContent
-// */
-// public function endContent()
-// {
-// $this->endWidget();
-// }
-
- /**
- * Finds the view file based on the given view name.
- *
- * A view name can be specified in one of the following formats:
- *
- * - path alias (e.g. "@app/views/site/index");
- * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
- * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
- * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
- * The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently
- * active module.
- * - relative path (e.g. "index"): the actual view file will be looked for under the [[owner]]'s view path.
- * If [[owner]] is a widget or a controller, its view path is given by their `viewPath` property.
- * If [[owner]] is an object of any other type, its view path is the `view` sub-directory of the directory
- * containing the owner class file.
- *
- * If the view name does not contain a file extension, it will default to `.php`.
- *
- * If [[enableTheme]] is true and there is an active application them, the method will also
- * attempt to use a themed version of the view file, when available.
- *
- * And if [[enableI18N]] is true, the method will attempt to use a translated version of the view file,
- * when available.
- *
- * @param string $view the view name or path alias. If the view name does not specify
- * the view file extension name, it will use `.php` as the extension name.
- * @return string the view file path if it exists. False if the view file cannot be found.
- * @throws InvalidConfigException if the view file does not exist
- */
- public function findViewFile($view)
- {
- if (FileHelper::getExtension($view) === '') {
- $view .= '.php';
- }
- if (strncmp($view, '@', 1) === 0) {
- // e.g. "@app/views/common"
- if (($file = Yii::getAlias($view)) === false) {
- throw new InvalidConfigException("Invalid path alias: $view");
- }
- } elseif (strncmp($view, '/', 1) !== 0) {
- // e.g. "index"
- if ($this->owner instanceof Controller || $this->owner instanceof Widget) {
- $file = $this->owner->getViewPath() . DIRECTORY_SEPARATOR . $view;
- } elseif ($this->owner !== null) {
- $class = new \ReflectionClass($this->owner);
- $file = dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . $view;
- } else {
- $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . $view;
- }
- } elseif (strncmp($view, '//', 2) !== 0 && Yii::$app->controller !== null) {
- // e.g. "/site/index"
- $file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
- } else {
- // e.g. "//layouts/main"
- $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
- }
-
- if (is_file($file)) {
- if ($this->enableTheme && ($theme = Yii::$app->getTheme()) !== null) {
- $file = $theme->apply($file);
- }
- return $this->enableI18N ? FileHelper::localize($file, $this->language, $this->sourceLanguage) : $file;
- } else {
- throw new InvalidConfigException("View file for view '$view' does not exist: $file");
+ throw new InvalidCallException("Unmatched beginWidget() and endWidget() calls.");
}
}
- /**
- * Finds the layout file that can be applied to the view.
- *
- * The applicable layout is resolved according to the following rules:
- *
- * - If [[layout]] is specified as a string, use it as the layout name and search for the layout file
- * under the layout path of the currently active module;
- * - If [[layout]] is null and [[owner]] is a controller:
- * * If the controller's [[Controller::layout|layout]] is a string, use it as the layout name
- * and search for the layout file under the layout path of the parent module of the controller;
- * * If the controller's [[Controller::layout|layout]] is null, look through its ancestor modules
- * and find the first one whose [[Module::layout|layout]] is not null. Use the layout specified
- * by that module;
- * - Returns false for all other cases.
- *
- * Like view names, a layout name can take several formats:
- *
- * - path alias (e.g. "@app/views/layouts/main");
- * - absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
- * looked for under the [[Application::layoutPath|layout path]] of the application;
- * - relative path (e.g. "main"): the actual layout layout file will be looked for under the
- * [[Module::viewPath|view path]] of the context module determined by the above layout resolution process.
- *
- * If the layout name does not contain a file extension, it will default to `.php`.
- *
- * If [[enableTheme]] is true and there is an active application them, the method will also
- * attempt to use a themed version of the layout file, when available.
- *
- * And if [[enableI18N]] is true, the method will attempt to use a translated version of the layout file,
- * when available.
- *
- * @return string|boolean the layout file path, or false if layout is not needed.
- * @throws InvalidConfigException if the layout file cannot be found
- */
- public function findLayoutFile()
- {
- /** @var $module Module */
- if (is_string($this->layout)) {
- if (Yii::$app->controller) {
- $module = Yii::$app->controller->module;
- } else {
- $module = Yii::$app;
- }
- $view = $this->layout;
- } elseif ($this->owner instanceof Controller) {
- if (is_string($this->owner->layout)) {
- $module = $this->owner->module;
- $view = $this->owner->layout;
- } elseif ($this->owner->layout === null) {
- $module = $this->owner->module;
- while ($module !== null && $module->layout === null) {
- $module = $module->module;
- }
- if ($module !== null && is_string($module->layout)) {
- $view = $module->layout;
- }
- }
- }
-
- if (!isset($view)) {
- return false;
- }
-
- if (FileHelper::getExtension($view) === '') {
- $view .= '.php';
- }
- if (strncmp($view, '@', 1) === 0) {
- if (($file = Yii::getAlias($view)) === false) {
- throw new InvalidConfigException("Invalid path alias: $view");
- }
- } elseif (strncmp($view, '/', 1) === 0) {
- $file = Yii::$app->getLayoutPath() . DIRECTORY_SEPARATOR . $view;
- } else {
- $file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $view;
- }
-
- if (is_file($file)) {
- if ($this->enableTheme && ($theme = Yii::$app->getTheme()) !== null) {
- $file = $theme->apply($file);
- }
- return $this->enableI18N ? FileHelper::localize($file, $this->language, $this->sourceLanguage) : $file;
- } else {
- throw new InvalidConfigException("Layout file for layout '$view' does not exist: $file");
- }
- }
+ //
+ // /**
+ // * Begins recording a clip.
+ // * This method is a shortcut to beginning [[yii\widgets\Clip]]
+ // * @param string $id the clip ID.
+ // * @param array $properties initial property values for [[yii\widgets\Clip]]
+ // */
+ // public function beginClip($id, $properties = array())
+ // {
+ // $properties['id'] = $id;
+ // $this->beginWidget('yii\widgets\Clip', $properties);
+ // }
+ //
+ // /**
+ // * Ends recording a clip.
+ // */
+ // public function endClip()
+ // {
+ // $this->endWidget();
+ // }
+ //
+ // /**
+ // * Begins fragment caching.
+ // * This method will display cached content if it is available.
+ // * If not, it will start caching and would expect an [[endCache()]]
+ // * call to end the cache and save the content into cache.
+ // * A typical usage of fragment caching is as follows,
+ // *
+ // * ~~~
+ // * if($this->beginCache($id)) {
+ // * // ...generate content here
+ // * $this->endCache();
+ // * }
+ // * ~~~
+ // *
+ // * @param string $id a unique ID identifying the fragment to be cached.
+ // * @param array $properties initial property values for [[yii\widgets\OutputCache]]
+ // * @return boolean whether we need to generate content for caching. False if cached version is available.
+ // * @see endCache
+ // */
+ // public function beginCache($id, $properties = array())
+ // {
+ // $properties['id'] = $id;
+ // $cache = $this->beginWidget('yii\widgets\OutputCache', $properties);
+ // if ($cache->getIsContentCached()) {
+ // $this->endCache();
+ // return false;
+ // } else {
+ // return true;
+ // }
+ // }
+ //
+ // /**
+ // * Ends fragment caching.
+ // * This is an alias to [[endWidget()]]
+ // * @see beginCache
+ // */
+ // public function endCache()
+ // {
+ // $this->endWidget();
+ // }
+ //
+ // /**
+ // * Begins the rendering of content that is to be decorated by the specified view.
+ // * @param mixed $view the name of the view that will be used to decorate the content. The actual view script
+ // * is resolved via {@link getViewFile}. If this parameter is null (default),
+ // * the default layout will be used as the decorative view.
+ // * Note that if the current controller does not belong to
+ // * any module, the default layout refers to the application's {@link CWebApplication::layout default layout};
+ // * If the controller belongs to a module, the default layout refers to the module's
+ // * {@link CWebModule::layout default layout}.
+ // * @param array $params the variables (name=>value) to be extracted and made available in the decorative view.
+ // * @see endContent
+ // * @see yii\widgets\ContentDecorator
+ // */
+ // public function beginContent($view, $params = array())
+ // {
+ // $this->beginWidget('yii\widgets\ContentDecorator', array(
+ // 'view' => $view,
+ // 'params' => $params,
+ // ));
+ // }
+ //
+ // /**
+ // * Ends the rendering of content.
+ // * @see beginContent
+ // */
+ // public function endContent()
+ // {
+ // $this->endWidget();
+ // }
}
\ No newline at end of file
diff --git a/framework/base/Widget.php b/framework/base/Widget.php
index 2f8264c..a9fe092 100644
--- a/framework/base/Widget.php
+++ b/framework/base/Widget.php
@@ -7,6 +7,9 @@
namespace yii\base;
+use Yii;
+use yii\util\FileHelper;
+
/**
* Widget is the base class for widgets.
*
@@ -70,35 +73,27 @@ class Widget extends Component
/**
* Renders a view.
- *
- * The method first finds the actual view file corresponding to the specified view.
- * It then calls [[renderFile()]] to render the view file. The rendering result is returned
- * as a string. If the view file does not exist, an exception will be thrown.
- *
- * To determine which view file should be rendered, the method calls [[findViewFile()]] which
- * will search in the directories as specified by [[basePath]].
- *
- * View name can be a path alias representing an absolute file path (e.g. `@app/views/layout/index`),
- * or a path relative to [[basePath]]. The file suffix is optional and defaults to `.php` if not given
- * in the view name.
- *
- * @param string $view the view to be rendered. This can be either a path alias or a path relative to [[basePath]].
- * @param array $params the parameters that should be made available in the view. The PHP function `extract()`
- * will be called on this variable to extract the variables from this parameter.
- * @return string the rendering result
- * @throws Exception if the view file cannot be found
+ * @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
+ * @param array $params the parameters (name-value pairs) that should be made available in the view.
+ * @return string the rendering result.
+ * @throws InvalidParamException if the view file does not exist.
*/
public function render($view, $params = array())
{
- return $this->createView()->renderPartial($view, $params);
+ $file = $this->findViewFile($view);
+ return Yii::$app->getView()->render($this, $file, $params);
}
/**
- * @return View
+ * Renders a view file.
+ * @param string $file the view file to be rendered. This can be either a file path or a path alias.
+ * @param array $params the parameters (name-value pairs) that should be made available in the view.
+ * @return string the rendering result.
+ * @throws InvalidParamException if the view file does not exist.
*/
- public function createView()
+ public function renderFile($file, $params = array())
{
- return new View($this);
+ return Yii::$app->getView()->render($this, $file, $params);
}
/**
@@ -112,4 +107,44 @@ class Widget extends Component
$class = new \ReflectionClass($className);
return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
}
+
+ /**
+ * Finds the view file based on the given view name.
+ *
+ * The view name can be specified in one of the following formats:
+ *
+ * - path alias (e.g. "@app/views/site/index");
+ * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
+ * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
+ * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
+ * The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently
+ * active module.
+ * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
+ *
+ * If the view name does not contain a file extension, it will use the default one `.php`.
+ *
+ * @param string $view the view name or the path alias of the view file.
+ * @return string the view file path. Note that the file may not exist.
+ * @throws InvalidParamException if the view file is an invalid path alias
+ */
+ public function findViewFile($view)
+ {
+ if (strncmp($view, '@', 1) === 0) {
+ // e.g. "@app/views/common"
+ $file = Yii::getAlias($view);
+ } elseif (strncmp($view, '/', 1) !== 0) {
+ // e.g. "index"
+ $file = $this->getViewPath() . DIRECTORY_SEPARATOR . $view;
+ } elseif (strncmp($view, '//', 2) !== 0 && Yii::$app->controller !== null) {
+ // e.g. "/site/index"
+ $file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
+ } else {
+ // e.g. "//layouts/main"
+ $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
+ }
+ if (FileHelper::getExtension($file) === '') {
+ $file .= '.php';
+ }
+ return $file;
+ }
}
\ No newline at end of file
diff --git a/framework/caching/FileCache.php b/framework/caching/FileCache.php
index 2c7bf0a..e565cad 100644
--- a/framework/caching/FileCache.php
+++ b/framework/caching/FileCache.php
@@ -52,9 +52,6 @@ class FileCache extends Cache
{
parent::init();
$this->cachePath = \Yii::getAlias($this->cachePath);
- if ($this->cachePath === false) {
- throw new InvalidConfigException('FileCache.cachePath must be a valid path alias.');
- }
if (!is_dir($this->cachePath)) {
mkdir($this->cachePath, 0777, true);
}
diff --git a/framework/console/controllers/MigrateController.php b/framework/console/controllers/MigrateController.php
index aec198c..06e100b 100644
--- a/framework/console/controllers/MigrateController.php
+++ b/framework/console/controllers/MigrateController.php
@@ -114,7 +114,7 @@ class MigrateController extends Controller
{
if (parent::beforeAction($action)) {
$path = Yii::getAlias($this->migrationPath);
- if ($path === false || !is_dir($path)) {
+ if (!is_dir($path)) {
throw new Exception("The migration directory \"{$this->migrationPath}\" does not exist.");
}
$this->migrationPath = $path;
diff --git a/framework/util/FileHelper.php b/framework/util/FileHelper.php
index e9af8c6..9108476 100644
--- a/framework/util/FileHelper.php
+++ b/framework/util/FileHelper.php
@@ -43,7 +43,7 @@ class FileHelper
public static function ensureDirectory($path)
{
$p = \Yii::getAlias($path);
- if ($p !== false && ($p = realpath($p)) !== false && is_dir($p)) {
+ if (($p = realpath($p)) !== false && is_dir($p)) {
return $p;
} else {
throw new InvalidConfigException('Directory does not exist: ' . $path);
diff --git a/framework/views/error.php b/framework/views/error.php
index 28c50f1..893640a 100644
--- a/framework/views/error.php
+++ b/framework/views/error.php
@@ -1,10 +1,10 @@
owner;
-$title = $owner->htmlEncode($exception instanceof \yii\base\Exception || $exception instanceof \yii\base\ErrorException ? $exception->getName() : get_class($exception));
+$context = $this->context;
+$title = $context->htmlEncode($exception instanceof \yii\base\Exception || $exception instanceof \yii\base\ErrorException ? $exception->getName() : get_class($exception));
?>
@@ -52,7 +52,7 @@ $title = $owner->htmlEncode($exception instanceof \yii\base\Exception || $except
-
htmlEncode($exception->getMessage()))?>
+
htmlEncode($exception->getMessage()))?>
The above error occurred while the Web server was processing your request.
diff --git a/framework/web/Session.php b/framework/web/Session.php
index 14bea3f..5697679 100644
--- a/framework/web/Session.php
+++ b/framework/web/Session.php
@@ -214,7 +214,7 @@ class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Co
public function setSavePath($value)
{
$path = Yii::getAlias($value);
- if ($path !== false && is_dir($path)) {
+ if (is_dir($path)) {
session_save_path($path);
} else {
throw new InvalidParamException("Session save path is not a valid directory: $value");
From 2ecf1d92842913246c9b02c3350e6ca6c642004d Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Fri, 22 Mar 2013 08:50:57 -0400
Subject: [PATCH 73/84] view sip
---
framework/base/Controller.php | 4 +--
framework/base/View.php | 59 +++++++++++++++++++++++++++++++++----------
2 files changed, 47 insertions(+), 16 deletions(-)
diff --git a/framework/base/Controller.php b/framework/base/Controller.php
index 8840cca..3069ce3 100644
--- a/framework/base/Controller.php
+++ b/framework/base/Controller.php
@@ -414,12 +414,10 @@ class Controller extends Component
*/
protected function findLayoutFile()
{
- /** @var $module Module */
+ $module = $this->module;
if (is_string($this->layout)) {
- $module = $this->module;
$view = $this->layout;
} elseif ($this->layout === null) {
- $module = $this->module;
while ($module !== null && $module->layout === null) {
$module = $module->module;
}
diff --git a/framework/base/View.php b/framework/base/View.php
index 75a911d..9911cd7 100644
--- a/framework/base/View.php
+++ b/framework/base/View.php
@@ -79,20 +79,10 @@ class View extends Component
* @return string the rendering result
* @throws InvalidParamException if the view file or the layout file does not exist.
*/
- public function render($context, $viewFile, $params = array(), $layoutFile = false)
+ public function render($view, $params = array())
{
- $oldContext = $this->context;
- $this->context = $context;
-
- $content = $this->renderFile($viewFile, $params);
-
- if ($layoutFile !== false) {
- $content = $this->renderFile($layoutFile, array('content' => $content));
- }
-
- $this->context = $oldContext;
-
- return $content;
+ $viewFile = $this->findViewFile($this->context, $view);
+ return $this->renderFile($viewFile, $params);
}
/**
@@ -156,6 +146,49 @@ class View extends Component
}
/**
+ * Finds the view file based on the given view name.
+ *
+ * A view name can be specified in one of the following formats:
+ *
+ * - path alias (e.g. "@app/views/site/index");
+ * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
+ * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
+ * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
+ * The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently
+ * active module.
+ * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
+ *
+ * If the view name does not contain a file extension, it will use the default one `.php`.
+ *
+ * @param string $view the view name or the path alias of the view file.
+ * @return string the view file path. Note that the file may not exist.
+ * @throws InvalidParamException if the view file is an invalid path alias
+ */
+ protected function findViewFile($context, $view)
+ {
+ if (FileHelper::getExtension($view) === '') {
+ $view .= '.php';
+ }
+
+ if (strncmp($view, '//', 2) === 0) {
+ // e.g. "//layouts/main"
+ $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
+ } elseif (strncmp($view, '/', 1) === 0) {
+ // e.g. "/site/index"
+ $file = Yii::$app->controller->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
+ } elseif (strncmp($view, '@', 1) !== 0) {
+ // e.g. "index" or "view/item"
+ if (method_exists($context, 'getViewPath')) {
+ $file = $context->getViewPath() . DIRECTORY_SEPARATOR . $view;
+ } else {
+ $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . $view;
+ }
+ }
+
+ return $file;
+ }
+
+ /**
* Creates a widget.
* This method will use [[Yii::createObject()]] to create the widget.
* @param string $class the widget class name or path alias
From a5ee5b842b29da5afebb6b6e5d2f1cd43161d9bc Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Fri, 22 Mar 2013 17:22:29 -0400
Subject: [PATCH 74/84] view WIP
---
framework/base/Controller.php | 52 ++-------
framework/base/View.php | 188 ++++++++++++++++++---------------
framework/base/Widget.php | 45 +-------
framework/widgets/Clip.php | 57 ++++++++++
framework/widgets/ContentDecorator.php | 81 ++++++++++++++
5 files changed, 249 insertions(+), 174 deletions(-)
create mode 100644 framework/widgets/Clip.php
create mode 100644 framework/widgets/ContentDecorator.php
diff --git a/framework/base/Controller.php b/framework/base/Controller.php
index 3069ce3..9219904 100644
--- a/framework/base/Controller.php
+++ b/framework/base/Controller.php
@@ -304,9 +304,13 @@ class Controller extends Component
*/
public function render($view, $params = array())
{
- $viewFile = $this->findViewFile($view);
+ $output = Yii::$app->getView()->render($view, $params, $this);
$layoutFile = $this->findLayoutFile();
- return Yii::$app->getView()->render($this, $viewFile, $params, $layoutFile);
+ if ($layoutFile !== false) {
+ return Yii::$app->getView()->renderFile($layoutFile, array('content' => $output), $this);
+ } else {
+ return $output;
+ }
}
/**
@@ -319,7 +323,7 @@ class Controller extends Component
*/
public function renderPartial($view, $params = array())
{
- return $this->renderFile($this->findViewFile($view), $params);
+ return Yii::$app->getView()->render($view, $params, $this);
}
/**
@@ -331,7 +335,7 @@ class Controller extends Component
*/
public function renderFile($file, $params = array())
{
- return Yii::$app->getView()->render($this, $file, $params);
+ return Yii::$app->getView()->renderFile($file, $params, $this);
}
/**
@@ -346,46 +350,6 @@ class Controller extends Component
}
/**
- * Finds the view file based on the given view name.
- *
- * A view name can be specified in one of the following formats:
- *
- * - path alias (e.g. "@app/views/site/index");
- * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
- * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
- * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
- * The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently
- * active module.
- * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
- *
- * If the view name does not contain a file extension, it will use the default one `.php`.
- *
- * @param string $view the view name or the path alias of the view file.
- * @return string the view file path. Note that the file may not exist.
- * @throws InvalidParamException if the view file is an invalid path alias
- */
- protected function findViewFile($view)
- {
- if (strncmp($view, '@', 1) === 0) {
- // e.g. "@app/views/common"
- $file = Yii::getAlias($view);
- } elseif (strncmp($view, '/', 1) !== 0) {
- // e.g. "index"
- $file = $this->getViewPath() . DIRECTORY_SEPARATOR . $view;
- } elseif (strncmp($view, '//', 2) !== 0) {
- // e.g. "/site/index"
- $file = $this->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
- } else {
- // e.g. "//layouts/main"
- $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
- }
- if (FileHelper::getExtension($file) === '') {
- $file .= '.php';
- }
- return $file;
- }
-
- /**
* Finds the applicable layout file.
*
* This method locates an applicable layout file via two steps.
diff --git a/framework/base/View.php b/framework/base/View.php
index 9911cd7..709b761 100644
--- a/framework/base/View.php
+++ b/framework/base/View.php
@@ -39,6 +39,12 @@ class View extends Component
* If not set, it means theming is not enabled.
*/
public $theme;
+ /**
+ * @var array a list of named output clips. You can call [[beginClip()]] and [[endClip()]]
+ * to capture small fragments of a view. They can be later accessed at somewhere else
+ * through this property.
+ */
+ public $clips;
/**
* @var Widget[] the widgets that are currently not ended
@@ -61,35 +67,29 @@ class View extends Component
}
/**
- * Renders a view file under a context with an optional layout.
+ * Renders a view.
*
- * This method is similar to [[renderFile()]] except that it will update [[context]]
- * with the provided $context parameter. It will also apply layout to the rendering result
- * of the view file if $layoutFile is given.
+ * This method will call [[findViewFile()]] to convert the view name into the corresponding view
+ * file path, and it will then call [[renderFile()]] to render the view.
*
- * Theming and localization will be performed for the view file and the layout file, if possible.
- *
- * @param object $context the context object for rendering the file. This could be a controller, a widget,
- * or any other object that serves as the rendering context of the view file. In the view file,
- * it can be accessed through the [[context]] property.
- * @param string $viewFile the view file. This can be a file path or a path alias.
+ * @param string $view the view name. Please refer to [[findViewFile()]] on how to specify this parameter.
* @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
- * @param string|boolean $layoutFile the layout file. This can be a file path or a path alias.
- * If it is false, it means no layout should be applied.
+ * @param object $context the context that the view should use for rendering the view. If null,
+ * existing [[context]] will be used.
* @return string the rendering result
- * @throws InvalidParamException if the view file or the layout file does not exist.
+ * @throws InvalidParamException if the view cannot be resolved or the view file does not exist.
+ * @see renderFile
+ * @see findViewFile
*/
- public function render($view, $params = array())
+ public function render($view, $params = array(), $context = null)
{
- $viewFile = $this->findViewFile($this->context, $view);
- return $this->renderFile($viewFile, $params);
+ $viewFile = $this->findViewFile($context, $view);
+ return $this->renderFile($viewFile, $params, $context);
}
/**
* Renders a view file.
*
- * This method renders the specified view file under the existing [[context]].
- *
* If [[theme]] is enabled (not null), it will try to render the themed version of the view file as long
* as it is available.
*
@@ -99,12 +99,14 @@ class View extends Component
* Otherwise, it will simply include the view file as a normal PHP file, capture its output and
* return it as a string.
*
- * @param string $viewFile the view file. This can be a file path or a path alias.
+ * @param string $viewFile the view file. This can be either a file path or a path alias.
* @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
+ * @param object $context the context that the view should use for rendering the view. If null,
+ * existing [[context]] will be used.
* @return string the rendering result
* @throws InvalidParamException if the view file does not exist
*/
- public function renderFile($viewFile, $params = array())
+ public function renderFile($viewFile, $params = array(), $context = null)
{
$viewFile = Yii::getAlias($viewFile);
if (is_file($viewFile)) {
@@ -116,11 +118,18 @@ class View extends Component
throw new InvalidParamException("The view file does not exist: $viewFile");
}
+ $oldContext = $this->context;
+ $this->context = $context;
+
if ($this->renderer !== null) {
- return $this->renderer->render($this, $viewFile, $params);
+ $output = $this->renderer->render($this, $viewFile, $params);
} else {
- return $this->renderPhpFile($viewFile, $params);
+ $output = $this->renderPhpFile($viewFile, $params);
}
+
+ $this->context = $oldContext;
+
+ return $output;
}
/**
@@ -156,36 +165,36 @@ class View extends Component
* - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
* The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently
* active module.
- * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
+ * - relative path (e.g. "index"): the actual view file will be looked for under [[Controller::viewPath|viewPath]]
+ * of the context object, assuming the context is either a [[Controller]] or a [[Widget]].
*
* If the view name does not contain a file extension, it will use the default one `.php`.
*
+ * @param object $context the view context object
* @param string $view the view name or the path alias of the view file.
* @return string the view file path. Note that the file may not exist.
- * @throws InvalidParamException if the view file is an invalid path alias
+ * @throws InvalidParamException if the view file is an invalid path alias or the context cannot be
+ * used to determine the actual view file corresponding to the specified view.
*/
protected function findViewFile($context, $view)
{
- if (FileHelper::getExtension($view) === '') {
- $view .= '.php';
- }
-
- if (strncmp($view, '//', 2) === 0) {
+ if (strncmp($view, '@', 1) === 0) {
+ // e.g. "@app/views/main"
+ $file = Yii::getAlias($view);
+ } elseif (strncmp($view, '//', 2) === 0) {
// e.g. "//layouts/main"
$file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
} elseif (strncmp($view, '/', 1) === 0) {
// e.g. "/site/index"
- $file = Yii::$app->controller->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
- } elseif (strncmp($view, '@', 1) !== 0) {
- // e.g. "index" or "view/item"
- if (method_exists($context, 'getViewPath')) {
- $file = $context->getViewPath() . DIRECTORY_SEPARATOR . $view;
- } else {
- $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . $view;
- }
+ $file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
+ } elseif ($context instanceof Controller || $context instanceof Widget) {
+ /** @var $context Controller|Widget */
+ $file = $context->getViewPath() . DIRECTORY_SEPARATOR . $view;
+ } else {
+ throw new InvalidParamException("Unable to resolve the view file for '$view'.");
}
- return $file;
+ return FileHelper::getExtension($file) === '' ? $file . '.php' : $file;
}
/**
@@ -260,26 +269,31 @@ class View extends Component
}
}
- //
- // /**
- // * Begins recording a clip.
- // * This method is a shortcut to beginning [[yii\widgets\Clip]]
- // * @param string $id the clip ID.
- // * @param array $properties initial property values for [[yii\widgets\Clip]]
- // */
- // public function beginClip($id, $properties = array())
- // {
- // $properties['id'] = $id;
- // $this->beginWidget('yii\widgets\Clip', $properties);
- // }
- //
- // /**
- // * Ends recording a clip.
- // */
- // public function endClip()
- // {
- // $this->endWidget();
- // }
+ /**
+ * Begins recording a clip.
+ * This method is a shortcut to beginning [[yii\widgets\Clip]]
+ * @param string $id the clip ID.
+ * @param boolean $renderInPlace whether to render the clip content in place.
+ * Defaults to false, meaning the captured clip will not be displayed.
+ * @return \yii\widgets\Clip the Clip widget instance
+ */
+ public function beginClip($id, $renderInPlace = false)
+ {
+ return $this->beginWidget('yii\widgets\Clip', array(
+ 'id' => $id,
+ 'renderInPlace' => $renderInPlace,
+ 'view' => $this,
+ ));
+ }
+
+ /**
+ * Ends recording a clip.
+ */
+ public function endClip()
+ {
+ $this->endWidget();
+ }
+
//
// /**
// * Begins fragment caching.
@@ -322,33 +336,33 @@ class View extends Component
// $this->endWidget();
// }
//
- // /**
- // * Begins the rendering of content that is to be decorated by the specified view.
- // * @param mixed $view the name of the view that will be used to decorate the content. The actual view script
- // * is resolved via {@link getViewFile}. If this parameter is null (default),
- // * the default layout will be used as the decorative view.
- // * Note that if the current controller does not belong to
- // * any module, the default layout refers to the application's {@link CWebApplication::layout default layout};
- // * If the controller belongs to a module, the default layout refers to the module's
- // * {@link CWebModule::layout default layout}.
- // * @param array $params the variables (name=>value) to be extracted and made available in the decorative view.
- // * @see endContent
- // * @see yii\widgets\ContentDecorator
- // */
- // public function beginContent($view, $params = array())
- // {
- // $this->beginWidget('yii\widgets\ContentDecorator', array(
- // 'view' => $view,
- // 'params' => $params,
- // ));
- // }
- //
- // /**
- // * Ends the rendering of content.
- // * @see beginContent
- // */
- // public function endContent()
- // {
- // $this->endWidget();
- // }
+ /**
+ * Begins the rendering of content that is to be decorated by the specified view.
+ * @param mixed $view the name of the view that will be used to decorate the content. The actual view script
+ * is resolved via {@link getViewFile}. If this parameter is null (default),
+ * the default layout will be used as the decorative view.
+ * Note that if the current controller does not belong to
+ * any module, the default layout refers to the application's {@link CWebApplication::layout default layout};
+ * If the controller belongs to a module, the default layout refers to the module's
+ * {@link CWebModule::layout default layout}.
+ * @param array $params the variables (name=>value) to be extracted and made available in the decorative view.
+ * @see endContent
+ * @see yii\widgets\ContentDecorator
+ */
+ public function beginContent($view, $params = array())
+ {
+ $this->beginWidget('yii\widgets\ContentDecorator', array(
+ 'view' => $view,
+ 'params' => $params,
+ ));
+ }
+
+ /**
+ * Ends the rendering of content.
+ * @see beginContent
+ */
+ public function endContent()
+ {
+ $this->endWidget();
+ }
}
\ No newline at end of file
diff --git a/framework/base/Widget.php b/framework/base/Widget.php
index a9fe092..6b92f09 100644
--- a/framework/base/Widget.php
+++ b/framework/base/Widget.php
@@ -80,8 +80,7 @@ class Widget extends Component
*/
public function render($view, $params = array())
{
- $file = $this->findViewFile($view);
- return Yii::$app->getView()->render($this, $file, $params);
+ return Yii::$app->getView()->render($view, $params, $this);
}
/**
@@ -93,7 +92,7 @@ class Widget extends Component
*/
public function renderFile($file, $params = array())
{
- return Yii::$app->getView()->render($this, $file, $params);
+ return Yii::$app->getView()->renderFile($file, $params, $this);
}
/**
@@ -107,44 +106,4 @@ class Widget extends Component
$class = new \ReflectionClass($className);
return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
}
-
- /**
- * Finds the view file based on the given view name.
- *
- * The view name can be specified in one of the following formats:
- *
- * - path alias (e.g. "@app/views/site/index");
- * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
- * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
- * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
- * The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently
- * active module.
- * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
- *
- * If the view name does not contain a file extension, it will use the default one `.php`.
- *
- * @param string $view the view name or the path alias of the view file.
- * @return string the view file path. Note that the file may not exist.
- * @throws InvalidParamException if the view file is an invalid path alias
- */
- public function findViewFile($view)
- {
- if (strncmp($view, '@', 1) === 0) {
- // e.g. "@app/views/common"
- $file = Yii::getAlias($view);
- } elseif (strncmp($view, '/', 1) !== 0) {
- // e.g. "index"
- $file = $this->getViewPath() . DIRECTORY_SEPARATOR . $view;
- } elseif (strncmp($view, '//', 2) !== 0 && Yii::$app->controller !== null) {
- // e.g. "/site/index"
- $file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
- } else {
- // e.g. "//layouts/main"
- $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
- }
- if (FileHelper::getExtension($file) === '') {
- $file .= '.php';
- }
- return $file;
- }
}
\ No newline at end of file
diff --git a/framework/widgets/Clip.php b/framework/widgets/Clip.php
new file mode 100644
index 0000000..d540b24
--- /dev/null
+++ b/framework/widgets/Clip.php
@@ -0,0 +1,57 @@
+
+ * @since 2.0
+ */
+class Clip extends Widget
+{
+ /**
+ * @var string the ID of this clip.
+ */
+ public $id;
+ /**
+ * @var View the view object for keeping the clip. If not set, the view registered with the application
+ * will be used.
+ */
+ public $view;
+ /**
+ * @var boolean whether to render the clip content in place. Defaults to false,
+ * meaning the captured clip will not be displayed.
+ */
+ public $renderInPlace = false;
+
+ /**
+ * Starts recording a clip.
+ */
+ public function init()
+ {
+ ob_start();
+ ob_implicit_flush(false);
+ }
+
+ /**
+ * Ends recording a clip.
+ * This method stops output buffering and saves the rendering result as a named clip in the controller.
+ */
+ public function run()
+ {
+ $clip = ob_get_clean();
+ if ($this->renderClip) {
+ echo $clip;
+ }
+ $view = $this->view !== null ? $this->view : Yii::$app->getView();
+ $view->clips[$this->id] = $clip;
+ }
+}
\ No newline at end of file
diff --git a/framework/widgets/ContentDecorator.php b/framework/widgets/ContentDecorator.php
new file mode 100644
index 0000000..0087698
--- /dev/null
+++ b/framework/widgets/ContentDecorator.php
@@ -0,0 +1,81 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright 2008-2013 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+/**
+ * CContentDecorator decorates the content it encloses with the specified view.
+ *
+ * CContentDecorator is mostly used to implement nested layouts, i.e., a layout
+ * is embedded within another layout. {@link CBaseController} defines a pair of
+ * convenient methods to use CContentDecorator:
+ *
+ * $this->beginContent('path/to/view');
+ * // ... content to be decorated
+ * $this->endContent();
+ *
+ *
+ * The property {@link view} specifies the name of the view that is used to
+ * decorate the content. In the view, the content being decorated may be
+ * accessed with variable $content.
+ *
+ * @author Qiang Xue
+ * @package system.web.widgets
+ * @since 1.0
+ */
+class CContentDecorator extends COutputProcessor
+{
+ /**
+ * @var mixed the name of the view that will be used to decorate the captured content.
+ * If this property is null (default value), the default layout will be used as
+ * the decorative view. Note that if the current controller does not belong to
+ * any module, the default layout refers to the application's {@link CWebApplication::layout default layout};
+ * If the controller belongs to a module, the default layout refers to the module's
+ * {@link CWebModule::layout default layout}.
+ */
+ public $view;
+ /**
+ * @var array the variables (name=>value) to be extracted and made available in the decorative view.
+ */
+ public $data=array();
+
+ /**
+ * Processes the captured output.
+ * This method decorates the output with the specified {@link view}.
+ * @param string $output the captured output to be processed
+ */
+ public function processOutput($output)
+ {
+ $output=$this->decorate($output);
+ parent::processOutput($output);
+ }
+
+ /**
+ * Decorates the content by rendering a view and embedding the content in it.
+ * The content being embedded can be accessed in the view using variable $content
+ * The decorated content will be displayed directly.
+ * @param string $content the content to be decorated
+ * @return string the decorated content
+ */
+ protected function decorate($content)
+ {
+ $owner=$this->getOwner();
+ if($this->view===null)
+ $viewFile=Yii::app()->getController()->getLayoutFile(null);
+ else
+ $viewFile=$owner->getViewFile($this->view);
+ if($viewFile!==false)
+ {
+ $data=$this->data;
+ $data['content']=$content;
+ return $owner->renderFile($viewFile,$data,true);
+ }
+ else
+ return $content;
+ }
+}
From 5a6c5ccb4ad042ddf48fd4a1880a87832632f86a Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Sat, 23 Mar 2013 15:28:18 -0400
Subject: [PATCH 75/84] Finished ContentDecorator.
---
framework/base/View.php | 59 +++++++++++------------
framework/widgets/ContentDecorator.php | 88 +++++++++++++---------------------
2 files changed, 62 insertions(+), 85 deletions(-)
diff --git a/framework/base/View.php b/framework/base/View.php
index 709b761..86020ad 100644
--- a/framework/base/View.php
+++ b/framework/base/View.php
@@ -119,7 +119,9 @@ class View extends Component
}
$oldContext = $this->context;
- $this->context = $context;
+ if ($context !== null) {
+ $this->context = $context;
+ }
if ($this->renderer !== null) {
$output = $this->renderer->render($this, $viewFile, $params);
@@ -276,6 +278,7 @@ class View extends Component
* @param boolean $renderInPlace whether to render the clip content in place.
* Defaults to false, meaning the captured clip will not be displayed.
* @return \yii\widgets\Clip the Clip widget instance
+ * @see \yii\widgets\Clip
*/
public function beginClip($id, $renderInPlace = false)
{
@@ -294,6 +297,31 @@ class View extends Component
$this->endWidget();
}
+ /**
+ * Begins the rendering of content that is to be decorated by the specified view.
+ * @param string $view the name of the view that will be used to decorate the content enclosed by this widget.
+ * Please refer to [[View::findViewFile()]] on how to set this property.
+ * @param array $params the variables (name=>value) to be extracted and made available in the decorative view.
+ * @return \yii\widgets\ContentDecorator the ContentDecorator widget instance
+ * @see \yii\widgets\ContentDecorator
+ */
+ public function beginContent($view, $params = array())
+ {
+ return $this->beginWidget('yii\widgets\ContentDecorator', array(
+ 'view' => $this,
+ 'viewName' => $view,
+ 'params' => $params,
+ ));
+ }
+
+ /**
+ * Ends the rendering of content.
+ */
+ public function endContent()
+ {
+ $this->endWidget();
+ }
+
//
// /**
// * Begins fragment caching.
@@ -336,33 +364,4 @@ class View extends Component
// $this->endWidget();
// }
//
- /**
- * Begins the rendering of content that is to be decorated by the specified view.
- * @param mixed $view the name of the view that will be used to decorate the content. The actual view script
- * is resolved via {@link getViewFile}. If this parameter is null (default),
- * the default layout will be used as the decorative view.
- * Note that if the current controller does not belong to
- * any module, the default layout refers to the application's {@link CWebApplication::layout default layout};
- * If the controller belongs to a module, the default layout refers to the module's
- * {@link CWebModule::layout default layout}.
- * @param array $params the variables (name=>value) to be extracted and made available in the decorative view.
- * @see endContent
- * @see yii\widgets\ContentDecorator
- */
- public function beginContent($view, $params = array())
- {
- $this->beginWidget('yii\widgets\ContentDecorator', array(
- 'view' => $view,
- 'params' => $params,
- ));
- }
-
- /**
- * Ends the rendering of content.
- * @see beginContent
- */
- public function endContent()
- {
- $this->endWidget();
- }
}
\ No newline at end of file
diff --git a/framework/widgets/ContentDecorator.php b/framework/widgets/ContentDecorator.php
index 0087698..4c3ae70 100644
--- a/framework/widgets/ContentDecorator.php
+++ b/framework/widgets/ContentDecorator.php
@@ -1,81 +1,59 @@
* @link http://www.yiiframework.com/
- * @copyright 2008-2013 Yii Software LLC
+ * @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
+namespace yii\widgets;
+
+use Yii;
+use yii\base\InvalidConfigException;
+use yii\base\Widget;
+use yii\base\View;
+
/**
- * CContentDecorator decorates the content it encloses with the specified view.
- *
- * CContentDecorator is mostly used to implement nested layouts, i.e., a layout
- * is embedded within another layout. {@link CBaseController} defines a pair of
- * convenient methods to use CContentDecorator:
- *
- * $this->beginContent('path/to/view');
- * // ... content to be decorated
- * $this->endContent();
- *
- *
- * The property {@link view} specifies the name of the view that is used to
- * decorate the content. In the view, the content being decorated may be
- * accessed with variable $content.
- *
* @author Qiang Xue
- * @package system.web.widgets
- * @since 1.0
+ * @since 2.0
*/
-class CContentDecorator extends COutputProcessor
+class ContentDecorator extends Widget
{
/**
- * @var mixed the name of the view that will be used to decorate the captured content.
- * If this property is null (default value), the default layout will be used as
- * the decorative view. Note that if the current controller does not belong to
- * any module, the default layout refers to the application's {@link CWebApplication::layout default layout};
- * If the controller belongs to a module, the default layout refers to the module's
- * {@link CWebModule::layout default layout}.
+ * @var View the view object for rendering [[viewName]]. If not set, the view registered with the application
+ * will be used.
*/
public $view;
/**
- * @var array the variables (name=>value) to be extracted and made available in the decorative view.
+ * @var string the name of the view that will be used to decorate the content enclosed by this widget.
+ * Please refer to [[View::findViewFile()]] on how to set this property.
*/
- public $data=array();
+ public $viewName;
+ /**
+ * @var array the parameters (name=>value) to be extracted and made available in the decorative view.
+ */
+ public $params = array();
/**
- * Processes the captured output.
- * This method decorates the output with the specified {@link view}.
- * @param string $output the captured output to be processed
+ * Starts recording a clip.
*/
- public function processOutput($output)
+ public function init()
{
- $output=$this->decorate($output);
- parent::processOutput($output);
+ if ($this->viewName === null) {
+ throw new InvalidConfigException('ContentDecorator::viewName must be set.');
+ }
+ ob_start();
+ ob_implicit_flush(false);
}
/**
- * Decorates the content by rendering a view and embedding the content in it.
- * The content being embedded can be accessed in the view using variable $content
- * The decorated content will be displayed directly.
- * @param string $content the content to be decorated
- * @return string the decorated content
+ * Ends recording a clip.
+ * This method stops output buffering and saves the rendering result as a named clip in the controller.
*/
- protected function decorate($content)
+ public function run()
{
- $owner=$this->getOwner();
- if($this->view===null)
- $viewFile=Yii::app()->getController()->getLayoutFile(null);
- else
- $viewFile=$owner->getViewFile($this->view);
- if($viewFile!==false)
- {
- $data=$this->data;
- $data['content']=$content;
- return $owner->renderFile($viewFile,$data,true);
- }
- else
- return $content;
+ $params = $this->params;
+ $params['content'] = ob_get_clean();
+ $view = $this->view !== null ? $this->view : Yii::$app->getView();
+ echo $view->render($this->viewName, $params);
}
}
From 339ac9b49f7d8f1ee00aa49e7d4babe3b2184ebf Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Sat, 23 Mar 2013 15:30:54 -0400
Subject: [PATCH 76/84] renamed util to helpers.
---
framework/helpers/ArrayHelper.php | 340 ++++++++++++
framework/helpers/ConsoleColor.php | 478 +++++++++++++++++
framework/helpers/FileHelper.php | 274 ++++++++++
framework/helpers/Html.php | 976 +++++++++++++++++++++++++++++++++++
framework/helpers/SecurityHelper.php | 272 ++++++++++
framework/helpers/StringHelper.php | 125 +++++
framework/helpers/VarDumper.php | 134 +++++
framework/helpers/mimeTypes.php | 187 +++++++
framework/util/ArrayHelper.php | 340 ------------
framework/util/ConsoleColor.php | 478 -----------------
framework/util/FileHelper.php | 274 ----------
framework/util/Html.php | 976 -----------------------------------
framework/util/SecurityHelper.php | 272 ----------
framework/util/StringHelper.php | 125 -----
framework/util/VarDumper.php | 134 -----
framework/util/mimeTypes.php | 187 -------
16 files changed, 2786 insertions(+), 2786 deletions(-)
create mode 100644 framework/helpers/ArrayHelper.php
create mode 100644 framework/helpers/ConsoleColor.php
create mode 100644 framework/helpers/FileHelper.php
create mode 100644 framework/helpers/Html.php
create mode 100644 framework/helpers/SecurityHelper.php
create mode 100644 framework/helpers/StringHelper.php
create mode 100644 framework/helpers/VarDumper.php
create mode 100644 framework/helpers/mimeTypes.php
delete mode 100644 framework/util/ArrayHelper.php
delete mode 100644 framework/util/ConsoleColor.php
delete mode 100644 framework/util/FileHelper.php
delete mode 100644 framework/util/Html.php
delete mode 100644 framework/util/SecurityHelper.php
delete mode 100644 framework/util/StringHelper.php
delete mode 100644 framework/util/VarDumper.php
delete mode 100644 framework/util/mimeTypes.php
diff --git a/framework/helpers/ArrayHelper.php b/framework/helpers/ArrayHelper.php
new file mode 100644
index 0000000..447d034
--- /dev/null
+++ b/framework/helpers/ArrayHelper.php
@@ -0,0 +1,340 @@
+
+ * @since 2.0
+ */
+class ArrayHelper
+{
+ /**
+ * Merges two or more arrays into one recursively.
+ * If each array has an element with the same string key value, the latter
+ * will overwrite the former (different from array_merge_recursive).
+ * Recursive merging will be conducted if both arrays have an element of array
+ * type and are having the same key.
+ * For integer-keyed elements, the elements from the latter array will
+ * be appended to the former array.
+ * @param array $a array to be merged to
+ * @param array $b array to be merged from. You can specify additional
+ * arrays via third argument, fourth argument etc.
+ * @return array the merged array (the original arrays are not changed.)
+ */
+ public static function merge($a, $b)
+ {
+ $args = func_get_args();
+ $res = array_shift($args);
+ while ($args !== array()) {
+ $next = array_shift($args);
+ foreach ($next as $k => $v) {
+ if (is_integer($k)) {
+ isset($res[$k]) ? $res[] = $v : $res[$k] = $v;
+ } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) {
+ $res[$k] = self::merge($res[$k], $v);
+ } else {
+ $res[$k] = $v;
+ }
+ }
+ }
+ return $res;
+ }
+
+ /**
+ * Retrieves the value of an array element or object property with the given key or property name.
+ * If the key does not exist in the array, the default value will be returned instead.
+ *
+ * Below are some usage examples,
+ *
+ * ~~~
+ * // working with array
+ * $username = \yii\util\ArrayHelper::getValue($_POST, 'username');
+ * // working with object
+ * $username = \yii\util\ArrayHelper::getValue($user, 'username');
+ * // working with anonymous function
+ * $fullName = \yii\util\ArrayHelper::getValue($user, function($user, $defaultValue) {
+ * return $user->firstName . ' ' . $user->lastName;
+ * });
+ * ~~~
+ *
+ * @param array|object $array array or object to extract value from
+ * @param string|\Closure $key key name of the array element, or property name of the object,
+ * or an anonymous function returning the value. The anonymous function signature should be:
+ * `function($array, $defaultValue)`.
+ * @param mixed $default the default value to be returned if the specified key does not exist
+ * @return mixed the value of the
+ */
+ public static function getValue($array, $key, $default = null)
+ {
+ if ($key instanceof \Closure) {
+ return $key($array, $default);
+ } elseif (is_array($array)) {
+ return isset($array[$key]) || array_key_exists($key, $array) ? $array[$key] : $default;
+ } else {
+ return $array->$key;
+ }
+ }
+
+ /**
+ * Indexes an array according to a specified key.
+ * The input array should be multidimensional or an array of objects.
+ *
+ * The key can be a key name of the sub-array, a property name of object, or an anonymous
+ * function which returns the key value given an array element.
+ *
+ * If a key value is null, the corresponding array element will be discarded and not put in the result.
+ *
+ * For example,
+ *
+ * ~~~
+ * $array = array(
+ * array('id' => '123', 'data' => 'abc'),
+ * array('id' => '345', 'data' => 'def'),
+ * );
+ * $result = ArrayHelper::index($array, 'id');
+ * // the result is:
+ * // array(
+ * // '123' => array('id' => '123', 'data' => 'abc'),
+ * // '345' => array('id' => '345', 'data' => 'def'),
+ * // )
+ *
+ * // using anonymous function
+ * $result = ArrayHelper::index($array, function(element) {
+ * return $element['id'];
+ * });
+ * ~~~
+ *
+ * @param array $array the array that needs to be indexed
+ * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array
+ * @return array the indexed array
+ */
+ public static function index($array, $key)
+ {
+ $result = array();
+ foreach ($array as $element) {
+ $value = static::getValue($element, $key);
+ $result[$value] = $element;
+ }
+ return $result;
+ }
+
+ /**
+ * Returns the values of a specified column in an array.
+ * The input array should be multidimensional or an array of objects.
+ *
+ * For example,
+ *
+ * ~~~
+ * $array = array(
+ * array('id' => '123', 'data' => 'abc'),
+ * array('id' => '345', 'data' => 'def'),
+ * );
+ * $result = ArrayHelper::getColumn($array, 'id');
+ * // the result is: array( '123', '345')
+ *
+ * // using anonymous function
+ * $result = ArrayHelper::getColumn($array, function(element) {
+ * return $element['id'];
+ * });
+ * ~~~
+ *
+ * @param array $array
+ * @param string|\Closure $name
+ * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array
+ * will be re-indexed with integers.
+ * @return array the list of column values
+ */
+ public static function getColumn($array, $name, $keepKeys = true)
+ {
+ $result = array();
+ if ($keepKeys) {
+ foreach ($array as $k => $element) {
+ $result[$k] = static::getValue($element, $name);
+ }
+ } else {
+ foreach ($array as $element) {
+ $result[] = static::getValue($element, $name);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Builds a map (key-value pairs) from a multidimensional array or an array of objects.
+ * The `$from` and `$to` parameters specify the key names or property names to set up the map.
+ * Optionally, one can further group the map according to a grouping field `$group`.
+ *
+ * For example,
+ *
+ * ~~~
+ * $array = array(
+ * array('id' => '123', 'name' => 'aaa', 'class' => 'x'),
+ * array('id' => '124', 'name' => 'bbb', 'class' => 'x'),
+ * array('id' => '345', 'name' => 'ccc', 'class' => 'y'),
+ * );
+ *
+ * $result = ArrayHelper::map($array, 'id', 'name');
+ * // the result is:
+ * // array(
+ * // '123' => 'aaa',
+ * // '124' => 'bbb',
+ * // '345' => 'ccc',
+ * // )
+ *
+ * $result = ArrayHelper::map($array, 'id', 'name', 'class');
+ * // the result is:
+ * // array(
+ * // 'x' => array(
+ * // '123' => 'aaa',
+ * // '124' => 'bbb',
+ * // ),
+ * // 'y' => array(
+ * // '345' => 'ccc',
+ * // ),
+ * // )
+ * ~~~
+ *
+ * @param array $array
+ * @param string|\Closure $from
+ * @param string|\Closure $to
+ * @param string|\Closure $group
+ * @return array
+ */
+ public static function map($array, $from, $to, $group = null)
+ {
+ $result = array();
+ foreach ($array as $element) {
+ $key = static::getValue($element, $from);
+ $value = static::getValue($element, $to);
+ if ($group !== null) {
+ $result[static::getValue($element, $group)][$key] = $value;
+ } else {
+ $result[$key] = $value;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Sorts an array of objects or arrays (with the same structure) by one or several keys.
+ * @param array $array the array to be sorted. The array will be modified after calling this method.
+ * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array
+ * elements, a property name of the objects, or an anonymous function returning the values for comparison
+ * purpose. The anonymous function signature should be: `function($item)`.
+ * To sort by multiple keys, provide an array of keys here.
+ * @param boolean|array $ascending whether to sort in ascending or descending order. When
+ * sorting by multiple keys with different ascending orders, use an array of ascending flags.
+ * @param integer|array $sortFlag the PHP sort flag. Valid values include:
+ * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING`, and `SORT_STRING | SORT_FLAG_CASE`. The last
+ * value is for sorting strings in case-insensitive manner. Please refer to
+ * See [PHP manual](http://php.net/manual/en/function.sort.php) for more details.
+ * When sorting by multiple keys with different sort flags, use an array of sort flags.
+ * @throws InvalidParamException if the $ascending or $sortFlag parameters do not have
+ * correct number of elements as that of $key.
+ */
+ public static function multisort(&$array, $key, $ascending = true, $sortFlag = SORT_REGULAR)
+ {
+ $keys = is_array($key) ? $key : array($key);
+ if (empty($keys) || empty($array)) {
+ return;
+ }
+ $n = count($keys);
+ if (is_scalar($ascending)) {
+ $ascending = array_fill(0, $n, $ascending);
+ } elseif (count($ascending) !== $n) {
+ throw new InvalidParamException('The length of $ascending parameter must be the same as that of $keys.');
+ }
+ if (is_scalar($sortFlag)) {
+ $sortFlag = array_fill(0, $n, $sortFlag);
+ } elseif (count($sortFlag) !== $n) {
+ throw new InvalidParamException('The length of $ascending parameter must be the same as that of $keys.');
+ }
+ $args = array();
+ foreach ($keys as $i => $key) {
+ $flag = $sortFlag[$i];
+ if ($flag == (SORT_STRING | SORT_FLAG_CASE)) {
+ $flag = SORT_STRING;
+ $column = array();
+ foreach (static::getColumn($array, $key) as $k => $value) {
+ $column[$k] = strtolower($value);
+ }
+ $args[] = $column;
+ } else {
+ $args[] = static::getColumn($array, $key);
+ }
+ $args[] = $ascending[$i] ? SORT_ASC : SORT_DESC;
+ $args[] = $flag;
+ }
+ $args[] = &$array;
+ call_user_func_array('array_multisort', $args);
+ }
+
+ /**
+ * Encodes special characters in an array of strings into HTML entities.
+ * Both the array keys and values will be encoded.
+ * If a value is an array, this method will also encode it recursively.
+ * @param array $data data to be encoded
+ * @param boolean $valuesOnly whether to encode array values only. If false,
+ * both the array keys and array values will be encoded.
+ * @param string $charset the charset that the data is using. If not set,
+ * [[\yii\base\Application::charset]] will be used.
+ * @return array the encoded data
+ * @see http://www.php.net/manual/en/function.htmlspecialchars.php
+ */
+ public static function htmlEncode($data, $valuesOnly = true, $charset = null)
+ {
+ if ($charset === null) {
+ $charset = Yii::$app->charset;
+ }
+ $d = array();
+ foreach ($data as $key => $value) {
+ if (!$valuesOnly && is_string($key)) {
+ $key = htmlspecialchars($key, ENT_QUOTES, $charset);
+ }
+ if (is_string($value)) {
+ $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset);
+ } elseif (is_array($value)) {
+ $d[$key] = static::htmlEncode($value, $charset);
+ }
+ }
+ return $d;
+ }
+
+ /**
+ * Decodes HTML entities into the corresponding characters in an array of strings.
+ * Both the array keys and values will be decoded.
+ * If a value is an array, this method will also decode it recursively.
+ * @param array $data data to be decoded
+ * @param boolean $valuesOnly whether to decode array values only. If false,
+ * both the array keys and array values will be decoded.
+ * @return array the decoded data
+ * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php
+ */
+ public static function htmlDecode($data, $valuesOnly = true)
+ {
+ $d = array();
+ foreach ($data as $key => $value) {
+ if (!$valuesOnly && is_string($key)) {
+ $key = htmlspecialchars_decode($key, ENT_QUOTES);
+ }
+ if (is_string($value)) {
+ $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES);
+ } elseif (is_array($value)) {
+ $d[$key] = static::htmlDecode($value);
+ }
+ }
+ return $d;
+ }
+}
\ No newline at end of file
diff --git a/framework/helpers/ConsoleColor.php b/framework/helpers/ConsoleColor.php
new file mode 100644
index 0000000..74aa154
--- /dev/null
+++ b/framework/helpers/ConsoleColor.php
@@ -0,0 +1,478 @@
+
+ * @since 2.0
+ */
+class ConsoleColor
+{
+ const FG_BLACK = 30;
+ const FG_RED = 31;
+ const FG_GREEN = 32;
+ const FG_YELLOW = 33;
+ const FG_BLUE = 34;
+ const FG_PURPLE = 35;
+ const FG_CYAN = 36;
+ const FG_GREY = 37;
+
+ const BG_BLACK = 40;
+ const BG_RED = 41;
+ const BG_GREEN = 42;
+ const BG_YELLOW = 43;
+ const BG_BLUE = 44;
+ const BG_PURPLE = 45;
+ const BG_CYAN = 46;
+ const BG_GREY = 47;
+
+ const BOLD = 1;
+ const ITALIC = 3;
+ const UNDERLINE = 4;
+ const BLINK = 5;
+ const NEGATIVE = 7;
+ const CONCEALED = 8;
+ const CROSSED_OUT = 9;
+ const FRAMED = 51;
+ const ENCIRCLED = 52;
+ const OVERLINED = 53;
+
+ /**
+ * Moves the terminal cursor up by sending ANSI control code CUU to the terminal.
+ * If the cursor is already at the edge of the screen, this has no effect.
+ * @param integer $rows number of rows the cursor should be moved up
+ */
+ public static function moveCursorUp($rows=1)
+ {
+ echo "\033[" . (int) $rows . 'A';
+ }
+
+ /**
+ * Moves the terminal cursor down by sending ANSI control code CUD to the terminal.
+ * If the cursor is already at the edge of the screen, this has no effect.
+ * @param integer $rows number of rows the cursor should be moved down
+ */
+ public static function moveCursorDown($rows=1)
+ {
+ echo "\033[" . (int) $rows . 'B';
+ }
+
+ /**
+ * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal.
+ * If the cursor is already at the edge of the screen, this has no effect.
+ * @param integer $steps number of steps the cursor should be moved forward
+ */
+ public static function moveCursorForward($steps=1)
+ {
+ echo "\033[" . (int) $steps . 'C';
+ }
+
+ /**
+ * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal.
+ * If the cursor is already at the edge of the screen, this has no effect.
+ * @param integer $steps number of steps the cursor should be moved backward
+ */
+ public static function moveCursorBackward($steps=1)
+ {
+ echo "\033[" . (int) $steps . 'D';
+ }
+
+ /**
+ * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal.
+ * @param integer $lines number of lines the cursor should be moved down
+ */
+ public static function moveCursorNextLine($lines=1)
+ {
+ echo "\033[" . (int) $lines . 'E';
+ }
+
+ /**
+ * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal.
+ * @param integer $lines number of lines the cursor should be moved up
+ */
+ public static function moveCursorPrevLine($lines=1)
+ {
+ echo "\033[" . (int) $lines . 'F';
+ }
+
+ /**
+ * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal.
+ * @param integer $column 1-based column number, 1 is the left edge of the screen.
+ * @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line.
+ */
+ public static function moveCursorTo($column, $row=null)
+ {
+ if ($row === null) {
+ echo "\033[" . (int) $column . 'G';
+ } else {
+ echo "\033[" . (int) $row . ';' . (int) $column . 'H';
+ }
+ }
+
+ /**
+ * Scrolls whole page up by sending ANSI control code SU to the terminal.
+ * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows.
+ * @param int $lines number of lines to scroll up
+ */
+ public static function scrollUp($lines=1)
+ {
+ echo "\033[".(int)$lines."S";
+ }
+
+ /**
+ * Scrolls whole page down by sending ANSI control code SD to the terminal.
+ * New lines are added at the top. This is not supported by ANSI.SYS used in windows.
+ * @param int $lines number of lines to scroll down
+ */
+ public static function scrollDown($lines=1)
+ {
+ echo "\033[".(int)$lines."T";
+ }
+
+ /**
+ * Saves the current cursor position by sending ANSI control code SCP to the terminal.
+ * Position can then be restored with {@link restoreCursorPosition}.
+ */
+ public static function saveCursorPosition()
+ {
+ echo "\033[s";
+ }
+
+ /**
+ * Restores the cursor position saved with {@link saveCursorPosition} by sending ANSI control code RCP to the terminal.
+ */
+ public static function restoreCursorPosition()
+ {
+ echo "\033[u";
+ }
+
+ /**
+ * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal.
+ * Use {@link showCursor} to bring it back.
+ * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit.
+ */
+ public static function hideCursor()
+ {
+ echo "\033[?25l";
+ }
+
+ /**
+ * Will show a cursor again when it has been hidden by {@link hideCursor} by sending ANSI DECTCEM code ?25h to the terminal.
+ */
+ public static function showCursor()
+ {
+ echo "\033[?25h";
+ }
+
+ /**
+ * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal.
+ * Cursor position will not be changed.
+ * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen.
+ */
+ public static function clearScreen()
+ {
+ echo "\033[2J";
+ }
+
+ /**
+ * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal.
+ * Cursor position will not be changed.
+ */
+ public static function clearScreenBeforeCursor()
+ {
+ echo "\033[1J";
+ }
+
+ /**
+ * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal.
+ * Cursor position will not be changed.
+ */
+ public static function clearScreenAfterCursor()
+ {
+ echo "\033[0J";
+ }
+
+ /**
+ * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal.
+ * Cursor position will not be changed.
+ */
+ public static function clearLine()
+ {
+ echo "\033[2K";
+ }
+
+ /**
+ * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal.
+ * Cursor position will not be changed.
+ */
+ public static function clearLineBeforeCursor()
+ {
+ echo "\033[1K";
+ }
+
+ /**
+ * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal.
+ * Cursor position will not be changed.
+ */
+ public static function clearLineAfterCursor()
+ {
+ echo "\033[0K";
+ }
+
+ /**
+ * Will send ANSI format for following output
+ *
+ * You can pass any of the FG_*, BG_* and TEXT_* constants and also xterm256ColorBg
+ * TODO: documentation
+ */
+ public static function ansiStyle()
+ {
+ echo "\033[" . implode(';', func_get_args()) . 'm';
+ }
+
+ /**
+ * Will return a string formatted with the given ANSI style
+ *
+ * See {@link ansiStyle} for possible arguments.
+ * @param string $string the string to be formatted
+ * @return string
+ */
+ public static function ansiStyleString($string)
+ {
+ $args = func_get_args();
+ array_shift($args);
+ $code = implode(';', $args);
+ return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string."\033[0m";
+ }
+
+ //const COLOR_XTERM256 = 38;// http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors
+ public static function xterm256ColorFg($i) // TODO naming!
+ {
+ return '38;5;'.$i;
+ }
+
+ public static function xterm256ColorBg($i) // TODO naming!
+ {
+ return '48;5;'.$i;
+ }
+
+ /**
+ * Usage: list($w, $h) = ConsoleHelper::getScreenSize();
+ *
+ * @return array
+ */
+ public static function getScreenSize()
+ {
+ // TODO implement
+ return array(150,50);
+ }
+
+ /**
+ * resets any ansi style set by previous method {@link ansiStyle}
+ * Any output after this is will have default text style.
+ */
+ public static function reset()
+ {
+ echo "\033[0m";
+ }
+
+ /**
+ * Strips ANSI control codes from a string
+ *
+ * @param string $string String to strip
+ * @return string
+ */
+ public static function strip($string)
+ {
+ return preg_replace('/\033\[[\d;]+m/', '', $string); // TODO currently only strips color
+ }
+
+ // TODO refactor and review
+ public static function ansiToHtml($string)
+ {
+ $tags = 0;
+ return preg_replace_callback('/\033\[[\d;]+m/', function($ansi) use (&$tags) {
+ $styleA = array();
+ foreach(explode(';', $ansi) as $controlCode)
+ {
+ switch($controlCode)
+ {
+ case static::FG_BLACK: $style = array('color' => '#000000'); break;
+ case static::FG_BLUE: $style = array('color' => '#000078'); break;
+ case static::FG_CYAN: $style = array('color' => '#007878'); break;
+ case static::FG_GREEN: $style = array('color' => '#007800'); break;
+ case static::FG_GREY: $style = array('color' => '#787878'); break;
+ case static::FG_PURPLE: $style = array('color' => '#780078'); break;
+ case static::FG_RED: $style = array('color' => '#780000'); break;
+ case static::FG_YELLOW: $style = array('color' => '#787800'); break;
+ case static::BG_BLACK: $style = array('background-color' => '#000000'); break;
+ case static::BG_BLUE: $style = array('background-color' => '#000078'); break;
+ case static::BG_CYAN: $style = array('background-color' => '#007878'); break;
+ case static::BG_GREEN: $style = array('background-color' => '#007800'); break;
+ case static::BG_GREY: $style = array('background-color' => '#787878'); break;
+ case static::BG_PURPLE: $style = array('background-color' => '#780078'); break;
+ case static::BG_RED: $style = array('background-color' => '#780000'); break;
+ case static::BG_YELLOW: $style = array('background-color' => '#787800'); break;
+ case static::BOLD: $style = array('font-weight' => 'bold'); break;
+ case static::ITALIC: $style = array('font-style' => 'italic'); break;
+ case static::UNDERLINE: $style = array('text-decoration' => array('underline')); break;
+ case static::OVERLINED: $style = array('text-decoration' => array('overline')); break;
+ case static::CROSSED_OUT:$style = array('text-decoration' => array('line-through')); break;
+ case static::BLINK: $style = array('text-decoration' => array('blink')); break;
+ case static::NEGATIVE: // ???
+ case static::CONCEALED:
+ case static::ENCIRCLED:
+ case static::FRAMED:
+ // TODO allow resetting codes
+ break;
+ case 0: // ansi reset
+ $return = '';
+ for($n=$tags; $tags>0; $tags--) {
+ $return .= '';
+ }
+ return $return;
+ }
+
+ $styleA = ArrayHelper::merge($styleA, $style);
+ }
+ $styleString[] = array();
+ foreach($styleA as $name => $content) {
+ if ($name === 'text-decoration') {
+ $content = implode(' ', $content);
+ }
+ $styleString[] = $name.':'.$content;
+ }
+ $tags++;
+ return '';
+ }, $string);
+ }
+
+ /**
+ * TODO syntax copied from https://github.com/pear/Console_Color2/blob/master/Console/Color2.php
+ *
+ * Converts colorcodes in the format %y (for yellow) into ansi-control
+ * codes. The conversion table is: ('bold' meaning 'light' on some
+ * terminals). It's almost the same conversion table irssi uses.
+ *
+ * text text background
+ * ------------------------------------------------
+ * %k %K %0 black dark grey black
+ * %r %R %1 red bold red red
+ * %g %G %2 green bold green green
+ * %y %Y %3 yellow bold yellow yellow
+ * %b %B %4 blue bold blue blue
+ * %m %M %5 magenta bold magenta magenta
+ * %p %P magenta (think: purple)
+ * %c %C %6 cyan bold cyan cyan
+ * %w %W %7 white bold white white
+ *
+ * %F Blinking, Flashing
+ * %U Underline
+ * %8 Reverse
+ * %_,%9 Bold
+ *
+ * %n Resets the color
+ * %% A single %
+ *
+ * First param is the string to convert, second is an optional flag if
+ * colors should be used. It defaults to true, if set to false, the
+ * colorcodes will just be removed (And %% will be transformed into %)
+ *
+ * @param string $string String to convert
+ * @param bool $colored Should the string be colored?
+ *
+ * @return string
+ */
+ public static function renderColoredString($string)
+ {
+ $colored = true;
+
+
+ static $conversions = array ( // static so the array doesn't get built
+ // everytime
+ // %y - yellow, and so on... {{{
+ '%y' => array('color' => 'yellow'),
+ '%g' => array('color' => 'green' ),
+ '%b' => array('color' => 'blue' ),
+ '%r' => array('color' => 'red' ),
+ '%p' => array('color' => 'purple'),
+ '%m' => array('color' => 'purple'),
+ '%c' => array('color' => 'cyan' ),
+ '%w' => array('color' => 'grey' ),
+ '%k' => array('color' => 'black' ),
+ '%n' => array('color' => 'reset' ),
+ '%Y' => array('color' => 'yellow', 'style' => 'light'),
+ '%G' => array('color' => 'green', 'style' => 'light'),
+ '%B' => array('color' => 'blue', 'style' => 'light'),
+ '%R' => array('color' => 'red', 'style' => 'light'),
+ '%P' => array('color' => 'purple', 'style' => 'light'),
+ '%M' => array('color' => 'purple', 'style' => 'light'),
+ '%C' => array('color' => 'cyan', 'style' => 'light'),
+ '%W' => array('color' => 'grey', 'style' => 'light'),
+ '%K' => array('color' => 'black', 'style' => 'light'),
+ '%N' => array('color' => 'reset', 'style' => 'light'),
+ '%3' => array('background' => 'yellow'),
+ '%2' => array('background' => 'green' ),
+ '%4' => array('background' => 'blue' ),
+ '%1' => array('background' => 'red' ),
+ '%5' => array('background' => 'purple'),
+ '%6' => array('background' => 'cyan' ),
+ '%7' => array('background' => 'grey' ),
+ '%0' => array('background' => 'black' ),
+ // Don't use this, I can't stand flashing text
+ '%F' => array('style' => 'blink'),
+ '%U' => array('style' => 'underline'),
+ '%8' => array('style' => 'inverse'),
+ '%9' => array('style' => 'bold'),
+ '%_' => array('style' => 'bold')
+ // }}}
+ );
+
+ if ($colored) {
+ $string = str_replace('%%', '% ', $string);
+ foreach ($conversions as $key => $value) {
+ $string = str_replace($key, Console_Color::color($value),
+ $string);
+ }
+ $string = str_replace('% ', '%', $string);
+
+ } else {
+ $string = preg_replace('/%((%)|.)/', '$2', $string);
+ }
+
+ return $string;
+ }
+
+ /**
+ * Escapes % so they don't get interpreted as color codes
+ *
+ * @param string $string String to escape
+ *
+ * @access public
+ * @return string
+ */
+ public static function escape($string)
+ {
+ return str_replace('%', '%%', $string);
+ }
+}
diff --git a/framework/helpers/FileHelper.php b/framework/helpers/FileHelper.php
new file mode 100644
index 0000000..9108476
--- /dev/null
+++ b/framework/helpers/FileHelper.php
@@ -0,0 +1,274 @@
+
+ * @author Alex Makarov
+ * @since 2.0
+ */
+class FileHelper
+{
+ /**
+ * Returns the extension name of a file path.
+ * For example, the path "path/to/something.php" would return "php".
+ * @param string $path the file path
+ * @return string the extension name without the dot character.
+ */
+ public static function getExtension($path)
+ {
+ return pathinfo($path, PATHINFO_EXTENSION);
+ }
+
+ /**
+ * Checks the given path and ensures it is a directory.
+ * This method will call `realpath()` to "normalize" the given path.
+ * If the given path does not refer to an existing directory, an exception will be thrown.
+ * @param string $path the given path. This can also be a path alias.
+ * @return string the normalized path
+ * @throws InvalidConfigException if the path does not refer to an existing directory.
+ */
+ public static function ensureDirectory($path)
+ {
+ $p = \Yii::getAlias($path);
+ if (($p = realpath($p)) !== false && is_dir($p)) {
+ return $p;
+ } else {
+ throw new InvalidConfigException('Directory does not exist: ' . $path);
+ }
+ }
+
+ /**
+ * Normalizes a file/directory path.
+ * After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`,
+ * and any trailing directory separators will be removed. For example, '/home\demo/' on Linux
+ * will be normalized as '/home/demo'.
+ * @param string $path the file/directory path to be normalized
+ * @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`.
+ * @return string the normalized file/directory path
+ */
+ public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR)
+ {
+ return rtrim(strtr($path, array('/' => $ds, '\\' => $ds)), $ds);
+ }
+
+ /**
+ * Returns the localized version of a specified file.
+ *
+ * The searching is based on the specified language code. In particular,
+ * a file with the same name will be looked for under the subdirectory
+ * whose name is same as the language code. For example, given the file "path/to/view.php"
+ * and language code "zh_cn", the localized file will be looked for as
+ * "path/to/zh_cn/view.php". If the file is not found, the original file
+ * will be returned.
+ *
+ * If the target and the source language codes are the same,
+ * the original file will be returned.
+ *
+ * For consistency, it is recommended that the language code is given
+ * in lower case and in the format of LanguageID_RegionID (e.g. "en_us").
+ *
+ * @param string $file the original file
+ * @param string $language the target language that the file should be localized to.
+ * If not set, the value of [[\yii\base\Application::language]] will be used.
+ * @param string $sourceLanguage the language that the original file is in.
+ * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used.
+ * @return string the matching localized file, or the original file if the localized version is not found.
+ * If the target and the source language codes are the same, the original file will be returned.
+ */
+ public static function localize($file, $language = null, $sourceLanguage = null)
+ {
+ if ($language === null) {
+ $language = \Yii::$app->language;
+ }
+ if ($sourceLanguage === null) {
+ $sourceLanguage = \Yii::$app->sourceLanguage;
+ }
+ if ($language === $sourceLanguage) {
+ return $file;
+ }
+ $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $sourceLanguage . DIRECTORY_SEPARATOR . basename($file);
+ return is_file($desiredFile) ? $desiredFile : $file;
+ }
+
+ /**
+ * Determines the MIME type of the specified file.
+ * This method will first try to determine the MIME type based on
+ * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will
+ * fall back to [[getMimeTypeByExtension()]].
+ * @param string $file the file name.
+ * @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`.
+ * This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php).
+ * @param boolean $checkExtension whether to use the file extension to determine the MIME type in case
+ * `finfo_open()` cannot determine it.
+ * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined.
+ */
+ public static function getMimeType($file, $magicFile = null, $checkExtension = true)
+ {
+ if (function_exists('finfo_open')) {
+ $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile);
+ if ($info && ($result = finfo_file($info, $file)) !== false) {
+ return $result;
+ }
+ }
+
+ return $checkExtension ? self::getMimeTypeByExtension($file) : null;
+ }
+
+ /**
+ * Determines the MIME type based on the extension name of the specified file.
+ * This method will use a local map between extension names and MIME types.
+ * @param string $file the file name.
+ * @param string $magicFile the path of the file that contains all available MIME type information.
+ * If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used.
+ * @return string the MIME type. Null is returned if the MIME type cannot be determined.
+ */
+ public static function getMimeTypeByExtension($file, $magicFile = null)
+ {
+ if ($magicFile === null) {
+ $magicFile = \Yii::getAlias('@yii/util/mimeTypes.php');
+ }
+ $mimeTypes = require($magicFile);
+ if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') {
+ $ext = strtolower($ext);
+ if (isset($mimeTypes[$ext])) {
+ return $mimeTypes[$ext];
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Copies a list of files from one place to another.
+ * @param array $fileList the list of files to be copied (name=>spec).
+ * The array keys are names displayed during the copy process, and array values are specifications
+ * for files to be copied. Each array value must be an array of the following structure:
+ *
+ *
source: required, the full path of the file/directory to be copied from
+ *
target: required, the full path of the file/directory to be copied to
+ *
callback: optional, the callback to be invoked when copying a file. The callback function
+ * should be declared as follows:
+ *
+ * function foo($source,$params)
+ *
+ * where $source parameter is the source file path, and the content returned
+ * by the function will be saved into the target file.
+ *
params: optional, the parameters to be passed to the callback
- * text text background
- * ------------------------------------------------
- * %k %K %0 black dark grey black
- * %r %R %1 red bold red red
- * %g %G %2 green bold green green
- * %y %Y %3 yellow bold yellow yellow
- * %b %B %4 blue bold blue blue
- * %m %M %5 magenta bold magenta magenta
- * %p %P magenta (think: purple)
- * %c %C %6 cyan bold cyan cyan
- * %w %W %7 white bold white white
- *
- * %F Blinking, Flashing
- * %U Underline
- * %8 Reverse
- * %_,%9 Bold
- *
- * %n Resets the color
- * %% A single %
- *
- * First param is the string to convert, second is an optional flag if
- * colors should be used. It defaults to true, if set to false, the
- * colorcodes will just be removed (And %% will be transformed into %)
- *
- * @param string $string String to convert
- * @param bool $colored Should the string be colored?
- *
- * @return string
- */
- public static function renderColoredString($string)
- {
- $colored = true;
-
-
- static $conversions = array ( // static so the array doesn't get built
- // everytime
- // %y - yellow, and so on... {{{
- '%y' => array('color' => 'yellow'),
- '%g' => array('color' => 'green' ),
- '%b' => array('color' => 'blue' ),
- '%r' => array('color' => 'red' ),
- '%p' => array('color' => 'purple'),
- '%m' => array('color' => 'purple'),
- '%c' => array('color' => 'cyan' ),
- '%w' => array('color' => 'grey' ),
- '%k' => array('color' => 'black' ),
- '%n' => array('color' => 'reset' ),
- '%Y' => array('color' => 'yellow', 'style' => 'light'),
- '%G' => array('color' => 'green', 'style' => 'light'),
- '%B' => array('color' => 'blue', 'style' => 'light'),
- '%R' => array('color' => 'red', 'style' => 'light'),
- '%P' => array('color' => 'purple', 'style' => 'light'),
- '%M' => array('color' => 'purple', 'style' => 'light'),
- '%C' => array('color' => 'cyan', 'style' => 'light'),
- '%W' => array('color' => 'grey', 'style' => 'light'),
- '%K' => array('color' => 'black', 'style' => 'light'),
- '%N' => array('color' => 'reset', 'style' => 'light'),
- '%3' => array('background' => 'yellow'),
- '%2' => array('background' => 'green' ),
- '%4' => array('background' => 'blue' ),
- '%1' => array('background' => 'red' ),
- '%5' => array('background' => 'purple'),
- '%6' => array('background' => 'cyan' ),
- '%7' => array('background' => 'grey' ),
- '%0' => array('background' => 'black' ),
- // Don't use this, I can't stand flashing text
- '%F' => array('style' => 'blink'),
- '%U' => array('style' => 'underline'),
- '%8' => array('style' => 'inverse'),
- '%9' => array('style' => 'bold'),
- '%_' => array('style' => 'bold')
- // }}}
- );
-
- if ($colored) {
- $string = str_replace('%%', '% ', $string);
- foreach ($conversions as $key => $value) {
- $string = str_replace($key, Console_Color::color($value),
- $string);
- }
- $string = str_replace('% ', '%', $string);
-
- } else {
- $string = preg_replace('/%((%)|.)/', '$2', $string);
- }
-
- return $string;
- }
-
- /**
- * Escapes % so they don't get interpreted as color codes
- *
- * @param string $string String to escape
- *
- * @access public
- * @return string
- */
- public static function escape($string)
- {
- return str_replace('%', '%%', $string);
- }
-}
diff --git a/framework/util/FileHelper.php b/framework/util/FileHelper.php
deleted file mode 100644
index 9108476..0000000
--- a/framework/util/FileHelper.php
+++ /dev/null
@@ -1,274 +0,0 @@
-
- * @author Alex Makarov
- * @since 2.0
- */
-class FileHelper
-{
- /**
- * Returns the extension name of a file path.
- * For example, the path "path/to/something.php" would return "php".
- * @param string $path the file path
- * @return string the extension name without the dot character.
- */
- public static function getExtension($path)
- {
- return pathinfo($path, PATHINFO_EXTENSION);
- }
-
- /**
- * Checks the given path and ensures it is a directory.
- * This method will call `realpath()` to "normalize" the given path.
- * If the given path does not refer to an existing directory, an exception will be thrown.
- * @param string $path the given path. This can also be a path alias.
- * @return string the normalized path
- * @throws InvalidConfigException if the path does not refer to an existing directory.
- */
- public static function ensureDirectory($path)
- {
- $p = \Yii::getAlias($path);
- if (($p = realpath($p)) !== false && is_dir($p)) {
- return $p;
- } else {
- throw new InvalidConfigException('Directory does not exist: ' . $path);
- }
- }
-
- /**
- * Normalizes a file/directory path.
- * After normalization, the directory separators in the path will be `DIRECTORY_SEPARATOR`,
- * and any trailing directory separators will be removed. For example, '/home\demo/' on Linux
- * will be normalized as '/home/demo'.
- * @param string $path the file/directory path to be normalized
- * @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`.
- * @return string the normalized file/directory path
- */
- public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR)
- {
- return rtrim(strtr($path, array('/' => $ds, '\\' => $ds)), $ds);
- }
-
- /**
- * Returns the localized version of a specified file.
- *
- * The searching is based on the specified language code. In particular,
- * a file with the same name will be looked for under the subdirectory
- * whose name is same as the language code. For example, given the file "path/to/view.php"
- * and language code "zh_cn", the localized file will be looked for as
- * "path/to/zh_cn/view.php". If the file is not found, the original file
- * will be returned.
- *
- * If the target and the source language codes are the same,
- * the original file will be returned.
- *
- * For consistency, it is recommended that the language code is given
- * in lower case and in the format of LanguageID_RegionID (e.g. "en_us").
- *
- * @param string $file the original file
- * @param string $language the target language that the file should be localized to.
- * If not set, the value of [[\yii\base\Application::language]] will be used.
- * @param string $sourceLanguage the language that the original file is in.
- * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used.
- * @return string the matching localized file, or the original file if the localized version is not found.
- * If the target and the source language codes are the same, the original file will be returned.
- */
- public static function localize($file, $language = null, $sourceLanguage = null)
- {
- if ($language === null) {
- $language = \Yii::$app->language;
- }
- if ($sourceLanguage === null) {
- $sourceLanguage = \Yii::$app->sourceLanguage;
- }
- if ($language === $sourceLanguage) {
- return $file;
- }
- $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $sourceLanguage . DIRECTORY_SEPARATOR . basename($file);
- return is_file($desiredFile) ? $desiredFile : $file;
- }
-
- /**
- * Determines the MIME type of the specified file.
- * This method will first try to determine the MIME type based on
- * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If this doesn't work, it will
- * fall back to [[getMimeTypeByExtension()]].
- * @param string $file the file name.
- * @param string $magicFile name of the optional magic database file, usually something like `/path/to/magic.mime`.
- * This will be passed as the second parameter to [finfo_open](http://php.net/manual/en/function.finfo-open.php).
- * @param boolean $checkExtension whether to use the file extension to determine the MIME type in case
- * `finfo_open()` cannot determine it.
- * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined.
- */
- public static function getMimeType($file, $magicFile = null, $checkExtension = true)
- {
- if (function_exists('finfo_open')) {
- $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile);
- if ($info && ($result = finfo_file($info, $file)) !== false) {
- return $result;
- }
- }
-
- return $checkExtension ? self::getMimeTypeByExtension($file) : null;
- }
-
- /**
- * Determines the MIME type based on the extension name of the specified file.
- * This method will use a local map between extension names and MIME types.
- * @param string $file the file name.
- * @param string $magicFile the path of the file that contains all available MIME type information.
- * If this is not set, the default file aliased by `@yii/util/mimeTypes.php` will be used.
- * @return string the MIME type. Null is returned if the MIME type cannot be determined.
- */
- public static function getMimeTypeByExtension($file, $magicFile = null)
- {
- if ($magicFile === null) {
- $magicFile = \Yii::getAlias('@yii/util/mimeTypes.php');
- }
- $mimeTypes = require($magicFile);
- if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') {
- $ext = strtolower($ext);
- if (isset($mimeTypes[$ext])) {
- return $mimeTypes[$ext];
- }
- }
- return null;
- }
-
- /**
- * Copies a list of files from one place to another.
- * @param array $fileList the list of files to be copied (name=>spec).
- * The array keys are names displayed during the copy process, and array values are specifications
- * for files to be copied. Each array value must be an array of the following structure:
- *
- *
source: required, the full path of the file/directory to be copied from
- *
target: required, the full path of the file/directory to be copied to
- *
callback: optional, the callback to be invoked when copying a file. The callback function
- * should be declared as follows:
- *
- * function foo($source,$params)
- *
- * where $source parameter is the source file path, and the content returned
- * by the function will be saved into the target file.
- *
params: optional, the parameters to be passed to the callback
+ * would make the output cache depends on the last modified time of all posts.
+ * If any post has its modification time changed, the cached content would be invalidated.
+ */
+ public $dependency;
+ /**
+ * @var boolean whether the content being cached should be differentiated according to route.
+ * A route consists of the requested controller ID and action ID.
+ * Defaults to true.
+ */
+ public $varyByRoute = true;
+ /**
+ * @var boolean whether the content being cached should be differentiated according to user's language.
+ * A language is retrieved via Yii::app()->language.
+ * Defaults to false.
+ * @since 1.1.14
+ */
+ public $varyByLanguage = false;
+ /**
+ * @var array list of GET parameters that should participate in cache key calculation.
+ * By setting this property, the output cache will use different cached data
+ * for each different set of GET parameter values.
+ */
+ public $varyByParam;
+ /**
+ * @var string a PHP expression whose result is used in the cache key calculation.
+ * By setting this property, the output cache will use different cached data
+ * for each different expression result.
+ * The expression can also be a valid PHP callback,
+ * including class method name (array(ClassName/Object, MethodName)),
+ * or anonymous function (PHP 5.3.0+). The function/method signature should be as follows:
+ *
+ * function foo($cache) { ... }
+ *
+ * where $cache refers to the output cache component.
+ */
+ public $varyByExpression;
+ /**
+ * @var array list of request types (e.g. GET, POST) for which the cache should be enabled only.
+ * Defaults to null, meaning all request types.
+ */
+ public $requestTypes;
+
+ private $_key;
+ private $_cache;
+ private $_contentCached;
+ private $_content;
+ private $_actions;
+
+ /**
+ * Marks the start of content to be cached.
+ * Content displayed after this method call and before {@link endCache()}
+ * will be captured and saved in cache.
+ * This method does nothing if valid content is already found in cache.
+ */
+ public function init()
+ {
+ if ($this->getIsContentCached()) {
+ $this->replayActions();
+ } elseif ($this->_cache !== null) {
+ $this->getController()->getCachingStack()->push($this);
+ ob_start();
+ ob_implicit_flush(false);
+ }
+ }
+
+ /**
+ * Marks the end of content to be cached.
+ * Content displayed before this method call and after {@link init()}
+ * will be captured and saved in cache.
+ * This method does nothing if valid content is already found in cache.
+ */
+ public function run()
+ {
+ if ($this->getIsContentCached()) {
+ if ($this->getController()->isCachingStackEmpty()) {
+ echo $this->getController()->processDynamicOutput($this->_content);
+ } else {
+ echo $this->_content;
+ }
+ } elseif ($this->_cache !== null) {
+ $this->_content = ob_get_clean();
+ $this->getController()->getCachingStack()->pop();
+ $data = array($this->_content, $this->_actions);
+ if (is_array($this->dependency)) {
+ $this->dependency = Yii::createComponent($this->dependency);
+ }
+ $this->_cache->set($this->getCacheKey(), $data, $this->duration, $this->dependency);
+
+ if ($this->getController()->isCachingStackEmpty()) {
+ echo $this->getController()->processDynamicOutput($this->_content);
+ } else {
+ echo $this->_content;
+ }
+ }
+ }
+
+ /**
+ * @return boolean whether the content can be found from cache
+ */
+ public function getIsContentCached()
+ {
+ if ($this->_contentCached !== null) {
+ return $this->_contentCached;
+ } else {
+ return $this->_contentCached = $this->checkContentCache();
+ }
+ }
+
+ /**
+ * Looks for content in cache.
+ * @return boolean whether the content is found in cache.
+ */
+ protected function checkContentCache()
+ {
+ if ((empty($this->requestTypes) || in_array(Yii::app()->getRequest()->getRequestType(), $this->requestTypes))
+ && ($this->_cache = $this->getCache()) !== null
+ ) {
+ if ($this->duration > 0 && ($data = $this->_cache->get($this->getCacheKey())) !== false) {
+ $this->_content = $data[0];
+ $this->_actions = $data[1];
+ return true;
+ }
+ if ($this->duration == 0) {
+ $this->_cache->delete($this->getCacheKey());
+ }
+ if ($this->duration <= 0) {
+ $this->_cache = null;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return ICache the cache used for caching the content.
+ */
+ protected function getCache()
+ {
+ return Yii::app()->getComponent($this->cacheID);
+ }
+
+ /**
+ * Caclulates the base cache key.
+ * The calculated key will be further variated in {@link getCacheKey}.
+ * Derived classes may override this method if more variations are needed.
+ * @return string basic cache key without variations
+ */
+ protected function getBaseCacheKey()
+ {
+ return self::CACHE_KEY_PREFIX . $this->getId() . '.';
+ }
+
+ /**
+ * Calculates the cache key.
+ * The key is calculated based on {@link getBaseCacheKey} and other factors, including
+ * {@link varyByRoute}, {@link varyByParam}, {@link varyBySession} and {@link varyByLanguage}.
+ * @return string cache key
+ */
+ protected function getCacheKey()
+ {
+ if ($this->_key !== null) {
+ return $this->_key;
+ } else {
+ $key = $this->getBaseCacheKey() . '.';
+ if ($this->varyByRoute) {
+ $controller = $this->getController();
+ $key .= $controller->getUniqueId() . '/';
+ if (($action = $controller->getAction()) !== null) {
+ $key .= $action->getId();
+ }
+ }
+ $key .= '.';
+
+ if ($this->varyBySession) {
+ $key .= Yii::app()->getSession()->getSessionID();
+ }
+ $key .= '.';
+
+ if (is_array($this->varyByParam) && isset($this->varyByParam[0])) {
+ $params = array();
+ foreach ($this->varyByParam as $name) {
+ if (isset($_GET[$name])) {
+ $params[$name] = $_GET[$name];
+ } else {
+ $params[$name] = '';
+ }
+ }
+ $key .= serialize($params);
+ }
+ $key .= '.';
+
+ if ($this->varyByExpression !== null) {
+ $key .= $this->evaluateExpression($this->varyByExpression);
+ }
+ $key .= '.';
+
+ if ($this->varyByLanguage) {
+ $key .= Yii::app()->language;
+ }
+ $key .= '.';
+
+ return $this->_key = $key;
+ }
+ }
+
+ /**
+ * Records a method call when this output cache is in effect.
+ * When the content is served from the output cache, the recorded
+ * method will be re-invoked.
+ * @param string $context a property name of the controller. The property should refer to an object
+ * whose method is being recorded. If empty it means the controller itself.
+ * @param string $method the method name
+ * @param array $params parameters passed to the method
+ */
+ public function recordAction($context, $method, $params)
+ {
+ $this->_actions[] = array($context, $method, $params);
+ }
+
+ /**
+ * Replays the recorded method calls.
+ */
+ protected function replayActions()
+ {
+ if (empty($this->_actions)) {
+ return;
+ }
+ $controller = $this->getController();
+ $cs = Yii::app()->getClientScript();
+ foreach ($this->_actions as $action) {
+ if ($action[0] === 'clientScript') {
+ $object = $cs;
+ } elseif ($action[0] === '') {
+ $object = $controller;
+ } else {
+ $object = $controller->{$action[0]};
+ }
+ if (method_exists($object, $action[1])) {
+ call_user_func_array(array($object, $action[1]), $action[2]);
+ } elseif ($action[0] === '' && function_exists($action[1])) {
+ call_user_func_array($action[1], $action[2]);
+ } else {
+ throw new CException(Yii::t('yii', 'Unable to replay the action "{object}.{method}". The method does not exist.',
+ array('object' => $action[0],
+ 'method' => $action[1])));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/todo.md b/todo.md
index 4d5343a..f102f41 100644
--- a/todo.md
+++ b/todo.md
@@ -39,7 +39,6 @@ memo
* consider to be released as a separate tool for user app docs
- i18n
* consider using PHP built-in support and data
- * message translations, choice format
* formatting: number and date
* parsing??
* make dates/date patterns uniform application-wide including JUI, formats etc.
From 392b293d33e428a3e7540b9dbc1313a37928e528 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Mon, 25 Mar 2013 14:27:15 -0400
Subject: [PATCH 79/84] FragmentCache WIP
---
framework/base/ActionFilter.php | 12 +-
framework/base/View.php | 5 +-
framework/caching/Cache.php | 18 +--
framework/caching/DbDependency.php | 37 ++---
framework/db/Command.php | 8 +-
framework/db/Schema.php | 7 +-
framework/helpers/ConsoleColor.php | 8 -
framework/web/CacheSession.php | 3 +-
framework/widgets/FragmentCache.php | 282 ++++++++++--------------------------
9 files changed, 138 insertions(+), 242 deletions(-)
diff --git a/framework/base/ActionFilter.php b/framework/base/ActionFilter.php
index 2655c5a..0ae7ab8 100644
--- a/framework/base/ActionFilter.php
+++ b/framework/base/ActionFilter.php
@@ -11,9 +11,19 @@ namespace yii\base;
* @author Qiang Xue
* @since 2.0
*/
-class Filter extends Behavior
+class ActionFilter extends Behavior
{
/**
+ * @var array list of action IDs that this filter should apply to. If this property is not set,
+ * then the filter applies to all actions, unless they are listed in [[except]].
+ */
+ public $only;
+ /**
+ * @var array list of action IDs that this filter should not apply to.
+ */
+ public $except = array();
+
+ /**
* Declares event handlers for the [[owner]]'s events.
* @return array events (array keys) and the corresponding event handler methods (array values).
*/
diff --git a/framework/base/View.php b/framework/base/View.php
index baa1d10..18748f2 100644
--- a/framework/base/View.php
+++ b/framework/base/View.php
@@ -337,15 +337,16 @@ class View extends Component
* ~~~
*
* @param string $id a unique ID identifying the fragment to be cached.
- * @param array $properties initial property values for [[\yii\widgets\OutputCache]]
+ * @param array $properties initial property values for [[\yii\widgets\FragmentCache]]
* @return boolean whether you should generate the content for caching.
* False if the cached version is available.
*/
public function beginCache($id, $properties = array())
{
$properties['id'] = $id;
+ /** @var $cache \yii\widgets\FragmentCache */
$cache = $this->beginWidget('yii\widgets\OutputCache', $properties);
- if ($cache->getIsContentCached()) {
+ if ($cache->getCachedContent() !== false) {
$this->endCache();
return false;
} else {
diff --git a/framework/caching/Cache.php b/framework/caching/Cache.php
index f3f1bc0..70cf8cb 100644
--- a/framework/caching/Cache.php
+++ b/framework/caching/Cache.php
@@ -8,6 +8,7 @@
namespace yii\caching;
use yii\base\Component;
+use yii\helpers\StringHelper;
/**
* Cache is the base class for cache classes supporting different cache storage implementation.
@@ -70,13 +71,13 @@ abstract class Cache extends Component implements \ArrayAccess
/**
- * Builds a normalized cache key from one or multiple parameters.
+ * Builds a normalized cache key from a given key.
*
* The generated key contains letters and digits only, and its length is no more than 32.
*
- * If only one parameter is given and it is already a normalized key, then
- * it will be returned back without change. Otherwise, a normalized key
- * is generated by serializing all given parameters and applying MD5 hashing.
+ * If the given key is a string containing alphanumeric characters only and no more than 32 characters,
+ * then the key will be returned back without change. Otherwise, a normalized key
+ * is generated by serializing the given key and applying MD5 hashing.
*
* The following example builds a cache key using three parameters:
*
@@ -84,16 +85,15 @@ abstract class Cache extends Component implements \ArrayAccess
* $key = $cache->buildKey($className, $method, $id);
* ~~~
*
- * @param string $key the first parameter
+ * @param array|string $key the key to be normalized
* @return string the generated cache key
*/
public function buildKey($key)
{
- if (func_num_args() === 1 && ctype_alnum($key) && strlen($key) <= 32) {
- return (string)$key;
+ if (is_string($key)) {
+ return ctype_alnum($key) && StringHelper::strlen($key) <= 32 ? $key : md5($key);
} else {
- $params = func_get_args();
- return md5(json_encode($params));
+ return md5(json_encode($key));
}
}
diff --git a/framework/caching/DbDependency.php b/framework/caching/DbDependency.php
index 9c6e1f1..247109b 100644
--- a/framework/caching/DbDependency.php
+++ b/framework/caching/DbDependency.php
@@ -7,15 +7,15 @@
namespace yii\caching;
+use Yii;
use yii\base\InvalidConfigException;
use yii\db\Connection;
-use yii\db\Query;
/**
* DbDependency represents a dependency based on the query result of a SQL statement.
*
* If the query result changes, the dependency is considered as changed.
- * The query is specified via the [[query]] property.
+ * The query is specified via the [[sql]] property.
*
* @author Qiang Xue
* @since 2.0
@@ -27,23 +27,25 @@ class DbDependency extends Dependency
*/
public $connectionID = 'db';
/**
- * @var Query the SQL query whose result is used to determine if the dependency has been changed.
+ * @var string the SQL query whose result is used to determine if the dependency has been changed.
* Only the first row of the query result will be used.
*/
- public $query;
+ public $sql;
/**
- * @var Connection the DB connection instance
+ * @var array the parameters (name=>value) to be bound to the SQL statement specified by [[sql]].
*/
- private $_db;
+ public $params;
/**
* Constructor.
- * @param Query $query the SQL query whose result is used to determine if the dependency has been changed.
+ * @param string $sql the SQL query whose result is used to determine if the dependency has been changed.
+ * @param array $params the parameters (name=>value) to be bound to the SQL statement specified by [[sql]].
* @param array $config name-value pairs that will be used to initialize the object properties
*/
- public function __construct($query = null, $config = array())
+ public function __construct($sql, $params = array(), $config = array())
{
- $this->query = $query;
+ $this->sql = $sql;
+ $this->params = $params;
parent::__construct($config);
}
@@ -66,22 +68,23 @@ class DbDependency extends Dependency
protected function generateDependencyData()
{
$db = $this->getDb();
- /**
- * @var \yii\db\Command $command
- */
- $command = $this->query->createCommand($db);
if ($db->enableQueryCache) {
// temporarily disable and re-enable query caching
$db->enableQueryCache = false;
- $result = $command->queryRow();
+ $result = $db->createCommand($this->sql, $this->params)->queryRow();
$db->enableQueryCache = true;
} else {
- $result = $command->queryRow();
+ $result = $db->createCommand($this->sql, $this->params)->queryRow();
}
return $result;
}
/**
+ * @var Connection the DB connection instance
+ */
+ private $_db;
+
+ /**
* Returns the DB connection instance used for caching purpose.
* @return Connection the DB connection instance
* @throws InvalidConfigException if [[connectionID]] does not point to a valid application component.
@@ -89,11 +92,11 @@ class DbDependency extends Dependency
public function getDb()
{
if ($this->_db === null) {
- $db = \Yii::$app->getComponent($this->connectionID);
+ $db = Yii::$app->getComponent($this->connectionID);
if ($db instanceof Connection) {
$this->_db = $db;
} else {
- throw new InvalidConfigException("DbCache::connectionID must refer to the ID of a DB application component.");
+ throw new InvalidConfigException("DbCacheDependency::connectionID must refer to the ID of a DB application component.");
}
}
return $this->_db;
diff --git a/framework/db/Command.php b/framework/db/Command.php
index 0861b8d..c2b2e05 100644
--- a/framework/db/Command.php
+++ b/framework/db/Command.php
@@ -389,7 +389,13 @@ class Command extends \yii\base\Component
}
if (isset($cache)) {
- $cacheKey = $cache->buildKey(__CLASS__, $db->dsn, $db->username, $sql, $paramLog);
+ $cacheKey = $cache->buildKey(array(
+ __CLASS__,
+ $db->dsn,
+ $db->username,
+ $sql,
+ $paramLog,
+ ));
if (($result = $cache->get($cacheKey)) !== false) {
\Yii::trace('Query result found in cache', __CLASS__);
return $result;
diff --git a/framework/db/Schema.php b/framework/db/Schema.php
index 5a6ffda..5fe6121 100644
--- a/framework/db/Schema.php
+++ b/framework/db/Schema.php
@@ -109,7 +109,12 @@ abstract class Schema extends \yii\base\Object
*/
public function getCacheKey($cache, $name)
{
- return $cache->buildKey(__CLASS__, $this->db->dsn, $this->db->username, $name);
+ return $cache->buildKey(array(
+ __CLASS__,
+ $this->db->dsn,
+ $this->db->username,
+ $name,
+ ));
}
/**
diff --git a/framework/helpers/ConsoleColor.php b/framework/helpers/ConsoleColor.php
index c64db24..429aeb1 100644
--- a/framework/helpers/ConsoleColor.php
+++ b/framework/helpers/ConsoleColor.php
@@ -7,15 +7,7 @@
namespace yii\helpers;
-// todo define how subclassing will work
-// todo add a run() or render() method
// todo test this on all kinds of terminals, especially windows (check out lib ncurses)
-// todo not sure if all methods should be static
-
-// todo subclass DetailView
-// todo subclass GridView
-// todo more subclasses
-
/**
* Console View is the base class for console view components
diff --git a/framework/web/CacheSession.php b/framework/web/CacheSession.php
index 25524a0..d7882a6 100644
--- a/framework/web/CacheSession.php
+++ b/framework/web/CacheSession.php
@@ -45,6 +45,7 @@ class CacheSession extends Session
{
return true;
}
+
/**
* Returns the cache instance used for storing session data.
* @return Cache the cache instance
@@ -114,6 +115,6 @@ class CacheSession extends Session
*/
protected function calculateKey($id)
{
- return $this->getCache()->buildKey(__CLASS__, $id);
+ return $this->getCache()->buildKey(array(__CLASS__, $id));
}
}
diff --git a/framework/widgets/FragmentCache.php b/framework/widgets/FragmentCache.php
index e6805f5..38073f0 100644
--- a/framework/widgets/FragmentCache.php
+++ b/framework/widgets/FragmentCache.php
@@ -7,7 +7,11 @@
namespace yii\widgets;
+use Yii;
+use yii\base\InvalidConfigException;
use yii\base\Widget;
+use yii\caching\Cache;
+use yii\caching\Dependency;
/**
* @author Qiang Xue
@@ -16,81 +20,47 @@ use yii\base\Widget;
class FragmentCache extends Widget
{
/**
- * Prefix to the keys for storing cached data
- */
- const CACHE_KEY_PREFIX = 'Yii.COutputCache.';
-
- /**
* @var string the ID of the cache application component. Defaults to 'cache' (the primary cache application component.)
*/
public $cacheID = 'cache';
/**
- * @var integer number of seconds that the data can remain in cache. Defaults to 60 seconds.
- * If it is 0, existing cached content would be removed from the cache.
- * If it is a negative value, the cache will be disabled (any existing cached content will
- * remain in the cache.)
- *
- * Note, if cache dependency changes or cache space is limited,
- * the data may be purged out of cache earlier.
+ * @var integer number of seconds that the data can remain valid in cache.
+ * Use 0 to indicate that the cached data will never expire.
*/
public $duration = 60;
/**
- * @var mixed the dependency that the cached content depends on.
- * This can be either an object implementing {@link ICacheDependency} interface or an array
- * specifying the configuration of the dependency object. For example,
- *
+ * @var array|Dependency the dependency that the cached content depends on.
+ * This can be either a [[Dependency]] object or a configuration array for creating the dependency object.
+ * For example,
+ *
+ * ~~~
* array(
- * 'class'=>'CDbCacheDependency',
- * 'sql'=>'SELECT MAX(lastModified) FROM Post',
+ * 'class' => 'yii\caching\DbDependency',
+ * 'sql' => 'SELECT MAX(lastModified) FROM Post',
* )
- *
+ * ~~~
+ *
* would make the output cache depends on the last modified time of all posts.
* If any post has its modification time changed, the cached content would be invalidated.
*/
public $dependency;
/**
- * @var boolean whether the content being cached should be differentiated according to route.
- * A route consists of the requested controller ID and action ID.
- * Defaults to true.
- */
- public $varyByRoute = true;
- /**
- * @var boolean whether the content being cached should be differentiated according to user's language.
- * A language is retrieved via Yii::app()->language.
- * Defaults to false.
- * @since 1.1.14
- */
- public $varyByLanguage = false;
- /**
- * @var array list of GET parameters that should participate in cache key calculation.
- * By setting this property, the output cache will use different cached data
- * for each different set of GET parameter values.
- */
- public $varyByParam;
- /**
- * @var string a PHP expression whose result is used in the cache key calculation.
- * By setting this property, the output cache will use different cached data
- * for each different expression result.
- * The expression can also be a valid PHP callback,
- * including class method name (array(ClassName/Object, MethodName)),
- * or anonymous function (PHP 5.3.0+). The function/method signature should be as follows:
- *
- * function foo($cache) { ... }
- *
- * where $cache refers to the output cache component.
+ * @var array list of factors that would cause the variation of the content being cached.
+ * Each factor is a string representing a variation (e.g. the language, a GET parameter).
+ * The following variation setting will cause the content to be cached in different versions
+ * according to the current application language:
+ *
+ * ~~~
+ * array(
+ * Yii::$app->language,
+ * )
*/
- public $varyByExpression;
+ public $variations;
/**
- * @var array list of request types (e.g. GET, POST) for which the cache should be enabled only.
- * Defaults to null, meaning all request types.
+ * @var boolean whether to enable the fragment cache. You may use this property to turn on and off
+ * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests).
*/
- public $requestTypes;
-
- private $_key;
- private $_cache;
- private $_contentCached;
- private $_content;
- private $_actions;
+ public $enabled = true;
/**
* Marks the start of content to be cached.
@@ -100,10 +70,7 @@ class FragmentCache extends Widget
*/
public function init()
{
- if ($this->getIsContentCached()) {
- $this->replayActions();
- } elseif ($this->_cache !== null) {
- $this->getController()->getCachingStack()->push($this);
+ if ($this->getCachedContent() === false && $this->getCache() !== null) {
ob_start();
ob_implicit_flush(false);
}
@@ -117,178 +84,89 @@ class FragmentCache extends Widget
*/
public function run()
{
- if ($this->getIsContentCached()) {
- if ($this->getController()->isCachingStackEmpty()) {
- echo $this->getController()->processDynamicOutput($this->_content);
- } else {
- echo $this->_content;
- }
- } elseif ($this->_cache !== null) {
- $this->_content = ob_get_clean();
- $this->getController()->getCachingStack()->pop();
- $data = array($this->_content, $this->_actions);
+ if (($content = $this->getCachedContent()) !== false) {
+ echo $content;
+ } elseif (($cache = $this->getCache()) !== false) {
+ $content = ob_get_clean();
if (is_array($this->dependency)) {
- $this->dependency = Yii::createComponent($this->dependency);
- }
- $this->_cache->set($this->getCacheKey(), $data, $this->duration, $this->dependency);
-
- if ($this->getController()->isCachingStackEmpty()) {
- echo $this->getController()->processDynamicOutput($this->_content);
- } else {
- echo $this->_content;
+ $this->dependency = Yii::createObject($this->dependency);
}
+ $cache->set($this->calculateKey(), $content, $this->duration, $this->dependency);
+ echo $content;
}
}
/**
- * @return boolean whether the content can be found from cache
+ * @var string|boolean the cached content. False if the content is not cached.
*/
- public function getIsContentCached()
- {
- if ($this->_contentCached !== null) {
- return $this->_contentCached;
- } else {
- return $this->_contentCached = $this->checkContentCache();
- }
- }
+ private $_content;
/**
- * Looks for content in cache.
- * @return boolean whether the content is found in cache.
+ * Returns the cached content if available.
+ * @return string|boolean the cached content. False is returned if valid content is not found in the cache.
*/
- protected function checkContentCache()
+ public function getCachedContent()
{
- if ((empty($this->requestTypes) || in_array(Yii::app()->getRequest()->getRequestType(), $this->requestTypes))
- && ($this->_cache = $this->getCache()) !== null
- ) {
- if ($this->duration > 0 && ($data = $this->_cache->get($this->getCacheKey())) !== false) {
- $this->_content = $data[0];
- $this->_actions = $data[1];
- return true;
- }
- if ($this->duration == 0) {
- $this->_cache->delete($this->getCacheKey());
- }
- if ($this->duration <= 0) {
- $this->_cache = null;
+ if ($this->_content === null) {
+ if (($cache = $this->getCache()) !== null) {
+ $key = $this->calculateKey();
+ $this->_content = $cache->get($key);
+ } else {
+ $this->_content = false;
}
}
- return false;
+ return $this->_content;
}
/**
- * @return ICache the cache used for caching the content.
+ * Generates a unique key used for storing the content in cache.
+ * The key generated depends on both [[id]] and [[variations]].
+ * @return string a valid cache key
*/
- protected function getCache()
+ protected function calculateKey()
{
- return Yii::app()->getComponent($this->cacheID);
+ $factors = array(__CLASS__, $this->getId());
+ if (is_array($this->variations)) {
+ foreach ($this->variations as $factor) {
+ $factors[] = $factor;
+ }
+ }
+ return $this->getCache()->buildKey($factors);
}
/**
- * Caclulates the base cache key.
- * The calculated key will be further variated in {@link getCacheKey}.
- * Derived classes may override this method if more variations are needed.
- * @return string basic cache key without variations
+ * @var Cache
*/
- protected function getBaseCacheKey()
- {
- return self::CACHE_KEY_PREFIX . $this->getId() . '.';
- }
+ private $_cache;
/**
- * Calculates the cache key.
- * The key is calculated based on {@link getBaseCacheKey} and other factors, including
- * {@link varyByRoute}, {@link varyByParam}, {@link varyBySession} and {@link varyByLanguage}.
- * @return string cache key
+ * Returns the cache instance used for storing content.
+ * @return Cache the cache instance. Null is returned if the cache component is not available
+ * or [[enabled]] is false.
+ * @throws InvalidConfigException if [[cacheID]] does not point to a valid application component.
*/
- protected function getCacheKey()
+ public function getCache()
{
- if ($this->_key !== null) {
- return $this->_key;
- } else {
- $key = $this->getBaseCacheKey() . '.';
- if ($this->varyByRoute) {
- $controller = $this->getController();
- $key .= $controller->getUniqueId() . '/';
- if (($action = $controller->getAction()) !== null) {
- $key .= $action->getId();
- }
- }
- $key .= '.';
-
- if ($this->varyBySession) {
- $key .= Yii::app()->getSession()->getSessionID();
- }
- $key .= '.';
-
- if (is_array($this->varyByParam) && isset($this->varyByParam[0])) {
- $params = array();
- foreach ($this->varyByParam as $name) {
- if (isset($_GET[$name])) {
- $params[$name] = $_GET[$name];
- } else {
- $params[$name] = '';
- }
- }
- $key .= serialize($params);
- }
- $key .= '.';
-
- if ($this->varyByExpression !== null) {
- $key .= $this->evaluateExpression($this->varyByExpression);
- }
- $key .= '.';
-
- if ($this->varyByLanguage) {
- $key .= Yii::app()->language;
+ if (!$this->enabled) {
+ return null;
+ }
+ if ($this->_cache === null) {
+ $cache = Yii::$app->getComponent($this->cacheID);
+ if ($cache instanceof Cache) {
+ $this->_cache = $cache;
+ } else {
+ throw new InvalidConfigException('FragmentCache::cacheID must refer to the ID of a cache application component.');
}
- $key .= '.';
-
- return $this->_key = $key;
}
+ return $this->_cache;
}
/**
- * Records a method call when this output cache is in effect.
- * When the content is served from the output cache, the recorded
- * method will be re-invoked.
- * @param string $context a property name of the controller. The property should refer to an object
- * whose method is being recorded. If empty it means the controller itself.
- * @param string $method the method name
- * @param array $params parameters passed to the method
- */
- public function recordAction($context, $method, $params)
- {
- $this->_actions[] = array($context, $method, $params);
- }
-
- /**
- * Replays the recorded method calls.
+ * Sets the cache instance used by the session component.
+ * @param Cache $value the cache instance
*/
- protected function replayActions()
+ public function setCache($value)
{
- if (empty($this->_actions)) {
- return;
- }
- $controller = $this->getController();
- $cs = Yii::app()->getClientScript();
- foreach ($this->_actions as $action) {
- if ($action[0] === 'clientScript') {
- $object = $cs;
- } elseif ($action[0] === '') {
- $object = $controller;
- } else {
- $object = $controller->{$action[0]};
- }
- if (method_exists($object, $action[1])) {
- call_user_func_array(array($object, $action[1]), $action[2]);
- } elseif ($action[0] === '' && function_exists($action[1])) {
- call_user_func_array($action[1], $action[2]);
- } else {
- throw new CException(Yii::t('yii', 'Unable to replay the action "{object}.{method}". The method does not exist.',
- array('object' => $action[0],
- 'method' => $action[1])));
- }
- }
+ $this->_cache = $value;
}
}
\ No newline at end of file
From 2f3cc8f16ecd9bfb55e847b5245faf949ce54a84 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Mon, 25 Mar 2013 14:37:25 -0400
Subject: [PATCH 80/84] finished ActionFilter.
---
framework/base/ActionFilter.php | 43 +++++++++++++++++++++++++++++++++++++----
todo.md | 17 ----------------
2 files changed, 39 insertions(+), 21 deletions(-)
diff --git a/framework/base/ActionFilter.php b/framework/base/ActionFilter.php
index 0ae7ab8..1f82e5d 100644
--- a/framework/base/ActionFilter.php
+++ b/framework/base/ActionFilter.php
@@ -30,8 +30,8 @@ class ActionFilter extends Behavior
public function events()
{
return array(
- 'beforeAction' => 'beforeAction',
- 'afterAction' => 'afterAction',
+ 'beforeAction' => 'beforeFilter',
+ 'afterAction' => 'afterFilter',
);
}
@@ -39,8 +39,11 @@ class ActionFilter extends Behavior
* @param ActionEvent $event
* @return boolean
*/
- public function beforeAction($event)
+ public function beforeFilter($event)
{
+ if ($this->isActive($event->action)) {
+ $event->isValid = $this->beforeAction($event->action);
+ }
return $event->isValid;
}
@@ -48,8 +51,40 @@ class ActionFilter extends Behavior
* @param ActionEvent $event
* @return boolean
*/
- public function afterAction($event)
+ public function afterFilter($event)
{
+ if ($this->isActive($event->action)) {
+ $this->afterAction($event->action);
+ }
+ }
+
+ /**
+ * This method is invoked right before an action is to be executed (after all possible filters.)
+ * You may override this method to do last-minute preparation for the action.
+ * @param Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ */
+ public function beforeAction($action)
+ {
+ return true;
+ }
+
+ /**
+ * This method is invoked right after an action is executed.
+ * You may override this method to do some postprocessing for the action.
+ * @param Action $action the action just executed.
+ */
+ public function afterAction($action)
+ {
+ }
+ /**
+ * Returns a value indicating whether the filer is active for the given action.
+ * @param Action $action the action being filtered
+ * @return boolean whether the filer is active for the given action.
+ */
+ protected function isActive($action)
+ {
+ return !in_array($action->id, $this->except, true) && (empty($this->only) || in_array($action->id, $this->only, true));
}
}
\ No newline at end of file
diff --git a/todo.md b/todo.md
index f102f41..f66d3c1 100644
--- a/todo.md
+++ b/todo.md
@@ -1,21 +1,4 @@
-- console
- * If console is executed using Windows, do not use colors. If not, use colors. Allow to override via console application settings.
-- db
- * pgsql, sql server, oracle, db2 drivers
- * unit tests on different DB drivers
- * document-based (should allow storage-specific methods additionally to generic ones)
- * mongodb (put it under framework/db/mongodb)
- * key-value-based (should allow storage-specific methods additionally to generic ones)
- * redis (put it under framework/db/redis or perhaps framework/caching?)
-- base
- * TwigViewRenderer (Alex)
- * SmartyViewRenderer (Alex)
-- logging
- * WebTarget (TBD after web is in place): should consider using javascript and make it into a toolbar
- * ProfileTarget (TBD after web is in place): should consider using javascript and make it into a toolbar
- * unit tests
- caching
- * backend-specific unit tests
* dependency unit tests
- validators
* Refactor validators to add validateValue() for every validator, if possible. Check if value is an array.
From 80dbaaca8bb390b735b1384f05aad6a71acfb7c8 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Mon, 25 Mar 2013 19:37:42 -0400
Subject: [PATCH 81/84] bug fixes.
---
framework/base/Application.php | 3 +++
framework/base/ErrorHandler.php | 8 ++++----
framework/views/exception.php | 2 +-
framework/web/Application.php | 6 ++++++
4 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/framework/base/Application.php b/framework/base/Application.php
index 31087e2..fd2ecad 100644
--- a/framework/base/Application.php
+++ b/framework/base/Application.php
@@ -479,6 +479,9 @@ class Application extends Module
$msg = (string)$e;
$msg .= "\nPrevious exception:\n";
$msg .= (string)$exception;
+ if (YII_DEBUG) {
+ echo $msg;
+ }
$msg .= "\n\$_SERVER = " . var_export($_SERVER, true);
error_log($msg);
exit(1);
diff --git a/framework/base/ErrorHandler.php b/framework/base/ErrorHandler.php
index a3ab137..f71b8c8 100644
--- a/framework/base/ErrorHandler.php
+++ b/framework/base/ErrorHandler.php
@@ -78,7 +78,7 @@ class ErrorHandler extends Component
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
\Yii::$app->renderException($exception);
} else {
- $view = new View($this);
+ $view = new View;
if (!YII_DEBUG || $exception instanceof UserException) {
$viewName = $this->errorView;
} else {
@@ -86,7 +86,7 @@ class ErrorHandler extends Component
}
echo $view->render($viewName, array(
'exception' => $exception,
- ));
+ ), $this);
}
} else {
\Yii::$app->renderException($exception);
@@ -255,8 +255,8 @@ class ErrorHandler extends Component
{
$view = new View;
$name = !YII_DEBUG || $exception instanceof HttpException ? $this->errorView : $this->exceptionView;
- echo $view->render($this, $name, array(
+ echo $view->render($name, array(
'exception' => $exception,
- ));
+ ), $this);
}
}
diff --git a/framework/views/exception.php b/framework/views/exception.php
index 6257ffe..db29302 100644
--- a/framework/views/exception.php
+++ b/framework/views/exception.php
@@ -183,7 +183,7 @@ $title = $context->htmlEncode($exception instanceof \yii\base\Exception || $exce
- versionInfo : ''?>
+ getVersionInfo() : ''?>
diff --git a/framework/web/Application.php b/framework/web/Application.php
index 61e84a3..6e0cc73 100644
--- a/framework/web/Application.php
+++ b/framework/web/Application.php
@@ -6,6 +6,7 @@
*/
namespace yii\web;
+
use yii\base\InvalidParamException;
/**
@@ -17,6 +18,11 @@ use yii\base\InvalidParamException;
class Application extends \yii\base\Application
{
/**
+ * @var string the default route of this application. Defaults to 'site'.
+ */
+ public $defaultRoute = 'site';
+
+ /**
* Sets default path aliases.
*/
public function registerDefaultAliases()
From 27f5c49bd5e0dd49ce1f53a64c986a44823eb702 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Mon, 25 Mar 2013 19:39:09 -0400
Subject: [PATCH 82/84] fragment cache WIP
---
framework/base/View.php | 19 +++++++++++++++++++
framework/widgets/FragmentCache.php | 33 +++++++++++++++++++++++++++++----
2 files changed, 48 insertions(+), 4 deletions(-)
diff --git a/framework/base/View.php b/framework/base/View.php
index 18748f2..7170251 100644
--- a/framework/base/View.php
+++ b/framework/base/View.php
@@ -156,6 +156,25 @@ class View extends Component
return ob_get_clean();
}
+ public function renderDynamic($statements)
+ {
+ if (!empty($this->cachingStack)) {
+ $n = count($this->_dynamicOutput);
+ $placeholder = "";
+ foreach ($this->cachingStack as $cache) {
+ $cache->dynamicPlaceholders[$placeholder] = $statements;
+ }
+ return $placeholder;
+ } else {
+ return $this->evaluateDynamicContent($statements);
+ }
+ }
+
+ public function evaluateDynamicContent($statements)
+ {
+ return eval($statements);
+ }
+
/**
* Finds the view file based on the given view name.
*
diff --git a/framework/widgets/FragmentCache.php b/framework/widgets/FragmentCache.php
index 38073f0..b56b86f 100644
--- a/framework/widgets/FragmentCache.php
+++ b/framework/widgets/FragmentCache.php
@@ -61,6 +61,16 @@ class FragmentCache extends Widget
* the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests).
*/
public $enabled = true;
+ /**
+ * @var \yii\base\View the view object within which this widget is sued. If not set,
+ * the view registered with the application will be used. This is mainly used by dynamic content feature.
+ */
+ public $view;
+ /**
+ * @var array
+ */
+ public $dynamicPlaceholders;
+
/**
* Marks the start of content to be cached.
@@ -70,7 +80,11 @@ class FragmentCache extends Widget
*/
public function init()
{
+ if ($this->view === null) {
+ $this->view = Yii::$app->getView();
+ }
if ($this->getCachedContent() === false && $this->getCache() !== null) {
+ array_push($this->view->cachingStack, $this);
ob_start();
ob_implicit_flush(false);
}
@@ -88,10 +102,12 @@ class FragmentCache extends Widget
echo $content;
} elseif (($cache = $this->getCache()) !== false) {
$content = ob_get_clean();
+ array_pop($this->view->cachingStack);
if (is_array($this->dependency)) {
$this->dependency = Yii::createObject($this->dependency);
}
- $cache->set($this->calculateKey(), $content, $this->duration, $this->dependency);
+ $data = array($content, $this->dynamicPlaceholders);
+ $cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
echo $content;
}
}
@@ -108,11 +124,20 @@ class FragmentCache extends Widget
public function getCachedContent()
{
if ($this->_content === null) {
+ $this->_content = false;
if (($cache = $this->getCache()) !== null) {
$key = $this->calculateKey();
- $this->_content = $cache->get($key);
- } else {
- $this->_content = false;
+ $data = $cache->get($key);
+ if (is_array($data) && count($data) === 2) {
+ list ($content, $placeholders) = $data;
+ if (is_array($placeholders) && count($placeholders) > 0) {
+ foreach ($placeholders as $name => $statements) {
+ $placeholders[$name] = $this->view->evaluateDynamicContent($statements);
+ }
+ $content = strtr($content, $placeholders);
+ }
+ $this->_content = $content;
+ }
}
}
return $this->_content;
From de5c304f64426d0585205469fdf5d0a9a386ecac Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Mon, 25 Mar 2013 22:26:25 -0400
Subject: [PATCH 83/84] finished fragment caching.
---
framework/YiiBase.php | 2 +-
framework/base/Component.php | 6 ++--
framework/base/Object.php | 8 ++---
framework/base/View.php | 60 ++++++++++++++++++++++++++++++-------
framework/widgets/FragmentCache.php | 30 ++++++++++++++-----
5 files changed, 80 insertions(+), 26 deletions(-)
diff --git a/framework/YiiBase.php b/framework/YiiBase.php
index 678856c..16e237d 100644
--- a/framework/YiiBase.php
+++ b/framework/YiiBase.php
@@ -298,7 +298,7 @@ class YiiBase
}
}
- if (isset($classFile, $alias)) {
+ if (isset($classFile, $alias) && is_file($classFile)) {
if (!YII_DEBUG || basename(realpath($classFile)) === basename($alias) . '.php') {
include($classFile);
return true;
diff --git a/framework/base/Component.php b/framework/base/Component.php
index 515f7e1..2d081d3 100644
--- a/framework/base/Component.php
+++ b/framework/base/Component.php
@@ -58,7 +58,7 @@ class Component extends \yii\base\Object
}
}
}
- throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '.' . $name);
+ throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
/**
@@ -105,9 +105,9 @@ class Component extends \yii\base\Object
}
}
if (method_exists($this, 'get' . $name)) {
- throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '.' . $name);
+ throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
} else {
- throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '.' . $name);
+ throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
}
}
diff --git a/framework/base/Object.php b/framework/base/Object.php
index f589e38..3bd8378 100644
--- a/framework/base/Object.php
+++ b/framework/base/Object.php
@@ -65,7 +65,7 @@ class Object
if (method_exists($this, $getter)) {
return $this->$getter();
} else {
- throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '.' . $name);
+ throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
}
@@ -86,9 +86,9 @@ class Object
if (method_exists($this, $setter)) {
$this->$setter($value);
} elseif (method_exists($this, 'get' . $name)) {
- throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '.' . $name);
+ throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
} else {
- throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '.' . $name);
+ throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
}
}
@@ -129,7 +129,7 @@ class Object
if (method_exists($this, $setter)) {
$this->$setter(null);
} elseif (method_exists($this, 'get' . $name)) {
- throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '.' . $name);
+ throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '::' . $name);
}
}
diff --git a/framework/base/View.php b/framework/base/View.php
index 7170251..c7087c1 100644
--- a/framework/base/View.php
+++ b/framework/base/View.php
@@ -45,11 +45,21 @@ class View extends Component
* through this property.
*/
public $clips;
-
/**
- * @var Widget[] the widgets that are currently not ended
+ * @var Widget[] the widgets that are currently being rendered (not ended). This property
+ * is maintained by [[beginWidget()]] and [[endWidget()]] methods. Do not modify it directly.
+ */
+ public $widgetStack = array();
+ /**
+ * @var array a list of currently active fragment cache widgets. This property
+ * is used internally to implement the content caching feature. Do not modify it.
+ */
+ public $cacheStack = array();
+ /**
+ * @var array a list of placeholders for embedding dynamic contents. This property
+ * is used internally to implement the content caching feature. Do not modify it.
*/
- private $_widgetStack = array();
+ public $dynamicPlaceholders = array();
/**
@@ -156,20 +166,47 @@ class View extends Component
return ob_get_clean();
}
+ /**
+ * Renders dynamic content returned by the given PHP statements.
+ * This method is mainly used together with content caching (fragment caching and page caching)
+ * when some portions of the content (called *dynamic content*) should not be cached.
+ * The dynamic content must be returned by some PHP statements.
+ * @param string $statements the PHP statements for generating the dynamic content.
+ * @return string the placeholder of the dynamic content, or the dynamic content if there is no
+ * active content cache currently.
+ */
public function renderDynamic($statements)
{
- if (!empty($this->cachingStack)) {
- $n = count($this->_dynamicOutput);
+ if (!empty($this->cacheStack)) {
+ $n = count($this->dynamicPlaceholders);
$placeholder = "";
- foreach ($this->cachingStack as $cache) {
- $cache->dynamicPlaceholders[$placeholder] = $statements;
- }
+ $this->addDynamicPlaceholder($placeholder, $statements);
return $placeholder;
} else {
return $this->evaluateDynamicContent($statements);
}
}
+ /**
+ * Adds a placeholder for dynamic content.
+ * This method is internally used.
+ * @param string $placeholder the placeholder name
+ * @param string $statements the PHP statements for generating the dynamic content
+ */
+ public function addDynamicPlaceholder($placeholder, $statements)
+ {
+ foreach ($this->cacheStack as $cache) {
+ $cache->dynamicPlaceholders[$placeholder] = $statements;
+ }
+ $this->dynamicPlaceholders[$placeholder] = $statements;
+ }
+
+ /**
+ * Evaluates the given PHP statements.
+ * This method is mainly used internally to implement dynamic content feature.
+ * @param string $statements the PHP statements to be evaluated.
+ * @return mixed the return value of the PHP statements.
+ */
public function evaluateDynamicContent($statements)
{
return eval($statements);
@@ -267,7 +304,7 @@ class View extends Component
public function beginWidget($class, $properties = array())
{
$widget = $this->createWidget($class, $properties);
- $this->_widgetStack[] = $widget;
+ $this->widgetStack[] = $widget;
return $widget;
}
@@ -281,7 +318,7 @@ class View extends Component
*/
public function endWidget()
{
- $widget = array_pop($this->_widgetStack);
+ $widget = array_pop($this->widgetStack);
if ($widget instanceof Widget) {
$widget->run();
return $widget;
@@ -363,8 +400,9 @@ class View extends Component
public function beginCache($id, $properties = array())
{
$properties['id'] = $id;
+ $properties['view'] = $this;
/** @var $cache \yii\widgets\FragmentCache */
- $cache = $this->beginWidget('yii\widgets\OutputCache', $properties);
+ $cache = $this->beginWidget('yii\widgets\FragmentCache', $properties);
if ($cache->getCachedContent() !== false) {
$this->endCache();
return false;
diff --git a/framework/widgets/FragmentCache.php b/framework/widgets/FragmentCache.php
index b56b86f..3bb321e 100644
--- a/framework/widgets/FragmentCache.php
+++ b/framework/widgets/FragmentCache.php
@@ -67,7 +67,8 @@ class FragmentCache extends Widget
*/
public $view;
/**
- * @var array
+ * @var array a list of placeholders for embedding dynamic contents. This property
+ * is used internally to implement the content caching feature. Do not modify it.
*/
public $dynamicPlaceholders;
@@ -83,8 +84,8 @@ class FragmentCache extends Widget
if ($this->view === null) {
$this->view = Yii::$app->getView();
}
- if ($this->getCachedContent() === false && $this->getCache() !== null) {
- array_push($this->view->cachingStack, $this);
+ if ($this->getCache() !== null && $this->getCachedContent() === false) {
+ $this->view->cacheStack[] = $this;
ob_start();
ob_implicit_flush(false);
}
@@ -100,14 +101,18 @@ class FragmentCache extends Widget
{
if (($content = $this->getCachedContent()) !== false) {
echo $content;
- } elseif (($cache = $this->getCache()) !== false) {
+ } elseif (($cache = $this->getCache()) !== null) {
$content = ob_get_clean();
- array_pop($this->view->cachingStack);
+ array_pop($this->view->cacheStack);
if (is_array($this->dependency)) {
$this->dependency = Yii::createObject($this->dependency);
}
$data = array($content, $this->dynamicPlaceholders);
$cache->set($this->calculateKey(), $data, $this->duration, $this->dependency);
+
+ if ($this->view->cacheStack === array() && !empty($this->dynamicPlaceholders)) {
+ $content = $this->updateDynamicContent($content, $this->dynamicPlaceholders);
+ }
echo $content;
}
}
@@ -131,10 +136,13 @@ class FragmentCache extends Widget
if (is_array($data) && count($data) === 2) {
list ($content, $placeholders) = $data;
if (is_array($placeholders) && count($placeholders) > 0) {
+ if ($this->view->cacheStack === array()) {
+ // outermost cache: replace placeholder with dynamic content
+ $content = $this->updateDynamicContent($content, $placeholders);
+ }
foreach ($placeholders as $name => $statements) {
- $placeholders[$name] = $this->view->evaluateDynamicContent($statements);
+ $this->view->addDynamicPlaceholder($name, $statements);
}
- $content = strtr($content, $placeholders);
}
$this->_content = $content;
}
@@ -143,6 +151,14 @@ class FragmentCache extends Widget
return $this->_content;
}
+ protected function updateDynamicContent($content, $placeholders)
+ {
+ foreach ($placeholders as $name => $statements) {
+ $placeholders[$name] = $this->view->evaluateDynamicContent($statements);
+ }
+ return strtr($content, $placeholders);
+ }
+
/**
* Generates a unique key used for storing the content in cache.
* The key generated depends on both [[id]] and [[variations]].
From 0ed14a741d2c13f66e2023823975287694845621 Mon Sep 17 00:00:00 2001
From: Qiang Xue
Date: Mon, 25 Mar 2013 23:11:32 -0400
Subject: [PATCH 84/84] Finished PageCache.
---
framework/web/PageCache.php | 110 ++++++++++++++++++++++++++++++++++++
framework/widgets/FragmentCache.php | 2 +-
2 files changed, 111 insertions(+), 1 deletion(-)
create mode 100644 framework/web/PageCache.php
diff --git a/framework/web/PageCache.php b/framework/web/PageCache.php
new file mode 100644
index 0000000..24cddea
--- /dev/null
+++ b/framework/web/PageCache.php
@@ -0,0 +1,110 @@
+
+ * @since 2.0
+ */
+class PageCache extends ActionFilter
+{
+ /**
+ * @var boolean whether the content being cached should be differentiated according to the route.
+ * A route consists of the requested controller ID and action ID. Defaults to true.
+ */
+ public $varyByRoute = true;
+ /**
+ * @var View the view object that is used to create the fragment cache widget to implement page caching.
+ * If not set, the view registered with the application will be used.
+ */
+ public $view;
+
+ /**
+ * @var string the ID of the cache application component. Defaults to 'cache' (the primary cache application component.)
+ */
+ public $cacheID = 'cache';
+ /**
+ * @var integer number of seconds that the data can remain valid in cache.
+ * Use 0 to indicate that the cached data will never expire.
+ */
+ public $duration = 60;
+ /**
+ * @var array|Dependency the dependency that the cached content depends on.
+ * This can be either a [[Dependency]] object or a configuration array for creating the dependency object.
+ * For example,
+ *
+ * ~~~
+ * array(
+ * 'class' => 'yii\caching\DbDependency',
+ * 'sql' => 'SELECT MAX(lastModified) FROM Post',
+ * )
+ * ~~~
+ *
+ * would make the output cache depends on the last modified time of all posts.
+ * If any post has its modification time changed, the cached content would be invalidated.
+ */
+ public $dependency;
+ /**
+ * @var array list of factors that would cause the variation of the content being cached.
+ * Each factor is a string representing a variation (e.g. the language, a GET parameter).
+ * The following variation setting will cause the content to be cached in different versions
+ * according to the current application language:
+ *
+ * ~~~
+ * array(
+ * Yii::$app->language,
+ * )
+ */
+ public $variations;
+ /**
+ * @var boolean whether to enable the fragment cache. You may use this property to turn on and off
+ * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests).
+ */
+ public $enabled = true;
+
+
+ public function init()
+ {
+ parent::init();
+ if ($this->view === null) {
+ $this->view = Yii::$app->getView();
+ }
+ }
+
+ /**
+ * This method is invoked right before an action is to be executed (after all possible filters.)
+ * You may override this method to do last-minute preparation for the action.
+ * @param Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ */
+ public function beforeAction($action)
+ {
+ $properties = array();
+ foreach (array('cacheID', 'duration', 'dependency', 'variations', 'enabled') as $name) {
+ $properties[$name] = $this->$name;
+ }
+ $id = $this->varyByRoute ? $action->getUniqueId() : __CLASS__;
+ return $this->view->beginCache($id, $properties);
+ }
+
+ /**
+ * This method is invoked right after an action is executed.
+ * You may override this method to do some postprocessing for the action.
+ * @param Action $action the action just executed.
+ */
+ public function afterAction($action)
+ {
+ $this->view->endCache();
+ }
+}
\ No newline at end of file
diff --git a/framework/widgets/FragmentCache.php b/framework/widgets/FragmentCache.php
index 3bb321e..d5185f8 100644
--- a/framework/widgets/FragmentCache.php
+++ b/framework/widgets/FragmentCache.php
@@ -62,7 +62,7 @@ class FragmentCache extends Widget
*/
public $enabled = true;
/**
- * @var \yii\base\View the view object within which this widget is sued. If not set,
+ * @var \yii\base\View the view object within which this widget is used. If not set,
* the view registered with the application will be used. This is mainly used by dynamic content feature.
*/
public $view;